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 action: &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 line_start = Point::new(row, 0);
13633 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13634
13635 if action.keep_selections {
13636 // Keep the selection range for each line
13637 let selection_start = if row == selection.start.row {
13638 selection.start
13639 } else {
13640 line_start
13641 };
13642 new_selection_ranges.push(selection_start..line_end);
13643 } else {
13644 // Collapse to cursor at end of line
13645 new_selection_ranges.push(line_end..line_end);
13646 }
13647 }
13648
13649 let is_multiline_selection = selection.start.row != selection.end.row;
13650 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13651 // so this action feels more ergonomic when paired with other selection operations
13652 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13653 if !should_skip_last {
13654 if action.keep_selections {
13655 if is_multiline_selection {
13656 let line_start = Point::new(selection.end.row, 0);
13657 new_selection_ranges.push(line_start..selection.end);
13658 } else {
13659 new_selection_ranges.push(selection.start..selection.end);
13660 }
13661 } else {
13662 new_selection_ranges.push(selection.end..selection.end);
13663 }
13664 }
13665 }
13666 }
13667 self.change_selections(Default::default(), window, cx, |s| {
13668 s.select_ranges(new_selection_ranges);
13669 });
13670 }
13671
13672 pub fn add_selection_above(
13673 &mut self,
13674 _: &AddSelectionAbove,
13675 window: &mut Window,
13676 cx: &mut Context<Self>,
13677 ) {
13678 self.add_selection(true, window, cx);
13679 }
13680
13681 pub fn add_selection_below(
13682 &mut self,
13683 _: &AddSelectionBelow,
13684 window: &mut Window,
13685 cx: &mut Context<Self>,
13686 ) {
13687 self.add_selection(false, window, cx);
13688 }
13689
13690 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13691 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13692
13693 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13694 let all_selections = self.selections.all::<Point>(cx);
13695 let text_layout_details = self.text_layout_details(window);
13696
13697 let (mut columnar_selections, new_selections_to_columnarize) = {
13698 if let Some(state) = self.add_selections_state.as_ref() {
13699 let columnar_selection_ids: HashSet<_> = state
13700 .groups
13701 .iter()
13702 .flat_map(|group| group.stack.iter())
13703 .copied()
13704 .collect();
13705
13706 all_selections
13707 .into_iter()
13708 .partition(|s| columnar_selection_ids.contains(&s.id))
13709 } else {
13710 (Vec::new(), all_selections)
13711 }
13712 };
13713
13714 let mut state = self
13715 .add_selections_state
13716 .take()
13717 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13718
13719 for selection in new_selections_to_columnarize {
13720 let range = selection.display_range(&display_map).sorted();
13721 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13722 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13723 let positions = start_x.min(end_x)..start_x.max(end_x);
13724 let mut stack = Vec::new();
13725 for row in range.start.row().0..=range.end.row().0 {
13726 if let Some(selection) = self.selections.build_columnar_selection(
13727 &display_map,
13728 DisplayRow(row),
13729 &positions,
13730 selection.reversed,
13731 &text_layout_details,
13732 ) {
13733 stack.push(selection.id);
13734 columnar_selections.push(selection);
13735 }
13736 }
13737 if !stack.is_empty() {
13738 if above {
13739 stack.reverse();
13740 }
13741 state.groups.push(AddSelectionsGroup { above, stack });
13742 }
13743 }
13744
13745 let mut final_selections = Vec::new();
13746 let end_row = if above {
13747 DisplayRow(0)
13748 } else {
13749 display_map.max_point().row()
13750 };
13751
13752 let mut last_added_item_per_group = HashMap::default();
13753 for group in state.groups.iter_mut() {
13754 if let Some(last_id) = group.stack.last() {
13755 last_added_item_per_group.insert(*last_id, group);
13756 }
13757 }
13758
13759 for selection in columnar_selections {
13760 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13761 if above == group.above {
13762 let range = selection.display_range(&display_map).sorted();
13763 debug_assert_eq!(range.start.row(), range.end.row());
13764 let mut row = range.start.row();
13765 let positions =
13766 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13767 px(start)..px(end)
13768 } else {
13769 let start_x =
13770 display_map.x_for_display_point(range.start, &text_layout_details);
13771 let end_x =
13772 display_map.x_for_display_point(range.end, &text_layout_details);
13773 start_x.min(end_x)..start_x.max(end_x)
13774 };
13775
13776 let mut maybe_new_selection = None;
13777 while row != end_row {
13778 if above {
13779 row.0 -= 1;
13780 } else {
13781 row.0 += 1;
13782 }
13783 if let Some(new_selection) = self.selections.build_columnar_selection(
13784 &display_map,
13785 row,
13786 &positions,
13787 selection.reversed,
13788 &text_layout_details,
13789 ) {
13790 maybe_new_selection = Some(new_selection);
13791 break;
13792 }
13793 }
13794
13795 if let Some(new_selection) = maybe_new_selection {
13796 group.stack.push(new_selection.id);
13797 if above {
13798 final_selections.push(new_selection);
13799 final_selections.push(selection);
13800 } else {
13801 final_selections.push(selection);
13802 final_selections.push(new_selection);
13803 }
13804 } else {
13805 final_selections.push(selection);
13806 }
13807 } else {
13808 group.stack.pop();
13809 }
13810 } else {
13811 final_selections.push(selection);
13812 }
13813 }
13814
13815 self.change_selections(Default::default(), window, cx, |s| {
13816 s.select(final_selections);
13817 });
13818
13819 let final_selection_ids: HashSet<_> = self
13820 .selections
13821 .all::<Point>(cx)
13822 .iter()
13823 .map(|s| s.id)
13824 .collect();
13825 state.groups.retain_mut(|group| {
13826 // selections might get merged above so we remove invalid items from stacks
13827 group.stack.retain(|id| final_selection_ids.contains(id));
13828
13829 // single selection in stack can be treated as initial state
13830 group.stack.len() > 1
13831 });
13832
13833 if !state.groups.is_empty() {
13834 self.add_selections_state = Some(state);
13835 }
13836 }
13837
13838 fn select_match_ranges(
13839 &mut self,
13840 range: Range<usize>,
13841 reversed: bool,
13842 replace_newest: bool,
13843 auto_scroll: Option<Autoscroll>,
13844 window: &mut Window,
13845 cx: &mut Context<Editor>,
13846 ) {
13847 self.unfold_ranges(
13848 std::slice::from_ref(&range),
13849 false,
13850 auto_scroll.is_some(),
13851 cx,
13852 );
13853 let effects = if let Some(scroll) = auto_scroll {
13854 SelectionEffects::scroll(scroll)
13855 } else {
13856 SelectionEffects::no_scroll()
13857 };
13858 self.change_selections(effects, window, cx, |s| {
13859 if replace_newest {
13860 s.delete(s.newest_anchor().id);
13861 }
13862 if reversed {
13863 s.insert_range(range.end..range.start);
13864 } else {
13865 s.insert_range(range);
13866 }
13867 });
13868 }
13869
13870 pub fn select_next_match_internal(
13871 &mut self,
13872 display_map: &DisplaySnapshot,
13873 replace_newest: bool,
13874 autoscroll: Option<Autoscroll>,
13875 window: &mut Window,
13876 cx: &mut Context<Self>,
13877 ) -> Result<()> {
13878 let buffer = &display_map.buffer_snapshot;
13879 let mut selections = self.selections.all::<usize>(cx);
13880 if let Some(mut select_next_state) = self.select_next_state.take() {
13881 let query = &select_next_state.query;
13882 if !select_next_state.done {
13883 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13884 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13885 let mut next_selected_range = None;
13886
13887 let bytes_after_last_selection =
13888 buffer.bytes_in_range(last_selection.end..buffer.len());
13889 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13890 let query_matches = query
13891 .stream_find_iter(bytes_after_last_selection)
13892 .map(|result| (last_selection.end, result))
13893 .chain(
13894 query
13895 .stream_find_iter(bytes_before_first_selection)
13896 .map(|result| (0, result)),
13897 );
13898
13899 for (start_offset, query_match) in query_matches {
13900 let query_match = query_match.unwrap(); // can only fail due to I/O
13901 let offset_range =
13902 start_offset + query_match.start()..start_offset + query_match.end();
13903
13904 if !select_next_state.wordwise
13905 || (!buffer.is_inside_word(offset_range.start, false)
13906 && !buffer.is_inside_word(offset_range.end, false))
13907 {
13908 // TODO: This is n^2, because we might check all the selections
13909 if !selections
13910 .iter()
13911 .any(|selection| selection.range().overlaps(&offset_range))
13912 {
13913 next_selected_range = Some(offset_range);
13914 break;
13915 }
13916 }
13917 }
13918
13919 if let Some(next_selected_range) = next_selected_range {
13920 self.select_match_ranges(
13921 next_selected_range,
13922 last_selection.reversed,
13923 replace_newest,
13924 autoscroll,
13925 window,
13926 cx,
13927 );
13928 } else {
13929 select_next_state.done = true;
13930 }
13931 }
13932
13933 self.select_next_state = Some(select_next_state);
13934 } else {
13935 let mut only_carets = true;
13936 let mut same_text_selected = true;
13937 let mut selected_text = None;
13938
13939 let mut selections_iter = selections.iter().peekable();
13940 while let Some(selection) = selections_iter.next() {
13941 if selection.start != selection.end {
13942 only_carets = false;
13943 }
13944
13945 if same_text_selected {
13946 if selected_text.is_none() {
13947 selected_text =
13948 Some(buffer.text_for_range(selection.range()).collect::<String>());
13949 }
13950
13951 if let Some(next_selection) = selections_iter.peek() {
13952 if next_selection.range().len() == selection.range().len() {
13953 let next_selected_text = buffer
13954 .text_for_range(next_selection.range())
13955 .collect::<String>();
13956 if Some(next_selected_text) != selected_text {
13957 same_text_selected = false;
13958 selected_text = None;
13959 }
13960 } else {
13961 same_text_selected = false;
13962 selected_text = None;
13963 }
13964 }
13965 }
13966 }
13967
13968 if only_carets {
13969 for selection in &mut selections {
13970 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13971 selection.start = word_range.start;
13972 selection.end = word_range.end;
13973 selection.goal = SelectionGoal::None;
13974 selection.reversed = false;
13975 self.select_match_ranges(
13976 selection.start..selection.end,
13977 selection.reversed,
13978 replace_newest,
13979 autoscroll,
13980 window,
13981 cx,
13982 );
13983 }
13984
13985 if selections.len() == 1 {
13986 let selection = selections
13987 .last()
13988 .expect("ensured that there's only one selection");
13989 let query = buffer
13990 .text_for_range(selection.start..selection.end)
13991 .collect::<String>();
13992 let is_empty = query.is_empty();
13993 let select_state = SelectNextState {
13994 query: AhoCorasick::new(&[query])?,
13995 wordwise: true,
13996 done: is_empty,
13997 };
13998 self.select_next_state = Some(select_state);
13999 } else {
14000 self.select_next_state = None;
14001 }
14002 } else if let Some(selected_text) = selected_text {
14003 self.select_next_state = Some(SelectNextState {
14004 query: AhoCorasick::new(&[selected_text])?,
14005 wordwise: false,
14006 done: false,
14007 });
14008 self.select_next_match_internal(
14009 display_map,
14010 replace_newest,
14011 autoscroll,
14012 window,
14013 cx,
14014 )?;
14015 }
14016 }
14017 Ok(())
14018 }
14019
14020 pub fn select_all_matches(
14021 &mut self,
14022 _action: &SelectAllMatches,
14023 window: &mut Window,
14024 cx: &mut Context<Self>,
14025 ) -> Result<()> {
14026 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14027
14028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14029
14030 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14031 let Some(select_next_state) = self.select_next_state.as_mut() else {
14032 return Ok(());
14033 };
14034 if select_next_state.done {
14035 return Ok(());
14036 }
14037
14038 let mut new_selections = Vec::new();
14039
14040 let reversed = self.selections.oldest::<usize>(cx).reversed;
14041 let buffer = &display_map.buffer_snapshot;
14042 let query_matches = select_next_state
14043 .query
14044 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14045
14046 for query_match in query_matches.into_iter() {
14047 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14048 let offset_range = if reversed {
14049 query_match.end()..query_match.start()
14050 } else {
14051 query_match.start()..query_match.end()
14052 };
14053
14054 if !select_next_state.wordwise
14055 || (!buffer.is_inside_word(offset_range.start, false)
14056 && !buffer.is_inside_word(offset_range.end, false))
14057 {
14058 new_selections.push(offset_range.start..offset_range.end);
14059 }
14060 }
14061
14062 select_next_state.done = true;
14063
14064 if new_selections.is_empty() {
14065 log::error!("bug: new_selections is empty in select_all_matches");
14066 return Ok(());
14067 }
14068
14069 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14070 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14071 selections.select_ranges(new_selections)
14072 });
14073
14074 Ok(())
14075 }
14076
14077 pub fn select_next(
14078 &mut self,
14079 action: &SelectNext,
14080 window: &mut Window,
14081 cx: &mut Context<Self>,
14082 ) -> Result<()> {
14083 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14084 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14085 self.select_next_match_internal(
14086 &display_map,
14087 action.replace_newest,
14088 Some(Autoscroll::newest()),
14089 window,
14090 cx,
14091 )?;
14092 Ok(())
14093 }
14094
14095 pub fn select_previous(
14096 &mut self,
14097 action: &SelectPrevious,
14098 window: &mut Window,
14099 cx: &mut Context<Self>,
14100 ) -> Result<()> {
14101 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14102 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14103 let buffer = &display_map.buffer_snapshot;
14104 let mut selections = self.selections.all::<usize>(cx);
14105 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14106 let query = &select_prev_state.query;
14107 if !select_prev_state.done {
14108 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14109 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14110 let mut next_selected_range = None;
14111 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14112 let bytes_before_last_selection =
14113 buffer.reversed_bytes_in_range(0..last_selection.start);
14114 let bytes_after_first_selection =
14115 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14116 let query_matches = query
14117 .stream_find_iter(bytes_before_last_selection)
14118 .map(|result| (last_selection.start, result))
14119 .chain(
14120 query
14121 .stream_find_iter(bytes_after_first_selection)
14122 .map(|result| (buffer.len(), result)),
14123 );
14124 for (end_offset, query_match) in query_matches {
14125 let query_match = query_match.unwrap(); // can only fail due to I/O
14126 let offset_range =
14127 end_offset - query_match.end()..end_offset - query_match.start();
14128
14129 if !select_prev_state.wordwise
14130 || (!buffer.is_inside_word(offset_range.start, false)
14131 && !buffer.is_inside_word(offset_range.end, false))
14132 {
14133 next_selected_range = Some(offset_range);
14134 break;
14135 }
14136 }
14137
14138 if let Some(next_selected_range) = next_selected_range {
14139 self.select_match_ranges(
14140 next_selected_range,
14141 last_selection.reversed,
14142 action.replace_newest,
14143 Some(Autoscroll::newest()),
14144 window,
14145 cx,
14146 );
14147 } else {
14148 select_prev_state.done = true;
14149 }
14150 }
14151
14152 self.select_prev_state = Some(select_prev_state);
14153 } else {
14154 let mut only_carets = true;
14155 let mut same_text_selected = true;
14156 let mut selected_text = None;
14157
14158 let mut selections_iter = selections.iter().peekable();
14159 while let Some(selection) = selections_iter.next() {
14160 if selection.start != selection.end {
14161 only_carets = false;
14162 }
14163
14164 if same_text_selected {
14165 if selected_text.is_none() {
14166 selected_text =
14167 Some(buffer.text_for_range(selection.range()).collect::<String>());
14168 }
14169
14170 if let Some(next_selection) = selections_iter.peek() {
14171 if next_selection.range().len() == selection.range().len() {
14172 let next_selected_text = buffer
14173 .text_for_range(next_selection.range())
14174 .collect::<String>();
14175 if Some(next_selected_text) != selected_text {
14176 same_text_selected = false;
14177 selected_text = None;
14178 }
14179 } else {
14180 same_text_selected = false;
14181 selected_text = None;
14182 }
14183 }
14184 }
14185 }
14186
14187 if only_carets {
14188 for selection in &mut selections {
14189 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14190 selection.start = word_range.start;
14191 selection.end = word_range.end;
14192 selection.goal = SelectionGoal::None;
14193 selection.reversed = false;
14194 self.select_match_ranges(
14195 selection.start..selection.end,
14196 selection.reversed,
14197 action.replace_newest,
14198 Some(Autoscroll::newest()),
14199 window,
14200 cx,
14201 );
14202 }
14203 if selections.len() == 1 {
14204 let selection = selections
14205 .last()
14206 .expect("ensured that there's only one selection");
14207 let query = buffer
14208 .text_for_range(selection.start..selection.end)
14209 .collect::<String>();
14210 let is_empty = query.is_empty();
14211 let select_state = SelectNextState {
14212 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14213 wordwise: true,
14214 done: is_empty,
14215 };
14216 self.select_prev_state = Some(select_state);
14217 } else {
14218 self.select_prev_state = None;
14219 }
14220 } else if let Some(selected_text) = selected_text {
14221 self.select_prev_state = Some(SelectNextState {
14222 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14223 wordwise: false,
14224 done: false,
14225 });
14226 self.select_previous(action, window, cx)?;
14227 }
14228 }
14229 Ok(())
14230 }
14231
14232 pub fn find_next_match(
14233 &mut self,
14234 _: &FindNextMatch,
14235 window: &mut Window,
14236 cx: &mut Context<Self>,
14237 ) -> Result<()> {
14238 let selections = self.selections.disjoint_anchors();
14239 match selections.first() {
14240 Some(first) if selections.len() >= 2 => {
14241 self.change_selections(Default::default(), window, cx, |s| {
14242 s.select_ranges([first.range()]);
14243 });
14244 }
14245 _ => self.select_next(
14246 &SelectNext {
14247 replace_newest: true,
14248 },
14249 window,
14250 cx,
14251 )?,
14252 }
14253 Ok(())
14254 }
14255
14256 pub fn find_previous_match(
14257 &mut self,
14258 _: &FindPreviousMatch,
14259 window: &mut Window,
14260 cx: &mut Context<Self>,
14261 ) -> Result<()> {
14262 let selections = self.selections.disjoint_anchors();
14263 match selections.last() {
14264 Some(last) if selections.len() >= 2 => {
14265 self.change_selections(Default::default(), window, cx, |s| {
14266 s.select_ranges([last.range()]);
14267 });
14268 }
14269 _ => self.select_previous(
14270 &SelectPrevious {
14271 replace_newest: true,
14272 },
14273 window,
14274 cx,
14275 )?,
14276 }
14277 Ok(())
14278 }
14279
14280 pub fn toggle_comments(
14281 &mut self,
14282 action: &ToggleComments,
14283 window: &mut Window,
14284 cx: &mut Context<Self>,
14285 ) {
14286 if self.read_only(cx) {
14287 return;
14288 }
14289 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14290 let text_layout_details = &self.text_layout_details(window);
14291 self.transact(window, cx, |this, window, cx| {
14292 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14293 let mut edits = Vec::new();
14294 let mut selection_edit_ranges = Vec::new();
14295 let mut last_toggled_row = None;
14296 let snapshot = this.buffer.read(cx).read(cx);
14297 let empty_str: Arc<str> = Arc::default();
14298 let mut suffixes_inserted = Vec::new();
14299 let ignore_indent = action.ignore_indent;
14300
14301 fn comment_prefix_range(
14302 snapshot: &MultiBufferSnapshot,
14303 row: MultiBufferRow,
14304 comment_prefix: &str,
14305 comment_prefix_whitespace: &str,
14306 ignore_indent: bool,
14307 ) -> Range<Point> {
14308 let indent_size = if ignore_indent {
14309 0
14310 } else {
14311 snapshot.indent_size_for_line(row).len
14312 };
14313
14314 let start = Point::new(row.0, indent_size);
14315
14316 let mut line_bytes = snapshot
14317 .bytes_in_range(start..snapshot.max_point())
14318 .flatten()
14319 .copied();
14320
14321 // If this line currently begins with the line comment prefix, then record
14322 // the range containing the prefix.
14323 if line_bytes
14324 .by_ref()
14325 .take(comment_prefix.len())
14326 .eq(comment_prefix.bytes())
14327 {
14328 // Include any whitespace that matches the comment prefix.
14329 let matching_whitespace_len = line_bytes
14330 .zip(comment_prefix_whitespace.bytes())
14331 .take_while(|(a, b)| a == b)
14332 .count() as u32;
14333 let end = Point::new(
14334 start.row,
14335 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14336 );
14337 start..end
14338 } else {
14339 start..start
14340 }
14341 }
14342
14343 fn comment_suffix_range(
14344 snapshot: &MultiBufferSnapshot,
14345 row: MultiBufferRow,
14346 comment_suffix: &str,
14347 comment_suffix_has_leading_space: bool,
14348 ) -> Range<Point> {
14349 let end = Point::new(row.0, snapshot.line_len(row));
14350 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14351
14352 let mut line_end_bytes = snapshot
14353 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14354 .flatten()
14355 .copied();
14356
14357 let leading_space_len = if suffix_start_column > 0
14358 && line_end_bytes.next() == Some(b' ')
14359 && comment_suffix_has_leading_space
14360 {
14361 1
14362 } else {
14363 0
14364 };
14365
14366 // If this line currently begins with the line comment prefix, then record
14367 // the range containing the prefix.
14368 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14369 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14370 start..end
14371 } else {
14372 end..end
14373 }
14374 }
14375
14376 // TODO: Handle selections that cross excerpts
14377 for selection in &mut selections {
14378 let start_column = snapshot
14379 .indent_size_for_line(MultiBufferRow(selection.start.row))
14380 .len;
14381 let language = if let Some(language) =
14382 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14383 {
14384 language
14385 } else {
14386 continue;
14387 };
14388
14389 selection_edit_ranges.clear();
14390
14391 // If multiple selections contain a given row, avoid processing that
14392 // row more than once.
14393 let mut start_row = MultiBufferRow(selection.start.row);
14394 if last_toggled_row == Some(start_row) {
14395 start_row = start_row.next_row();
14396 }
14397 let end_row =
14398 if selection.end.row > selection.start.row && selection.end.column == 0 {
14399 MultiBufferRow(selection.end.row - 1)
14400 } else {
14401 MultiBufferRow(selection.end.row)
14402 };
14403 last_toggled_row = Some(end_row);
14404
14405 if start_row > end_row {
14406 continue;
14407 }
14408
14409 // If the language has line comments, toggle those.
14410 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14411
14412 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14413 if ignore_indent {
14414 full_comment_prefixes = full_comment_prefixes
14415 .into_iter()
14416 .map(|s| Arc::from(s.trim_end()))
14417 .collect();
14418 }
14419
14420 if !full_comment_prefixes.is_empty() {
14421 let first_prefix = full_comment_prefixes
14422 .first()
14423 .expect("prefixes is non-empty");
14424 let prefix_trimmed_lengths = full_comment_prefixes
14425 .iter()
14426 .map(|p| p.trim_end_matches(' ').len())
14427 .collect::<SmallVec<[usize; 4]>>();
14428
14429 let mut all_selection_lines_are_comments = true;
14430
14431 for row in start_row.0..=end_row.0 {
14432 let row = MultiBufferRow(row);
14433 if start_row < end_row && snapshot.is_line_blank(row) {
14434 continue;
14435 }
14436
14437 let prefix_range = full_comment_prefixes
14438 .iter()
14439 .zip(prefix_trimmed_lengths.iter().copied())
14440 .map(|(prefix, trimmed_prefix_len)| {
14441 comment_prefix_range(
14442 snapshot.deref(),
14443 row,
14444 &prefix[..trimmed_prefix_len],
14445 &prefix[trimmed_prefix_len..],
14446 ignore_indent,
14447 )
14448 })
14449 .max_by_key(|range| range.end.column - range.start.column)
14450 .expect("prefixes is non-empty");
14451
14452 if prefix_range.is_empty() {
14453 all_selection_lines_are_comments = false;
14454 }
14455
14456 selection_edit_ranges.push(prefix_range);
14457 }
14458
14459 if all_selection_lines_are_comments {
14460 edits.extend(
14461 selection_edit_ranges
14462 .iter()
14463 .cloned()
14464 .map(|range| (range, empty_str.clone())),
14465 );
14466 } else {
14467 let min_column = selection_edit_ranges
14468 .iter()
14469 .map(|range| range.start.column)
14470 .min()
14471 .unwrap_or(0);
14472 edits.extend(selection_edit_ranges.iter().map(|range| {
14473 let position = Point::new(range.start.row, min_column);
14474 (position..position, first_prefix.clone())
14475 }));
14476 }
14477 } else if let Some(BlockCommentConfig {
14478 start: full_comment_prefix,
14479 end: comment_suffix,
14480 ..
14481 }) = language.block_comment()
14482 {
14483 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14484 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14485 let prefix_range = comment_prefix_range(
14486 snapshot.deref(),
14487 start_row,
14488 comment_prefix,
14489 comment_prefix_whitespace,
14490 ignore_indent,
14491 );
14492 let suffix_range = comment_suffix_range(
14493 snapshot.deref(),
14494 end_row,
14495 comment_suffix.trim_start_matches(' '),
14496 comment_suffix.starts_with(' '),
14497 );
14498
14499 if prefix_range.is_empty() || suffix_range.is_empty() {
14500 edits.push((
14501 prefix_range.start..prefix_range.start,
14502 full_comment_prefix.clone(),
14503 ));
14504 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14505 suffixes_inserted.push((end_row, comment_suffix.len()));
14506 } else {
14507 edits.push((prefix_range, empty_str.clone()));
14508 edits.push((suffix_range, empty_str.clone()));
14509 }
14510 } else {
14511 continue;
14512 }
14513 }
14514
14515 drop(snapshot);
14516 this.buffer.update(cx, |buffer, cx| {
14517 buffer.edit(edits, None, cx);
14518 });
14519
14520 // Adjust selections so that they end before any comment suffixes that
14521 // were inserted.
14522 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14523 let mut selections = this.selections.all::<Point>(cx);
14524 let snapshot = this.buffer.read(cx).read(cx);
14525 for selection in &mut selections {
14526 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14527 match row.cmp(&MultiBufferRow(selection.end.row)) {
14528 Ordering::Less => {
14529 suffixes_inserted.next();
14530 continue;
14531 }
14532 Ordering::Greater => break,
14533 Ordering::Equal => {
14534 if selection.end.column == snapshot.line_len(row) {
14535 if selection.is_empty() {
14536 selection.start.column -= suffix_len as u32;
14537 }
14538 selection.end.column -= suffix_len as u32;
14539 }
14540 break;
14541 }
14542 }
14543 }
14544 }
14545
14546 drop(snapshot);
14547 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14548
14549 let selections = this.selections.all::<Point>(cx);
14550 let selections_on_single_row = selections.windows(2).all(|selections| {
14551 selections[0].start.row == selections[1].start.row
14552 && selections[0].end.row == selections[1].end.row
14553 && selections[0].start.row == selections[0].end.row
14554 });
14555 let selections_selecting = selections
14556 .iter()
14557 .any(|selection| selection.start != selection.end);
14558 let advance_downwards = action.advance_downwards
14559 && selections_on_single_row
14560 && !selections_selecting
14561 && !matches!(this.mode, EditorMode::SingleLine { .. });
14562
14563 if advance_downwards {
14564 let snapshot = this.buffer.read(cx).snapshot(cx);
14565
14566 this.change_selections(Default::default(), window, cx, |s| {
14567 s.move_cursors_with(|display_snapshot, display_point, _| {
14568 let mut point = display_point.to_point(display_snapshot);
14569 point.row += 1;
14570 point = snapshot.clip_point(point, Bias::Left);
14571 let display_point = point.to_display_point(display_snapshot);
14572 let goal = SelectionGoal::HorizontalPosition(
14573 display_snapshot
14574 .x_for_display_point(display_point, text_layout_details)
14575 .into(),
14576 );
14577 (display_point, goal)
14578 })
14579 });
14580 }
14581 });
14582 }
14583
14584 pub fn select_enclosing_symbol(
14585 &mut self,
14586 _: &SelectEnclosingSymbol,
14587 window: &mut Window,
14588 cx: &mut Context<Self>,
14589 ) {
14590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14591
14592 let buffer = self.buffer.read(cx).snapshot(cx);
14593 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14594
14595 fn update_selection(
14596 selection: &Selection<usize>,
14597 buffer_snap: &MultiBufferSnapshot,
14598 ) -> Option<Selection<usize>> {
14599 let cursor = selection.head();
14600 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14601 for symbol in symbols.iter().rev() {
14602 let start = symbol.range.start.to_offset(buffer_snap);
14603 let end = symbol.range.end.to_offset(buffer_snap);
14604 let new_range = start..end;
14605 if start < selection.start || end > selection.end {
14606 return Some(Selection {
14607 id: selection.id,
14608 start: new_range.start,
14609 end: new_range.end,
14610 goal: SelectionGoal::None,
14611 reversed: selection.reversed,
14612 });
14613 }
14614 }
14615 None
14616 }
14617
14618 let mut selected_larger_symbol = false;
14619 let new_selections = old_selections
14620 .iter()
14621 .map(|selection| match update_selection(selection, &buffer) {
14622 Some(new_selection) => {
14623 if new_selection.range() != selection.range() {
14624 selected_larger_symbol = true;
14625 }
14626 new_selection
14627 }
14628 None => selection.clone(),
14629 })
14630 .collect::<Vec<_>>();
14631
14632 if selected_larger_symbol {
14633 self.change_selections(Default::default(), window, cx, |s| {
14634 s.select(new_selections);
14635 });
14636 }
14637 }
14638
14639 pub fn select_larger_syntax_node(
14640 &mut self,
14641 _: &SelectLargerSyntaxNode,
14642 window: &mut Window,
14643 cx: &mut Context<Self>,
14644 ) {
14645 let Some(visible_row_count) = self.visible_row_count() else {
14646 return;
14647 };
14648 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14649 if old_selections.is_empty() {
14650 return;
14651 }
14652
14653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14654
14655 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14656 let buffer = self.buffer.read(cx).snapshot(cx);
14657
14658 let mut selected_larger_node = false;
14659 let mut new_selections = old_selections
14660 .iter()
14661 .map(|selection| {
14662 let old_range = selection.start..selection.end;
14663
14664 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14665 // manually select word at selection
14666 if ["string_content", "inline"].contains(&node.kind()) {
14667 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14668 // ignore if word is already selected
14669 if !word_range.is_empty() && old_range != word_range {
14670 let (last_word_range, _) =
14671 buffer.surrounding_word(old_range.end, false);
14672 // only select word if start and end point belongs to same word
14673 if word_range == last_word_range {
14674 selected_larger_node = true;
14675 return Selection {
14676 id: selection.id,
14677 start: word_range.start,
14678 end: word_range.end,
14679 goal: SelectionGoal::None,
14680 reversed: selection.reversed,
14681 };
14682 }
14683 }
14684 }
14685 }
14686
14687 let mut new_range = old_range.clone();
14688 while let Some((_node, containing_range)) =
14689 buffer.syntax_ancestor(new_range.clone())
14690 {
14691 new_range = match containing_range {
14692 MultiOrSingleBufferOffsetRange::Single(_) => break,
14693 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14694 };
14695 if !display_map.intersects_fold(new_range.start)
14696 && !display_map.intersects_fold(new_range.end)
14697 {
14698 break;
14699 }
14700 }
14701
14702 selected_larger_node |= new_range != old_range;
14703 Selection {
14704 id: selection.id,
14705 start: new_range.start,
14706 end: new_range.end,
14707 goal: SelectionGoal::None,
14708 reversed: selection.reversed,
14709 }
14710 })
14711 .collect::<Vec<_>>();
14712
14713 if !selected_larger_node {
14714 return; // don't put this call in the history
14715 }
14716
14717 // scroll based on transformation done to the last selection created by the user
14718 let (last_old, last_new) = old_selections
14719 .last()
14720 .zip(new_selections.last().cloned())
14721 .expect("old_selections isn't empty");
14722
14723 // revert selection
14724 let is_selection_reversed = {
14725 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14726 new_selections.last_mut().expect("checked above").reversed =
14727 should_newest_selection_be_reversed;
14728 should_newest_selection_be_reversed
14729 };
14730
14731 if selected_larger_node {
14732 self.select_syntax_node_history.disable_clearing = true;
14733 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14734 s.select(new_selections.clone());
14735 });
14736 self.select_syntax_node_history.disable_clearing = false;
14737 }
14738
14739 let start_row = last_new.start.to_display_point(&display_map).row().0;
14740 let end_row = last_new.end.to_display_point(&display_map).row().0;
14741 let selection_height = end_row - start_row + 1;
14742 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14743
14744 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14745 let scroll_behavior = if fits_on_the_screen {
14746 self.request_autoscroll(Autoscroll::fit(), cx);
14747 SelectSyntaxNodeScrollBehavior::FitSelection
14748 } else if is_selection_reversed {
14749 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14750 SelectSyntaxNodeScrollBehavior::CursorTop
14751 } else {
14752 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14753 SelectSyntaxNodeScrollBehavior::CursorBottom
14754 };
14755
14756 self.select_syntax_node_history.push((
14757 old_selections,
14758 scroll_behavior,
14759 is_selection_reversed,
14760 ));
14761 }
14762
14763 pub fn select_smaller_syntax_node(
14764 &mut self,
14765 _: &SelectSmallerSyntaxNode,
14766 window: &mut Window,
14767 cx: &mut Context<Self>,
14768 ) {
14769 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14770
14771 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14772 self.select_syntax_node_history.pop()
14773 {
14774 if let Some(selection) = selections.last_mut() {
14775 selection.reversed = is_selection_reversed;
14776 }
14777
14778 self.select_syntax_node_history.disable_clearing = true;
14779 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14780 s.select(selections.to_vec());
14781 });
14782 self.select_syntax_node_history.disable_clearing = false;
14783
14784 match scroll_behavior {
14785 SelectSyntaxNodeScrollBehavior::CursorTop => {
14786 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14787 }
14788 SelectSyntaxNodeScrollBehavior::FitSelection => {
14789 self.request_autoscroll(Autoscroll::fit(), cx);
14790 }
14791 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14792 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14793 }
14794 }
14795 }
14796 }
14797
14798 pub fn unwrap_syntax_node(
14799 &mut self,
14800 _: &UnwrapSyntaxNode,
14801 window: &mut Window,
14802 cx: &mut Context<Self>,
14803 ) {
14804 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14805
14806 let buffer = self.buffer.read(cx).snapshot(cx);
14807 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14808
14809 let edits = old_selections
14810 .iter()
14811 // only consider the first selection for now
14812 .take(1)
14813 .map(|selection| {
14814 // Only requires two branches once if-let-chains stabilize (#53667)
14815 let selection_range = if !selection.is_empty() {
14816 selection.range()
14817 } else if let Some((_, ancestor_range)) =
14818 buffer.syntax_ancestor(selection.start..selection.end)
14819 {
14820 match ancestor_range {
14821 MultiOrSingleBufferOffsetRange::Single(range) => range,
14822 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14823 }
14824 } else {
14825 selection.range()
14826 };
14827
14828 let mut new_range = selection_range.clone();
14829 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(new_range.clone()) {
14830 new_range = match ancestor_range {
14831 MultiOrSingleBufferOffsetRange::Single(range) => range,
14832 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14833 };
14834 if new_range.start < selection_range.start
14835 || new_range.end > selection_range.end
14836 {
14837 break;
14838 }
14839 }
14840
14841 (selection, selection_range, new_range)
14842 })
14843 .collect::<Vec<_>>();
14844
14845 self.transact(window, cx, |editor, window, cx| {
14846 for (_, child, parent) in &edits {
14847 let text = buffer.text_for_range(child.clone()).collect::<String>();
14848 editor.replace_text_in_range(Some(parent.clone()), &text, window, cx);
14849 }
14850
14851 editor.change_selections(
14852 SelectionEffects::scroll(Autoscroll::fit()),
14853 window,
14854 cx,
14855 |s| {
14856 s.select(
14857 edits
14858 .iter()
14859 .map(|(s, old, new)| Selection {
14860 id: s.id,
14861 start: new.start,
14862 end: new.start + old.len(),
14863 goal: SelectionGoal::None,
14864 reversed: s.reversed,
14865 })
14866 .collect(),
14867 );
14868 },
14869 );
14870 });
14871 }
14872
14873 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14874 if !EditorSettings::get_global(cx).gutter.runnables {
14875 self.clear_tasks();
14876 return Task::ready(());
14877 }
14878 let project = self.project.as_ref().map(Entity::downgrade);
14879 let task_sources = self.lsp_task_sources(cx);
14880 let multi_buffer = self.buffer.downgrade();
14881 cx.spawn_in(window, async move |editor, cx| {
14882 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14883 let Some(project) = project.and_then(|p| p.upgrade()) else {
14884 return;
14885 };
14886 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14887 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14888 }) else {
14889 return;
14890 };
14891
14892 let hide_runnables = project
14893 .update(cx, |project, cx| {
14894 // Do not display any test indicators in non-dev server remote projects.
14895 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14896 })
14897 .unwrap_or(true);
14898 if hide_runnables {
14899 return;
14900 }
14901 let new_rows =
14902 cx.background_spawn({
14903 let snapshot = display_snapshot.clone();
14904 async move {
14905 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14906 }
14907 })
14908 .await;
14909 let Ok(lsp_tasks) =
14910 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14911 else {
14912 return;
14913 };
14914 let lsp_tasks = lsp_tasks.await;
14915
14916 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14917 lsp_tasks
14918 .into_iter()
14919 .flat_map(|(kind, tasks)| {
14920 tasks.into_iter().filter_map(move |(location, task)| {
14921 Some((kind.clone(), location?, task))
14922 })
14923 })
14924 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14925 let buffer = location.target.buffer;
14926 let buffer_snapshot = buffer.read(cx).snapshot();
14927 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14928 |(excerpt_id, snapshot, _)| {
14929 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14930 display_snapshot
14931 .buffer_snapshot
14932 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14933 } else {
14934 None
14935 }
14936 },
14937 );
14938 if let Some(offset) = offset {
14939 let task_buffer_range =
14940 location.target.range.to_point(&buffer_snapshot);
14941 let context_buffer_range =
14942 task_buffer_range.to_offset(&buffer_snapshot);
14943 let context_range = BufferOffset(context_buffer_range.start)
14944 ..BufferOffset(context_buffer_range.end);
14945
14946 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14947 .or_insert_with(|| RunnableTasks {
14948 templates: Vec::new(),
14949 offset,
14950 column: task_buffer_range.start.column,
14951 extra_variables: HashMap::default(),
14952 context_range,
14953 })
14954 .templates
14955 .push((kind, task.original_task().clone()));
14956 }
14957
14958 acc
14959 })
14960 }) else {
14961 return;
14962 };
14963
14964 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14965 buffer.language_settings(cx).tasks.prefer_lsp
14966 }) else {
14967 return;
14968 };
14969
14970 let rows = Self::runnable_rows(
14971 project,
14972 display_snapshot,
14973 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14974 new_rows,
14975 cx.clone(),
14976 )
14977 .await;
14978 editor
14979 .update(cx, |editor, _| {
14980 editor.clear_tasks();
14981 for (key, mut value) in rows {
14982 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14983 value.templates.extend(lsp_tasks.templates);
14984 }
14985
14986 editor.insert_tasks(key, value);
14987 }
14988 for (key, value) in lsp_tasks_by_rows {
14989 editor.insert_tasks(key, value);
14990 }
14991 })
14992 .ok();
14993 })
14994 }
14995 fn fetch_runnable_ranges(
14996 snapshot: &DisplaySnapshot,
14997 range: Range<Anchor>,
14998 ) -> Vec<language::RunnableRange> {
14999 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15000 }
15001
15002 fn runnable_rows(
15003 project: Entity<Project>,
15004 snapshot: DisplaySnapshot,
15005 prefer_lsp: bool,
15006 runnable_ranges: Vec<RunnableRange>,
15007 cx: AsyncWindowContext,
15008 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15009 cx.spawn(async move |cx| {
15010 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15011 for mut runnable in runnable_ranges {
15012 let Some(tasks) = cx
15013 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15014 .ok()
15015 else {
15016 continue;
15017 };
15018 let mut tasks = tasks.await;
15019
15020 if prefer_lsp {
15021 tasks.retain(|(task_kind, _)| {
15022 !matches!(task_kind, TaskSourceKind::Language { .. })
15023 });
15024 }
15025 if tasks.is_empty() {
15026 continue;
15027 }
15028
15029 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15030 let Some(row) = snapshot
15031 .buffer_snapshot
15032 .buffer_line_for_row(MultiBufferRow(point.row))
15033 .map(|(_, range)| range.start.row)
15034 else {
15035 continue;
15036 };
15037
15038 let context_range =
15039 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15040 runnable_rows.push((
15041 (runnable.buffer_id, row),
15042 RunnableTasks {
15043 templates: tasks,
15044 offset: snapshot
15045 .buffer_snapshot
15046 .anchor_before(runnable.run_range.start),
15047 context_range,
15048 column: point.column,
15049 extra_variables: runnable.extra_captures,
15050 },
15051 ));
15052 }
15053 runnable_rows
15054 })
15055 }
15056
15057 fn templates_with_tags(
15058 project: &Entity<Project>,
15059 runnable: &mut Runnable,
15060 cx: &mut App,
15061 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15062 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15063 let (worktree_id, file) = project
15064 .buffer_for_id(runnable.buffer, cx)
15065 .and_then(|buffer| buffer.read(cx).file())
15066 .map(|file| (file.worktree_id(cx), file.clone()))
15067 .unzip();
15068
15069 (
15070 project.task_store().read(cx).task_inventory().cloned(),
15071 worktree_id,
15072 file,
15073 )
15074 });
15075
15076 let tags = mem::take(&mut runnable.tags);
15077 let language = runnable.language.clone();
15078 cx.spawn(async move |cx| {
15079 let mut templates_with_tags = Vec::new();
15080 if let Some(inventory) = inventory {
15081 for RunnableTag(tag) in tags {
15082 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15083 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15084 }) else {
15085 return templates_with_tags;
15086 };
15087 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15088 move |(_, template)| {
15089 template.tags.iter().any(|source_tag| source_tag == &tag)
15090 },
15091 ));
15092 }
15093 }
15094 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15095
15096 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15097 // Strongest source wins; if we have worktree tag binding, prefer that to
15098 // global and language bindings;
15099 // if we have a global binding, prefer that to language binding.
15100 let first_mismatch = templates_with_tags
15101 .iter()
15102 .position(|(tag_source, _)| tag_source != leading_tag_source);
15103 if let Some(index) = first_mismatch {
15104 templates_with_tags.truncate(index);
15105 }
15106 }
15107
15108 templates_with_tags
15109 })
15110 }
15111
15112 pub fn move_to_enclosing_bracket(
15113 &mut self,
15114 _: &MoveToEnclosingBracket,
15115 window: &mut Window,
15116 cx: &mut Context<Self>,
15117 ) {
15118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15119 self.change_selections(Default::default(), window, cx, |s| {
15120 s.move_offsets_with(|snapshot, selection| {
15121 let Some(enclosing_bracket_ranges) =
15122 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15123 else {
15124 return;
15125 };
15126
15127 let mut best_length = usize::MAX;
15128 let mut best_inside = false;
15129 let mut best_in_bracket_range = false;
15130 let mut best_destination = None;
15131 for (open, close) in enclosing_bracket_ranges {
15132 let close = close.to_inclusive();
15133 let length = close.end() - open.start;
15134 let inside = selection.start >= open.end && selection.end <= *close.start();
15135 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15136 || close.contains(&selection.head());
15137
15138 // If best is next to a bracket and current isn't, skip
15139 if !in_bracket_range && best_in_bracket_range {
15140 continue;
15141 }
15142
15143 // Prefer smaller lengths unless best is inside and current isn't
15144 if length > best_length && (best_inside || !inside) {
15145 continue;
15146 }
15147
15148 best_length = length;
15149 best_inside = inside;
15150 best_in_bracket_range = in_bracket_range;
15151 best_destination = Some(
15152 if close.contains(&selection.start) && close.contains(&selection.end) {
15153 if inside { open.end } else { open.start }
15154 } else if inside {
15155 *close.start()
15156 } else {
15157 *close.end()
15158 },
15159 );
15160 }
15161
15162 if let Some(destination) = best_destination {
15163 selection.collapse_to(destination, SelectionGoal::None);
15164 }
15165 })
15166 });
15167 }
15168
15169 pub fn undo_selection(
15170 &mut self,
15171 _: &UndoSelection,
15172 window: &mut Window,
15173 cx: &mut Context<Self>,
15174 ) {
15175 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15176 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15177 self.selection_history.mode = SelectionHistoryMode::Undoing;
15178 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15179 this.end_selection(window, cx);
15180 this.change_selections(
15181 SelectionEffects::scroll(Autoscroll::newest()),
15182 window,
15183 cx,
15184 |s| s.select_anchors(entry.selections.to_vec()),
15185 );
15186 });
15187 self.selection_history.mode = SelectionHistoryMode::Normal;
15188
15189 self.select_next_state = entry.select_next_state;
15190 self.select_prev_state = entry.select_prev_state;
15191 self.add_selections_state = entry.add_selections_state;
15192 }
15193 }
15194
15195 pub fn redo_selection(
15196 &mut self,
15197 _: &RedoSelection,
15198 window: &mut Window,
15199 cx: &mut Context<Self>,
15200 ) {
15201 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15202 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15203 self.selection_history.mode = SelectionHistoryMode::Redoing;
15204 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15205 this.end_selection(window, cx);
15206 this.change_selections(
15207 SelectionEffects::scroll(Autoscroll::newest()),
15208 window,
15209 cx,
15210 |s| s.select_anchors(entry.selections.to_vec()),
15211 );
15212 });
15213 self.selection_history.mode = SelectionHistoryMode::Normal;
15214
15215 self.select_next_state = entry.select_next_state;
15216 self.select_prev_state = entry.select_prev_state;
15217 self.add_selections_state = entry.add_selections_state;
15218 }
15219 }
15220
15221 pub fn expand_excerpts(
15222 &mut self,
15223 action: &ExpandExcerpts,
15224 _: &mut Window,
15225 cx: &mut Context<Self>,
15226 ) {
15227 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15228 }
15229
15230 pub fn expand_excerpts_down(
15231 &mut self,
15232 action: &ExpandExcerptsDown,
15233 _: &mut Window,
15234 cx: &mut Context<Self>,
15235 ) {
15236 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15237 }
15238
15239 pub fn expand_excerpts_up(
15240 &mut self,
15241 action: &ExpandExcerptsUp,
15242 _: &mut Window,
15243 cx: &mut Context<Self>,
15244 ) {
15245 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15246 }
15247
15248 pub fn expand_excerpts_for_direction(
15249 &mut self,
15250 lines: u32,
15251 direction: ExpandExcerptDirection,
15252
15253 cx: &mut Context<Self>,
15254 ) {
15255 let selections = self.selections.disjoint_anchors();
15256
15257 let lines = if lines == 0 {
15258 EditorSettings::get_global(cx).expand_excerpt_lines
15259 } else {
15260 lines
15261 };
15262
15263 self.buffer.update(cx, |buffer, cx| {
15264 let snapshot = buffer.snapshot(cx);
15265 let mut excerpt_ids = selections
15266 .iter()
15267 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15268 .collect::<Vec<_>>();
15269 excerpt_ids.sort();
15270 excerpt_ids.dedup();
15271 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15272 })
15273 }
15274
15275 pub fn expand_excerpt(
15276 &mut self,
15277 excerpt: ExcerptId,
15278 direction: ExpandExcerptDirection,
15279 window: &mut Window,
15280 cx: &mut Context<Self>,
15281 ) {
15282 let current_scroll_position = self.scroll_position(cx);
15283 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15284 let mut should_scroll_up = false;
15285
15286 if direction == ExpandExcerptDirection::Down {
15287 let multi_buffer = self.buffer.read(cx);
15288 let snapshot = multi_buffer.snapshot(cx);
15289 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15290 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15291 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15292 let buffer_snapshot = buffer.read(cx).snapshot();
15293 let excerpt_end_row =
15294 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15295 let last_row = buffer_snapshot.max_point().row;
15296 let lines_below = last_row.saturating_sub(excerpt_end_row);
15297 should_scroll_up = lines_below >= lines_to_expand;
15298 }
15299 }
15300 }
15301 }
15302
15303 self.buffer.update(cx, |buffer, cx| {
15304 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15305 });
15306
15307 if should_scroll_up {
15308 let new_scroll_position =
15309 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15310 self.set_scroll_position(new_scroll_position, window, cx);
15311 }
15312 }
15313
15314 pub fn go_to_singleton_buffer_point(
15315 &mut self,
15316 point: Point,
15317 window: &mut Window,
15318 cx: &mut Context<Self>,
15319 ) {
15320 self.go_to_singleton_buffer_range(point..point, window, cx);
15321 }
15322
15323 pub fn go_to_singleton_buffer_range(
15324 &mut self,
15325 range: Range<Point>,
15326 window: &mut Window,
15327 cx: &mut Context<Self>,
15328 ) {
15329 let multibuffer = self.buffer().read(cx);
15330 let Some(buffer) = multibuffer.as_singleton() else {
15331 return;
15332 };
15333 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15334 return;
15335 };
15336 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15337 return;
15338 };
15339 self.change_selections(
15340 SelectionEffects::default().nav_history(true),
15341 window,
15342 cx,
15343 |s| s.select_anchor_ranges([start..end]),
15344 );
15345 }
15346
15347 pub fn go_to_diagnostic(
15348 &mut self,
15349 action: &GoToDiagnostic,
15350 window: &mut Window,
15351 cx: &mut Context<Self>,
15352 ) {
15353 if !self.diagnostics_enabled() {
15354 return;
15355 }
15356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15357 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15358 }
15359
15360 pub fn go_to_prev_diagnostic(
15361 &mut self,
15362 action: &GoToPreviousDiagnostic,
15363 window: &mut Window,
15364 cx: &mut Context<Self>,
15365 ) {
15366 if !self.diagnostics_enabled() {
15367 return;
15368 }
15369 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15370 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15371 }
15372
15373 pub fn go_to_diagnostic_impl(
15374 &mut self,
15375 direction: Direction,
15376 severity: GoToDiagnosticSeverityFilter,
15377 window: &mut Window,
15378 cx: &mut Context<Self>,
15379 ) {
15380 let buffer = self.buffer.read(cx).snapshot(cx);
15381 let selection = self.selections.newest::<usize>(cx);
15382
15383 let mut active_group_id = None;
15384 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15385 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15386 active_group_id = Some(active_group.group_id);
15387 }
15388 }
15389
15390 fn filtered(
15391 snapshot: EditorSnapshot,
15392 severity: GoToDiagnosticSeverityFilter,
15393 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15394 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15395 diagnostics
15396 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15397 .filter(|entry| entry.range.start != entry.range.end)
15398 .filter(|entry| !entry.diagnostic.is_unnecessary)
15399 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15400 }
15401
15402 let snapshot = self.snapshot(window, cx);
15403 let before = filtered(
15404 snapshot.clone(),
15405 severity,
15406 buffer
15407 .diagnostics_in_range(0..selection.start)
15408 .filter(|entry| entry.range.start <= selection.start),
15409 );
15410 let after = filtered(
15411 snapshot,
15412 severity,
15413 buffer
15414 .diagnostics_in_range(selection.start..buffer.len())
15415 .filter(|entry| entry.range.start >= selection.start),
15416 );
15417
15418 let mut found: Option<DiagnosticEntry<usize>> = None;
15419 if direction == Direction::Prev {
15420 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15421 {
15422 for diagnostic in prev_diagnostics.into_iter().rev() {
15423 if diagnostic.range.start != selection.start
15424 || active_group_id
15425 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15426 {
15427 found = Some(diagnostic);
15428 break 'outer;
15429 }
15430 }
15431 }
15432 } else {
15433 for diagnostic in after.chain(before) {
15434 if diagnostic.range.start != selection.start
15435 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15436 {
15437 found = Some(diagnostic);
15438 break;
15439 }
15440 }
15441 }
15442 let Some(next_diagnostic) = found else {
15443 return;
15444 };
15445
15446 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15447 return;
15448 };
15449 self.change_selections(Default::default(), window, cx, |s| {
15450 s.select_ranges(vec![
15451 next_diagnostic.range.start..next_diagnostic.range.start,
15452 ])
15453 });
15454 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15455 self.refresh_edit_prediction(false, true, window, cx);
15456 }
15457
15458 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15459 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15460 let snapshot = self.snapshot(window, cx);
15461 let selection = self.selections.newest::<Point>(cx);
15462 self.go_to_hunk_before_or_after_position(
15463 &snapshot,
15464 selection.head(),
15465 Direction::Next,
15466 window,
15467 cx,
15468 );
15469 }
15470
15471 pub fn go_to_hunk_before_or_after_position(
15472 &mut self,
15473 snapshot: &EditorSnapshot,
15474 position: Point,
15475 direction: Direction,
15476 window: &mut Window,
15477 cx: &mut Context<Editor>,
15478 ) {
15479 let row = if direction == Direction::Next {
15480 self.hunk_after_position(snapshot, position)
15481 .map(|hunk| hunk.row_range.start)
15482 } else {
15483 self.hunk_before_position(snapshot, position)
15484 };
15485
15486 if let Some(row) = row {
15487 let destination = Point::new(row.0, 0);
15488 let autoscroll = Autoscroll::center();
15489
15490 self.unfold_ranges(&[destination..destination], false, false, cx);
15491 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15492 s.select_ranges([destination..destination]);
15493 });
15494 }
15495 }
15496
15497 fn hunk_after_position(
15498 &mut self,
15499 snapshot: &EditorSnapshot,
15500 position: Point,
15501 ) -> Option<MultiBufferDiffHunk> {
15502 snapshot
15503 .buffer_snapshot
15504 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15505 .find(|hunk| hunk.row_range.start.0 > position.row)
15506 .or_else(|| {
15507 snapshot
15508 .buffer_snapshot
15509 .diff_hunks_in_range(Point::zero()..position)
15510 .find(|hunk| hunk.row_range.end.0 < position.row)
15511 })
15512 }
15513
15514 fn go_to_prev_hunk(
15515 &mut self,
15516 _: &GoToPreviousHunk,
15517 window: &mut Window,
15518 cx: &mut Context<Self>,
15519 ) {
15520 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15521 let snapshot = self.snapshot(window, cx);
15522 let selection = self.selections.newest::<Point>(cx);
15523 self.go_to_hunk_before_or_after_position(
15524 &snapshot,
15525 selection.head(),
15526 Direction::Prev,
15527 window,
15528 cx,
15529 );
15530 }
15531
15532 fn hunk_before_position(
15533 &mut self,
15534 snapshot: &EditorSnapshot,
15535 position: Point,
15536 ) -> Option<MultiBufferRow> {
15537 snapshot
15538 .buffer_snapshot
15539 .diff_hunk_before(position)
15540 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15541 }
15542
15543 fn go_to_next_change(
15544 &mut self,
15545 _: &GoToNextChange,
15546 window: &mut Window,
15547 cx: &mut Context<Self>,
15548 ) {
15549 if let Some(selections) = self
15550 .change_list
15551 .next_change(1, Direction::Next)
15552 .map(|s| s.to_vec())
15553 {
15554 self.change_selections(Default::default(), window, cx, |s| {
15555 let map = s.display_map();
15556 s.select_display_ranges(selections.iter().map(|a| {
15557 let point = a.to_display_point(&map);
15558 point..point
15559 }))
15560 })
15561 }
15562 }
15563
15564 fn go_to_previous_change(
15565 &mut self,
15566 _: &GoToPreviousChange,
15567 window: &mut Window,
15568 cx: &mut Context<Self>,
15569 ) {
15570 if let Some(selections) = self
15571 .change_list
15572 .next_change(1, Direction::Prev)
15573 .map(|s| s.to_vec())
15574 {
15575 self.change_selections(Default::default(), window, cx, |s| {
15576 let map = s.display_map();
15577 s.select_display_ranges(selections.iter().map(|a| {
15578 let point = a.to_display_point(&map);
15579 point..point
15580 }))
15581 })
15582 }
15583 }
15584
15585 fn go_to_line<T: 'static>(
15586 &mut self,
15587 position: Anchor,
15588 highlight_color: Option<Hsla>,
15589 window: &mut Window,
15590 cx: &mut Context<Self>,
15591 ) {
15592 let snapshot = self.snapshot(window, cx).display_snapshot;
15593 let position = position.to_point(&snapshot.buffer_snapshot);
15594 let start = snapshot
15595 .buffer_snapshot
15596 .clip_point(Point::new(position.row, 0), Bias::Left);
15597 let end = start + Point::new(1, 0);
15598 let start = snapshot.buffer_snapshot.anchor_before(start);
15599 let end = snapshot.buffer_snapshot.anchor_before(end);
15600
15601 self.highlight_rows::<T>(
15602 start..end,
15603 highlight_color
15604 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15605 Default::default(),
15606 cx,
15607 );
15608
15609 if self.buffer.read(cx).is_singleton() {
15610 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15611 }
15612 }
15613
15614 pub fn go_to_definition(
15615 &mut self,
15616 _: &GoToDefinition,
15617 window: &mut Window,
15618 cx: &mut Context<Self>,
15619 ) -> Task<Result<Navigated>> {
15620 let definition =
15621 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15622 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15623 cx.spawn_in(window, async move |editor, cx| {
15624 if definition.await? == Navigated::Yes {
15625 return Ok(Navigated::Yes);
15626 }
15627 match fallback_strategy {
15628 GoToDefinitionFallback::None => Ok(Navigated::No),
15629 GoToDefinitionFallback::FindAllReferences => {
15630 match editor.update_in(cx, |editor, window, cx| {
15631 editor.find_all_references(&FindAllReferences, window, cx)
15632 })? {
15633 Some(references) => references.await,
15634 None => Ok(Navigated::No),
15635 }
15636 }
15637 }
15638 })
15639 }
15640
15641 pub fn go_to_declaration(
15642 &mut self,
15643 _: &GoToDeclaration,
15644 window: &mut Window,
15645 cx: &mut Context<Self>,
15646 ) -> Task<Result<Navigated>> {
15647 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15648 }
15649
15650 pub fn go_to_declaration_split(
15651 &mut self,
15652 _: &GoToDeclaration,
15653 window: &mut Window,
15654 cx: &mut Context<Self>,
15655 ) -> Task<Result<Navigated>> {
15656 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15657 }
15658
15659 pub fn go_to_implementation(
15660 &mut self,
15661 _: &GoToImplementation,
15662 window: &mut Window,
15663 cx: &mut Context<Self>,
15664 ) -> Task<Result<Navigated>> {
15665 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15666 }
15667
15668 pub fn go_to_implementation_split(
15669 &mut self,
15670 _: &GoToImplementationSplit,
15671 window: &mut Window,
15672 cx: &mut Context<Self>,
15673 ) -> Task<Result<Navigated>> {
15674 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15675 }
15676
15677 pub fn go_to_type_definition(
15678 &mut self,
15679 _: &GoToTypeDefinition,
15680 window: &mut Window,
15681 cx: &mut Context<Self>,
15682 ) -> Task<Result<Navigated>> {
15683 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15684 }
15685
15686 pub fn go_to_definition_split(
15687 &mut self,
15688 _: &GoToDefinitionSplit,
15689 window: &mut Window,
15690 cx: &mut Context<Self>,
15691 ) -> Task<Result<Navigated>> {
15692 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15693 }
15694
15695 pub fn go_to_type_definition_split(
15696 &mut self,
15697 _: &GoToTypeDefinitionSplit,
15698 window: &mut Window,
15699 cx: &mut Context<Self>,
15700 ) -> Task<Result<Navigated>> {
15701 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15702 }
15703
15704 fn go_to_definition_of_kind(
15705 &mut self,
15706 kind: GotoDefinitionKind,
15707 split: bool,
15708 window: &mut Window,
15709 cx: &mut Context<Self>,
15710 ) -> Task<Result<Navigated>> {
15711 let Some(provider) = self.semantics_provider.clone() else {
15712 return Task::ready(Ok(Navigated::No));
15713 };
15714 let head = self.selections.newest::<usize>(cx).head();
15715 let buffer = self.buffer.read(cx);
15716 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15717 return Task::ready(Ok(Navigated::No));
15718 };
15719 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15720 return Task::ready(Ok(Navigated::No));
15721 };
15722
15723 cx.spawn_in(window, async move |editor, cx| {
15724 let definitions = definitions.await?;
15725 let navigated = editor
15726 .update_in(cx, |editor, window, cx| {
15727 editor.navigate_to_hover_links(
15728 Some(kind),
15729 definitions
15730 .into_iter()
15731 .filter(|location| {
15732 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15733 })
15734 .map(HoverLink::Text)
15735 .collect::<Vec<_>>(),
15736 split,
15737 window,
15738 cx,
15739 )
15740 })?
15741 .await?;
15742 anyhow::Ok(navigated)
15743 })
15744 }
15745
15746 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15747 let selection = self.selections.newest_anchor();
15748 let head = selection.head();
15749 let tail = selection.tail();
15750
15751 let Some((buffer, start_position)) =
15752 self.buffer.read(cx).text_anchor_for_position(head, cx)
15753 else {
15754 return;
15755 };
15756
15757 let end_position = if head != tail {
15758 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15759 return;
15760 };
15761 Some(pos)
15762 } else {
15763 None
15764 };
15765
15766 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15767 let url = if let Some(end_pos) = end_position {
15768 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15769 } else {
15770 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15771 };
15772
15773 if let Some(url) = url {
15774 editor.update(cx, |_, cx| {
15775 cx.open_url(&url);
15776 })
15777 } else {
15778 Ok(())
15779 }
15780 });
15781
15782 url_finder.detach();
15783 }
15784
15785 pub fn open_selected_filename(
15786 &mut self,
15787 _: &OpenSelectedFilename,
15788 window: &mut Window,
15789 cx: &mut Context<Self>,
15790 ) {
15791 let Some(workspace) = self.workspace() else {
15792 return;
15793 };
15794
15795 let position = self.selections.newest_anchor().head();
15796
15797 let Some((buffer, buffer_position)) =
15798 self.buffer.read(cx).text_anchor_for_position(position, cx)
15799 else {
15800 return;
15801 };
15802
15803 let project = self.project.clone();
15804
15805 cx.spawn_in(window, async move |_, cx| {
15806 let result = find_file(&buffer, project, buffer_position, cx).await;
15807
15808 if let Some((_, path)) = result {
15809 workspace
15810 .update_in(cx, |workspace, window, cx| {
15811 workspace.open_resolved_path(path, window, cx)
15812 })?
15813 .await?;
15814 }
15815 anyhow::Ok(())
15816 })
15817 .detach();
15818 }
15819
15820 pub(crate) fn navigate_to_hover_links(
15821 &mut self,
15822 kind: Option<GotoDefinitionKind>,
15823 definitions: Vec<HoverLink>,
15824 split: bool,
15825 window: &mut Window,
15826 cx: &mut Context<Editor>,
15827 ) -> Task<Result<Navigated>> {
15828 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15829 let mut first_url_or_file = None;
15830 let definitions: Vec<_> = definitions
15831 .into_iter()
15832 .filter_map(|def| match def {
15833 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15834 HoverLink::InlayHint(lsp_location, server_id) => {
15835 let computation =
15836 self.compute_target_location(lsp_location, server_id, window, cx);
15837 Some(cx.background_spawn(computation))
15838 }
15839 HoverLink::Url(url) => {
15840 first_url_or_file = Some(Either::Left(url));
15841 None
15842 }
15843 HoverLink::File(path) => {
15844 first_url_or_file = Some(Either::Right(path));
15845 None
15846 }
15847 })
15848 .collect();
15849
15850 let workspace = self.workspace();
15851
15852 cx.spawn_in(window, async move |editor, acx| {
15853 let mut locations: Vec<Location> = future::join_all(definitions)
15854 .await
15855 .into_iter()
15856 .filter_map(|location| location.transpose())
15857 .collect::<Result<_>>()
15858 .context("location tasks")?;
15859
15860 if locations.len() > 1 {
15861 let Some(workspace) = workspace else {
15862 return Ok(Navigated::No);
15863 };
15864
15865 let tab_kind = match kind {
15866 Some(GotoDefinitionKind::Implementation) => "Implementations",
15867 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15868 Some(GotoDefinitionKind::Declaration) => "Declarations",
15869 Some(GotoDefinitionKind::Type) => "Types",
15870 };
15871 let title = editor
15872 .update_in(acx, |_, _, cx| {
15873 let target = locations
15874 .iter()
15875 .map(|location| {
15876 location
15877 .buffer
15878 .read(cx)
15879 .text_for_range(location.range.clone())
15880 .collect::<String>()
15881 })
15882 .join(", ");
15883 format!("{tab_kind} for {target}")
15884 })
15885 .context("buffer title")?;
15886
15887 let opened = workspace
15888 .update_in(acx, |workspace, window, cx| {
15889 Self::open_locations_in_multibuffer(
15890 workspace,
15891 locations,
15892 title,
15893 split,
15894 MultibufferSelectionMode::First,
15895 window,
15896 cx,
15897 )
15898 })
15899 .is_ok();
15900
15901 anyhow::Ok(Navigated::from_bool(opened))
15902 } else if locations.is_empty() {
15903 // If there is one definition, just open it directly
15904 match first_url_or_file {
15905 Some(Either::Left(url)) => {
15906 acx.update(|_, cx| cx.open_url(&url))?;
15907 Ok(Navigated::Yes)
15908 }
15909 Some(Either::Right(path)) => {
15910 let Some(workspace) = workspace else {
15911 return Ok(Navigated::No);
15912 };
15913
15914 workspace
15915 .update_in(acx, |workspace, window, cx| {
15916 workspace.open_resolved_path(path, window, cx)
15917 })?
15918 .await?;
15919 Ok(Navigated::Yes)
15920 }
15921 None => Ok(Navigated::No),
15922 }
15923 } else {
15924 let Some(workspace) = workspace else {
15925 return Ok(Navigated::No);
15926 };
15927
15928 let target = locations.pop().unwrap();
15929 editor.update_in(acx, |editor, window, cx| {
15930 let pane = workspace.read(cx).active_pane().clone();
15931
15932 let range = target.range.to_point(target.buffer.read(cx));
15933 let range = editor.range_for_match(&range);
15934 let range = collapse_multiline_range(range);
15935
15936 if !split
15937 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15938 {
15939 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15940 } else {
15941 window.defer(cx, move |window, cx| {
15942 let target_editor: Entity<Self> =
15943 workspace.update(cx, |workspace, cx| {
15944 let pane = if split {
15945 workspace.adjacent_pane(window, cx)
15946 } else {
15947 workspace.active_pane().clone()
15948 };
15949
15950 workspace.open_project_item(
15951 pane,
15952 target.buffer.clone(),
15953 true,
15954 true,
15955 window,
15956 cx,
15957 )
15958 });
15959 target_editor.update(cx, |target_editor, cx| {
15960 // When selecting a definition in a different buffer, disable the nav history
15961 // to avoid creating a history entry at the previous cursor location.
15962 pane.update(cx, |pane, _| pane.disable_history());
15963 target_editor.go_to_singleton_buffer_range(range, window, cx);
15964 pane.update(cx, |pane, _| pane.enable_history());
15965 });
15966 });
15967 }
15968 Navigated::Yes
15969 })
15970 }
15971 })
15972 }
15973
15974 fn compute_target_location(
15975 &self,
15976 lsp_location: lsp::Location,
15977 server_id: LanguageServerId,
15978 window: &mut Window,
15979 cx: &mut Context<Self>,
15980 ) -> Task<anyhow::Result<Option<Location>>> {
15981 let Some(project) = self.project.clone() else {
15982 return Task::ready(Ok(None));
15983 };
15984
15985 cx.spawn_in(window, async move |editor, cx| {
15986 let location_task = editor.update(cx, |_, cx| {
15987 project.update(cx, |project, cx| {
15988 let language_server_name = project
15989 .language_server_statuses(cx)
15990 .find(|(id, _)| server_id == *id)
15991 .map(|(_, status)| status.name.clone());
15992 language_server_name.map(|language_server_name| {
15993 project.open_local_buffer_via_lsp(
15994 lsp_location.uri.clone(),
15995 server_id,
15996 language_server_name,
15997 cx,
15998 )
15999 })
16000 })
16001 })?;
16002 let location = match location_task {
16003 Some(task) => Some({
16004 let target_buffer_handle = task.await.context("open local buffer")?;
16005 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16006 let target_start = target_buffer
16007 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16008 let target_end = target_buffer
16009 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16010 target_buffer.anchor_after(target_start)
16011 ..target_buffer.anchor_before(target_end)
16012 })?;
16013 Location {
16014 buffer: target_buffer_handle,
16015 range,
16016 }
16017 }),
16018 None => None,
16019 };
16020 Ok(location)
16021 })
16022 }
16023
16024 pub fn find_all_references(
16025 &mut self,
16026 _: &FindAllReferences,
16027 window: &mut Window,
16028 cx: &mut Context<Self>,
16029 ) -> Option<Task<Result<Navigated>>> {
16030 let selection = self.selections.newest::<usize>(cx);
16031 let multi_buffer = self.buffer.read(cx);
16032 let head = selection.head();
16033
16034 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16035 let head_anchor = multi_buffer_snapshot.anchor_at(
16036 head,
16037 if head < selection.tail() {
16038 Bias::Right
16039 } else {
16040 Bias::Left
16041 },
16042 );
16043
16044 match self
16045 .find_all_references_task_sources
16046 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16047 {
16048 Ok(_) => {
16049 log::info!(
16050 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16051 );
16052 return None;
16053 }
16054 Err(i) => {
16055 self.find_all_references_task_sources.insert(i, head_anchor);
16056 }
16057 }
16058
16059 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16060 let workspace = self.workspace()?;
16061 let project = workspace.read(cx).project().clone();
16062 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16063 Some(cx.spawn_in(window, async move |editor, cx| {
16064 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16065 if let Ok(i) = editor
16066 .find_all_references_task_sources
16067 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16068 {
16069 editor.find_all_references_task_sources.remove(i);
16070 }
16071 });
16072
16073 let locations = references.await?;
16074 if locations.is_empty() {
16075 return anyhow::Ok(Navigated::No);
16076 }
16077
16078 workspace.update_in(cx, |workspace, window, cx| {
16079 let target = locations
16080 .iter()
16081 .map(|location| {
16082 location
16083 .buffer
16084 .read(cx)
16085 .text_for_range(location.range.clone())
16086 .collect::<String>()
16087 })
16088 .join(", ");
16089 let title = format!("References to {target}");
16090 Self::open_locations_in_multibuffer(
16091 workspace,
16092 locations,
16093 title,
16094 false,
16095 MultibufferSelectionMode::First,
16096 window,
16097 cx,
16098 );
16099 Navigated::Yes
16100 })
16101 }))
16102 }
16103
16104 /// Opens a multibuffer with the given project locations in it
16105 pub fn open_locations_in_multibuffer(
16106 workspace: &mut Workspace,
16107 mut locations: Vec<Location>,
16108 title: String,
16109 split: bool,
16110 multibuffer_selection_mode: MultibufferSelectionMode,
16111 window: &mut Window,
16112 cx: &mut Context<Workspace>,
16113 ) {
16114 if locations.is_empty() {
16115 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16116 return;
16117 }
16118
16119 // If there are multiple definitions, open them in a multibuffer
16120 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16121 let mut locations = locations.into_iter().peekable();
16122 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16123 let capability = workspace.project().read(cx).capability();
16124
16125 let excerpt_buffer = cx.new(|cx| {
16126 let mut multibuffer = MultiBuffer::new(capability);
16127 while let Some(location) = locations.next() {
16128 let buffer = location.buffer.read(cx);
16129 let mut ranges_for_buffer = Vec::new();
16130 let range = location.range.to_point(buffer);
16131 ranges_for_buffer.push(range.clone());
16132
16133 while let Some(next_location) = locations.peek() {
16134 if next_location.buffer == location.buffer {
16135 ranges_for_buffer.push(next_location.range.to_point(buffer));
16136 locations.next();
16137 } else {
16138 break;
16139 }
16140 }
16141
16142 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16143 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16144 PathKey::for_buffer(&location.buffer, cx),
16145 location.buffer.clone(),
16146 ranges_for_buffer,
16147 DEFAULT_MULTIBUFFER_CONTEXT,
16148 cx,
16149 );
16150 ranges.extend(new_ranges)
16151 }
16152
16153 multibuffer.with_title(title)
16154 });
16155
16156 let editor = cx.new(|cx| {
16157 Editor::for_multibuffer(
16158 excerpt_buffer,
16159 Some(workspace.project().clone()),
16160 window,
16161 cx,
16162 )
16163 });
16164 editor.update(cx, |editor, cx| {
16165 match multibuffer_selection_mode {
16166 MultibufferSelectionMode::First => {
16167 if let Some(first_range) = ranges.first() {
16168 editor.change_selections(
16169 SelectionEffects::no_scroll(),
16170 window,
16171 cx,
16172 |selections| {
16173 selections.clear_disjoint();
16174 selections
16175 .select_anchor_ranges(std::iter::once(first_range.clone()));
16176 },
16177 );
16178 }
16179 editor.highlight_background::<Self>(
16180 &ranges,
16181 |theme| theme.colors().editor_highlighted_line_background,
16182 cx,
16183 );
16184 }
16185 MultibufferSelectionMode::All => {
16186 editor.change_selections(
16187 SelectionEffects::no_scroll(),
16188 window,
16189 cx,
16190 |selections| {
16191 selections.clear_disjoint();
16192 selections.select_anchor_ranges(ranges);
16193 },
16194 );
16195 }
16196 }
16197 editor.register_buffers_with_language_servers(cx);
16198 });
16199
16200 let item = Box::new(editor);
16201 let item_id = item.item_id();
16202
16203 if split {
16204 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16205 } else {
16206 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16207 let (preview_item_id, preview_item_idx) =
16208 workspace.active_pane().read_with(cx, |pane, _| {
16209 (pane.preview_item_id(), pane.preview_item_idx())
16210 });
16211
16212 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16213
16214 if let Some(preview_item_id) = preview_item_id {
16215 workspace.active_pane().update(cx, |pane, cx| {
16216 pane.remove_item(preview_item_id, false, false, window, cx);
16217 });
16218 }
16219 } else {
16220 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16221 }
16222 }
16223 workspace.active_pane().update(cx, |pane, cx| {
16224 pane.set_preview_item_id(Some(item_id), cx);
16225 });
16226 }
16227
16228 pub fn rename(
16229 &mut self,
16230 _: &Rename,
16231 window: &mut Window,
16232 cx: &mut Context<Self>,
16233 ) -> Option<Task<Result<()>>> {
16234 use language::ToOffset as _;
16235
16236 let provider = self.semantics_provider.clone()?;
16237 let selection = self.selections.newest_anchor().clone();
16238 let (cursor_buffer, cursor_buffer_position) = self
16239 .buffer
16240 .read(cx)
16241 .text_anchor_for_position(selection.head(), cx)?;
16242 let (tail_buffer, cursor_buffer_position_end) = self
16243 .buffer
16244 .read(cx)
16245 .text_anchor_for_position(selection.tail(), cx)?;
16246 if tail_buffer != cursor_buffer {
16247 return None;
16248 }
16249
16250 let snapshot = cursor_buffer.read(cx).snapshot();
16251 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16252 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16253 let prepare_rename = provider
16254 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16255 .unwrap_or_else(|| Task::ready(Ok(None)));
16256 drop(snapshot);
16257
16258 Some(cx.spawn_in(window, async move |this, cx| {
16259 let rename_range = if let Some(range) = prepare_rename.await? {
16260 Some(range)
16261 } else {
16262 this.update(cx, |this, cx| {
16263 let buffer = this.buffer.read(cx).snapshot(cx);
16264 let mut buffer_highlights = this
16265 .document_highlights_for_position(selection.head(), &buffer)
16266 .filter(|highlight| {
16267 highlight.start.excerpt_id == selection.head().excerpt_id
16268 && highlight.end.excerpt_id == selection.head().excerpt_id
16269 });
16270 buffer_highlights
16271 .next()
16272 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16273 })?
16274 };
16275 if let Some(rename_range) = rename_range {
16276 this.update_in(cx, |this, window, cx| {
16277 let snapshot = cursor_buffer.read(cx).snapshot();
16278 let rename_buffer_range = rename_range.to_offset(&snapshot);
16279 let cursor_offset_in_rename_range =
16280 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16281 let cursor_offset_in_rename_range_end =
16282 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16283
16284 this.take_rename(false, window, cx);
16285 let buffer = this.buffer.read(cx).read(cx);
16286 let cursor_offset = selection.head().to_offset(&buffer);
16287 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16288 let rename_end = rename_start + rename_buffer_range.len();
16289 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16290 let mut old_highlight_id = None;
16291 let old_name: Arc<str> = buffer
16292 .chunks(rename_start..rename_end, true)
16293 .map(|chunk| {
16294 if old_highlight_id.is_none() {
16295 old_highlight_id = chunk.syntax_highlight_id;
16296 }
16297 chunk.text
16298 })
16299 .collect::<String>()
16300 .into();
16301
16302 drop(buffer);
16303
16304 // Position the selection in the rename editor so that it matches the current selection.
16305 this.show_local_selections = false;
16306 let rename_editor = cx.new(|cx| {
16307 let mut editor = Editor::single_line(window, cx);
16308 editor.buffer.update(cx, |buffer, cx| {
16309 buffer.edit([(0..0, old_name.clone())], None, cx)
16310 });
16311 let rename_selection_range = match cursor_offset_in_rename_range
16312 .cmp(&cursor_offset_in_rename_range_end)
16313 {
16314 Ordering::Equal => {
16315 editor.select_all(&SelectAll, window, cx);
16316 return editor;
16317 }
16318 Ordering::Less => {
16319 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16320 }
16321 Ordering::Greater => {
16322 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16323 }
16324 };
16325 if rename_selection_range.end > old_name.len() {
16326 editor.select_all(&SelectAll, window, cx);
16327 } else {
16328 editor.change_selections(Default::default(), window, cx, |s| {
16329 s.select_ranges([rename_selection_range]);
16330 });
16331 }
16332 editor
16333 });
16334 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16335 if e == &EditorEvent::Focused {
16336 cx.emit(EditorEvent::FocusedIn)
16337 }
16338 })
16339 .detach();
16340
16341 let write_highlights =
16342 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16343 let read_highlights =
16344 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16345 let ranges = write_highlights
16346 .iter()
16347 .flat_map(|(_, ranges)| ranges.iter())
16348 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16349 .cloned()
16350 .collect();
16351
16352 this.highlight_text::<Rename>(
16353 ranges,
16354 HighlightStyle {
16355 fade_out: Some(0.6),
16356 ..Default::default()
16357 },
16358 cx,
16359 );
16360 let rename_focus_handle = rename_editor.focus_handle(cx);
16361 window.focus(&rename_focus_handle);
16362 let block_id = this.insert_blocks(
16363 [BlockProperties {
16364 style: BlockStyle::Flex,
16365 placement: BlockPlacement::Below(range.start),
16366 height: Some(1),
16367 render: Arc::new({
16368 let rename_editor = rename_editor.clone();
16369 move |cx: &mut BlockContext| {
16370 let mut text_style = cx.editor_style.text.clone();
16371 if let Some(highlight_style) = old_highlight_id
16372 .and_then(|h| h.style(&cx.editor_style.syntax))
16373 {
16374 text_style = text_style.highlight(highlight_style);
16375 }
16376 div()
16377 .block_mouse_except_scroll()
16378 .pl(cx.anchor_x)
16379 .child(EditorElement::new(
16380 &rename_editor,
16381 EditorStyle {
16382 background: cx.theme().system().transparent,
16383 local_player: cx.editor_style.local_player,
16384 text: text_style,
16385 scrollbar_width: cx.editor_style.scrollbar_width,
16386 syntax: cx.editor_style.syntax.clone(),
16387 status: cx.editor_style.status.clone(),
16388 inlay_hints_style: HighlightStyle {
16389 font_weight: Some(FontWeight::BOLD),
16390 ..make_inlay_hints_style(cx.app)
16391 },
16392 edit_prediction_styles: make_suggestion_styles(
16393 cx.app,
16394 ),
16395 ..EditorStyle::default()
16396 },
16397 ))
16398 .into_any_element()
16399 }
16400 }),
16401 priority: 0,
16402 }],
16403 Some(Autoscroll::fit()),
16404 cx,
16405 )[0];
16406 this.pending_rename = Some(RenameState {
16407 range,
16408 old_name,
16409 editor: rename_editor,
16410 block_id,
16411 });
16412 })?;
16413 }
16414
16415 Ok(())
16416 }))
16417 }
16418
16419 pub fn confirm_rename(
16420 &mut self,
16421 _: &ConfirmRename,
16422 window: &mut Window,
16423 cx: &mut Context<Self>,
16424 ) -> Option<Task<Result<()>>> {
16425 let rename = self.take_rename(false, window, cx)?;
16426 let workspace = self.workspace()?.downgrade();
16427 let (buffer, start) = self
16428 .buffer
16429 .read(cx)
16430 .text_anchor_for_position(rename.range.start, cx)?;
16431 let (end_buffer, _) = self
16432 .buffer
16433 .read(cx)
16434 .text_anchor_for_position(rename.range.end, cx)?;
16435 if buffer != end_buffer {
16436 return None;
16437 }
16438
16439 let old_name = rename.old_name;
16440 let new_name = rename.editor.read(cx).text(cx);
16441
16442 let rename = self.semantics_provider.as_ref()?.perform_rename(
16443 &buffer,
16444 start,
16445 new_name.clone(),
16446 cx,
16447 )?;
16448
16449 Some(cx.spawn_in(window, async move |editor, cx| {
16450 let project_transaction = rename.await?;
16451 Self::open_project_transaction(
16452 &editor,
16453 workspace,
16454 project_transaction,
16455 format!("Rename: {} → {}", old_name, new_name),
16456 cx,
16457 )
16458 .await?;
16459
16460 editor.update(cx, |editor, cx| {
16461 editor.refresh_document_highlights(cx);
16462 })?;
16463 Ok(())
16464 }))
16465 }
16466
16467 fn take_rename(
16468 &mut self,
16469 moving_cursor: bool,
16470 window: &mut Window,
16471 cx: &mut Context<Self>,
16472 ) -> Option<RenameState> {
16473 let rename = self.pending_rename.take()?;
16474 if rename.editor.focus_handle(cx).is_focused(window) {
16475 window.focus(&self.focus_handle);
16476 }
16477
16478 self.remove_blocks(
16479 [rename.block_id].into_iter().collect(),
16480 Some(Autoscroll::fit()),
16481 cx,
16482 );
16483 self.clear_highlights::<Rename>(cx);
16484 self.show_local_selections = true;
16485
16486 if moving_cursor {
16487 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16488 editor.selections.newest::<usize>(cx).head()
16489 });
16490
16491 // Update the selection to match the position of the selection inside
16492 // the rename editor.
16493 let snapshot = self.buffer.read(cx).read(cx);
16494 let rename_range = rename.range.to_offset(&snapshot);
16495 let cursor_in_editor = snapshot
16496 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16497 .min(rename_range.end);
16498 drop(snapshot);
16499
16500 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16501 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16502 });
16503 } else {
16504 self.refresh_document_highlights(cx);
16505 }
16506
16507 Some(rename)
16508 }
16509
16510 pub fn pending_rename(&self) -> Option<&RenameState> {
16511 self.pending_rename.as_ref()
16512 }
16513
16514 fn format(
16515 &mut self,
16516 _: &Format,
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 Some(self.perform_format(
16528 project,
16529 FormatTrigger::Manual,
16530 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16531 window,
16532 cx,
16533 ))
16534 }
16535
16536 fn format_selections(
16537 &mut self,
16538 _: &FormatSelections,
16539 window: &mut Window,
16540 cx: &mut Context<Self>,
16541 ) -> Option<Task<Result<()>>> {
16542 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16543
16544 let project = match &self.project {
16545 Some(project) => project.clone(),
16546 None => return None,
16547 };
16548
16549 let ranges = self
16550 .selections
16551 .all_adjusted(cx)
16552 .into_iter()
16553 .map(|selection| selection.range())
16554 .collect_vec();
16555
16556 Some(self.perform_format(
16557 project,
16558 FormatTrigger::Manual,
16559 FormatTarget::Ranges(ranges),
16560 window,
16561 cx,
16562 ))
16563 }
16564
16565 fn perform_format(
16566 &mut self,
16567 project: Entity<Project>,
16568 trigger: FormatTrigger,
16569 target: FormatTarget,
16570 window: &mut Window,
16571 cx: &mut Context<Self>,
16572 ) -> Task<Result<()>> {
16573 let buffer = self.buffer.clone();
16574 let (buffers, target) = match target {
16575 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16576 FormatTarget::Ranges(selection_ranges) => {
16577 let multi_buffer = buffer.read(cx);
16578 let snapshot = multi_buffer.read(cx);
16579 let mut buffers = HashSet::default();
16580 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16581 BTreeMap::new();
16582 for selection_range in selection_ranges {
16583 for (buffer, buffer_range, _) in
16584 snapshot.range_to_buffer_ranges(selection_range)
16585 {
16586 let buffer_id = buffer.remote_id();
16587 let start = buffer.anchor_before(buffer_range.start);
16588 let end = buffer.anchor_after(buffer_range.end);
16589 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16590 buffer_id_to_ranges
16591 .entry(buffer_id)
16592 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16593 .or_insert_with(|| vec![start..end]);
16594 }
16595 }
16596 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16597 }
16598 };
16599
16600 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16601 let selections_prev = transaction_id_prev
16602 .and_then(|transaction_id_prev| {
16603 // default to selections as they were after the last edit, if we have them,
16604 // instead of how they are now.
16605 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16606 // will take you back to where you made the last edit, instead of staying where you scrolled
16607 self.selection_history
16608 .transaction(transaction_id_prev)
16609 .map(|t| t.0.clone())
16610 })
16611 .unwrap_or_else(|| {
16612 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16613 self.selections.disjoint_anchors()
16614 });
16615
16616 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16617 let format = project.update(cx, |project, cx| {
16618 project.format(buffers, target, true, trigger, cx)
16619 });
16620
16621 cx.spawn_in(window, async move |editor, cx| {
16622 let transaction = futures::select_biased! {
16623 transaction = format.log_err().fuse() => transaction,
16624 () = timeout => {
16625 log::warn!("timed out waiting for formatting");
16626 None
16627 }
16628 };
16629
16630 buffer
16631 .update(cx, |buffer, cx| {
16632 if let Some(transaction) = transaction {
16633 if !buffer.is_singleton() {
16634 buffer.push_transaction(&transaction.0, cx);
16635 }
16636 }
16637 cx.notify();
16638 })
16639 .ok();
16640
16641 if let Some(transaction_id_now) =
16642 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16643 {
16644 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16645 if has_new_transaction {
16646 _ = editor.update(cx, |editor, _| {
16647 editor
16648 .selection_history
16649 .insert_transaction(transaction_id_now, selections_prev);
16650 });
16651 }
16652 }
16653
16654 Ok(())
16655 })
16656 }
16657
16658 fn organize_imports(
16659 &mut self,
16660 _: &OrganizeImports,
16661 window: &mut Window,
16662 cx: &mut Context<Self>,
16663 ) -> Option<Task<Result<()>>> {
16664 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16665 let project = match &self.project {
16666 Some(project) => project.clone(),
16667 None => return None,
16668 };
16669 Some(self.perform_code_action_kind(
16670 project,
16671 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16672 window,
16673 cx,
16674 ))
16675 }
16676
16677 fn perform_code_action_kind(
16678 &mut self,
16679 project: Entity<Project>,
16680 kind: CodeActionKind,
16681 window: &mut Window,
16682 cx: &mut Context<Self>,
16683 ) -> Task<Result<()>> {
16684 let buffer = self.buffer.clone();
16685 let buffers = buffer.read(cx).all_buffers();
16686 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16687 let apply_action = project.update(cx, |project, cx| {
16688 project.apply_code_action_kind(buffers, kind, true, cx)
16689 });
16690 cx.spawn_in(window, async move |_, cx| {
16691 let transaction = futures::select_biased! {
16692 () = timeout => {
16693 log::warn!("timed out waiting for executing code action");
16694 None
16695 }
16696 transaction = apply_action.log_err().fuse() => transaction,
16697 };
16698 buffer
16699 .update(cx, |buffer, cx| {
16700 // check if we need this
16701 if let Some(transaction) = transaction {
16702 if !buffer.is_singleton() {
16703 buffer.push_transaction(&transaction.0, cx);
16704 }
16705 }
16706 cx.notify();
16707 })
16708 .ok();
16709 Ok(())
16710 })
16711 }
16712
16713 pub fn restart_language_server(
16714 &mut self,
16715 _: &RestartLanguageServer,
16716 _: &mut Window,
16717 cx: &mut Context<Self>,
16718 ) {
16719 if let Some(project) = self.project.clone() {
16720 self.buffer.update(cx, |multi_buffer, cx| {
16721 project.update(cx, |project, cx| {
16722 project.restart_language_servers_for_buffers(
16723 multi_buffer.all_buffers().into_iter().collect(),
16724 HashSet::default(),
16725 cx,
16726 );
16727 });
16728 })
16729 }
16730 }
16731
16732 pub fn stop_language_server(
16733 &mut self,
16734 _: &StopLanguageServer,
16735 _: &mut Window,
16736 cx: &mut Context<Self>,
16737 ) {
16738 if let Some(project) = self.project.clone() {
16739 self.buffer.update(cx, |multi_buffer, cx| {
16740 project.update(cx, |project, cx| {
16741 project.stop_language_servers_for_buffers(
16742 multi_buffer.all_buffers().into_iter().collect(),
16743 HashSet::default(),
16744 cx,
16745 );
16746 cx.emit(project::Event::RefreshInlayHints);
16747 });
16748 });
16749 }
16750 }
16751
16752 fn cancel_language_server_work(
16753 workspace: &mut Workspace,
16754 _: &actions::CancelLanguageServerWork,
16755 _: &mut Window,
16756 cx: &mut Context<Workspace>,
16757 ) {
16758 let project = workspace.project();
16759 let buffers = workspace
16760 .active_item(cx)
16761 .and_then(|item| item.act_as::<Editor>(cx))
16762 .map_or(HashSet::default(), |editor| {
16763 editor.read(cx).buffer.read(cx).all_buffers()
16764 });
16765 project.update(cx, |project, cx| {
16766 project.cancel_language_server_work_for_buffers(buffers, cx);
16767 });
16768 }
16769
16770 fn show_character_palette(
16771 &mut self,
16772 _: &ShowCharacterPalette,
16773 window: &mut Window,
16774 _: &mut Context<Self>,
16775 ) {
16776 window.show_character_palette();
16777 }
16778
16779 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16780 if !self.diagnostics_enabled() {
16781 return;
16782 }
16783
16784 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16785 let buffer = self.buffer.read(cx).snapshot(cx);
16786 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16787 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16788 let is_valid = buffer
16789 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16790 .any(|entry| {
16791 entry.diagnostic.is_primary
16792 && !entry.range.is_empty()
16793 && entry.range.start == primary_range_start
16794 && entry.diagnostic.message == active_diagnostics.active_message
16795 });
16796
16797 if !is_valid {
16798 self.dismiss_diagnostics(cx);
16799 }
16800 }
16801 }
16802
16803 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16804 match &self.active_diagnostics {
16805 ActiveDiagnostic::Group(group) => Some(group),
16806 _ => None,
16807 }
16808 }
16809
16810 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16811 if !self.diagnostics_enabled() {
16812 return;
16813 }
16814 self.dismiss_diagnostics(cx);
16815 self.active_diagnostics = ActiveDiagnostic::All;
16816 }
16817
16818 fn activate_diagnostics(
16819 &mut self,
16820 buffer_id: BufferId,
16821 diagnostic: DiagnosticEntry<usize>,
16822 window: &mut Window,
16823 cx: &mut Context<Self>,
16824 ) {
16825 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16826 return;
16827 }
16828 self.dismiss_diagnostics(cx);
16829 let snapshot = self.snapshot(window, cx);
16830 let buffer = self.buffer.read(cx).snapshot(cx);
16831 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16832 return;
16833 };
16834
16835 let diagnostic_group = buffer
16836 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16837 .collect::<Vec<_>>();
16838
16839 let blocks =
16840 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16841
16842 let blocks = self.display_map.update(cx, |display_map, cx| {
16843 display_map.insert_blocks(blocks, cx).into_iter().collect()
16844 });
16845 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16846 active_range: buffer.anchor_before(diagnostic.range.start)
16847 ..buffer.anchor_after(diagnostic.range.end),
16848 active_message: diagnostic.diagnostic.message.clone(),
16849 group_id: diagnostic.diagnostic.group_id,
16850 blocks,
16851 });
16852 cx.notify();
16853 }
16854
16855 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16856 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16857 return;
16858 };
16859
16860 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16861 if let ActiveDiagnostic::Group(group) = prev {
16862 self.display_map.update(cx, |display_map, cx| {
16863 display_map.remove_blocks(group.blocks, cx);
16864 });
16865 cx.notify();
16866 }
16867 }
16868
16869 /// Disable inline diagnostics rendering for this editor.
16870 pub fn disable_inline_diagnostics(&mut self) {
16871 self.inline_diagnostics_enabled = false;
16872 self.inline_diagnostics_update = Task::ready(());
16873 self.inline_diagnostics.clear();
16874 }
16875
16876 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16877 self.diagnostics_enabled = false;
16878 self.dismiss_diagnostics(cx);
16879 self.inline_diagnostics_update = Task::ready(());
16880 self.inline_diagnostics.clear();
16881 }
16882
16883 pub fn diagnostics_enabled(&self) -> bool {
16884 self.diagnostics_enabled && self.mode.is_full()
16885 }
16886
16887 pub fn inline_diagnostics_enabled(&self) -> bool {
16888 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16889 }
16890
16891 pub fn show_inline_diagnostics(&self) -> bool {
16892 self.show_inline_diagnostics
16893 }
16894
16895 pub fn toggle_inline_diagnostics(
16896 &mut self,
16897 _: &ToggleInlineDiagnostics,
16898 window: &mut Window,
16899 cx: &mut Context<Editor>,
16900 ) {
16901 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16902 self.refresh_inline_diagnostics(false, window, cx);
16903 }
16904
16905 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16906 self.diagnostics_max_severity = severity;
16907 self.display_map.update(cx, |display_map, _| {
16908 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16909 });
16910 }
16911
16912 pub fn toggle_diagnostics(
16913 &mut self,
16914 _: &ToggleDiagnostics,
16915 window: &mut Window,
16916 cx: &mut Context<Editor>,
16917 ) {
16918 if !self.diagnostics_enabled() {
16919 return;
16920 }
16921
16922 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16923 EditorSettings::get_global(cx)
16924 .diagnostics_max_severity
16925 .filter(|severity| severity != &DiagnosticSeverity::Off)
16926 .unwrap_or(DiagnosticSeverity::Hint)
16927 } else {
16928 DiagnosticSeverity::Off
16929 };
16930 self.set_max_diagnostics_severity(new_severity, cx);
16931 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16932 self.active_diagnostics = ActiveDiagnostic::None;
16933 self.inline_diagnostics_update = Task::ready(());
16934 self.inline_diagnostics.clear();
16935 } else {
16936 self.refresh_inline_diagnostics(false, window, cx);
16937 }
16938
16939 cx.notify();
16940 }
16941
16942 pub fn toggle_minimap(
16943 &mut self,
16944 _: &ToggleMinimap,
16945 window: &mut Window,
16946 cx: &mut Context<Editor>,
16947 ) {
16948 if self.supports_minimap(cx) {
16949 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16950 }
16951 }
16952
16953 fn refresh_inline_diagnostics(
16954 &mut self,
16955 debounce: bool,
16956 window: &mut Window,
16957 cx: &mut Context<Self>,
16958 ) {
16959 let max_severity = ProjectSettings::get_global(cx)
16960 .diagnostics
16961 .inline
16962 .max_severity
16963 .unwrap_or(self.diagnostics_max_severity);
16964
16965 if !self.inline_diagnostics_enabled()
16966 || !self.show_inline_diagnostics
16967 || max_severity == DiagnosticSeverity::Off
16968 {
16969 self.inline_diagnostics_update = Task::ready(());
16970 self.inline_diagnostics.clear();
16971 return;
16972 }
16973
16974 let debounce_ms = ProjectSettings::get_global(cx)
16975 .diagnostics
16976 .inline
16977 .update_debounce_ms;
16978 let debounce = if debounce && debounce_ms > 0 {
16979 Some(Duration::from_millis(debounce_ms))
16980 } else {
16981 None
16982 };
16983 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16984 if let Some(debounce) = debounce {
16985 cx.background_executor().timer(debounce).await;
16986 }
16987 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16988 editor
16989 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16990 .ok()
16991 }) else {
16992 return;
16993 };
16994
16995 let new_inline_diagnostics = cx
16996 .background_spawn(async move {
16997 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16998 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16999 let message = diagnostic_entry
17000 .diagnostic
17001 .message
17002 .split_once('\n')
17003 .map(|(line, _)| line)
17004 .map(SharedString::new)
17005 .unwrap_or_else(|| {
17006 SharedString::from(diagnostic_entry.diagnostic.message)
17007 });
17008 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17009 let (Ok(i) | Err(i)) = inline_diagnostics
17010 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17011 inline_diagnostics.insert(
17012 i,
17013 (
17014 start_anchor,
17015 InlineDiagnostic {
17016 message,
17017 group_id: diagnostic_entry.diagnostic.group_id,
17018 start: diagnostic_entry.range.start.to_point(&snapshot),
17019 is_primary: diagnostic_entry.diagnostic.is_primary,
17020 severity: diagnostic_entry.diagnostic.severity,
17021 },
17022 ),
17023 );
17024 }
17025 inline_diagnostics
17026 })
17027 .await;
17028
17029 editor
17030 .update(cx, |editor, cx| {
17031 editor.inline_diagnostics = new_inline_diagnostics;
17032 cx.notify();
17033 })
17034 .ok();
17035 });
17036 }
17037
17038 fn pull_diagnostics(
17039 &mut self,
17040 buffer_id: Option<BufferId>,
17041 window: &Window,
17042 cx: &mut Context<Self>,
17043 ) -> Option<()> {
17044 if !self.mode().is_full() {
17045 return None;
17046 }
17047 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17048 .diagnostics
17049 .lsp_pull_diagnostics;
17050 if !pull_diagnostics_settings.enabled {
17051 return None;
17052 }
17053 let project = self.project.as_ref()?.downgrade();
17054 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17055 let mut buffers = self.buffer.read(cx).all_buffers();
17056 if let Some(buffer_id) = buffer_id {
17057 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17058 }
17059
17060 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17061 cx.background_executor().timer(debounce).await;
17062
17063 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17064 buffers
17065 .into_iter()
17066 .filter_map(|buffer| {
17067 project
17068 .update(cx, |project, cx| {
17069 project.lsp_store().update(cx, |lsp_store, cx| {
17070 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17071 })
17072 })
17073 .ok()
17074 })
17075 .collect::<FuturesUnordered<_>>()
17076 }) else {
17077 return;
17078 };
17079
17080 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17081 match pull_task {
17082 Ok(()) => {
17083 if editor
17084 .update_in(cx, |editor, window, cx| {
17085 editor.update_diagnostics_state(window, cx);
17086 })
17087 .is_err()
17088 {
17089 return;
17090 }
17091 }
17092 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17093 }
17094 }
17095 });
17096
17097 Some(())
17098 }
17099
17100 pub fn set_selections_from_remote(
17101 &mut self,
17102 selections: Vec<Selection<Anchor>>,
17103 pending_selection: Option<Selection<Anchor>>,
17104 window: &mut Window,
17105 cx: &mut Context<Self>,
17106 ) {
17107 let old_cursor_position = self.selections.newest_anchor().head();
17108 self.selections.change_with(cx, |s| {
17109 s.select_anchors(selections);
17110 if let Some(pending_selection) = pending_selection {
17111 s.set_pending(pending_selection, SelectMode::Character);
17112 } else {
17113 s.clear_pending();
17114 }
17115 });
17116 self.selections_did_change(
17117 false,
17118 &old_cursor_position,
17119 SelectionEffects::default(),
17120 window,
17121 cx,
17122 );
17123 }
17124
17125 pub fn transact(
17126 &mut self,
17127 window: &mut Window,
17128 cx: &mut Context<Self>,
17129 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17130 ) -> Option<TransactionId> {
17131 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17132 this.start_transaction_at(Instant::now(), window, cx);
17133 update(this, window, cx);
17134 this.end_transaction_at(Instant::now(), cx)
17135 })
17136 }
17137
17138 pub fn start_transaction_at(
17139 &mut self,
17140 now: Instant,
17141 window: &mut Window,
17142 cx: &mut Context<Self>,
17143 ) -> Option<TransactionId> {
17144 self.end_selection(window, cx);
17145 if let Some(tx_id) = self
17146 .buffer
17147 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17148 {
17149 self.selection_history
17150 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17151 cx.emit(EditorEvent::TransactionBegun {
17152 transaction_id: tx_id,
17153 });
17154 Some(tx_id)
17155 } else {
17156 None
17157 }
17158 }
17159
17160 pub fn end_transaction_at(
17161 &mut self,
17162 now: Instant,
17163 cx: &mut Context<Self>,
17164 ) -> Option<TransactionId> {
17165 if let Some(transaction_id) = self
17166 .buffer
17167 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17168 {
17169 if let Some((_, end_selections)) =
17170 self.selection_history.transaction_mut(transaction_id)
17171 {
17172 *end_selections = Some(self.selections.disjoint_anchors());
17173 } else {
17174 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17175 }
17176
17177 cx.emit(EditorEvent::Edited { transaction_id });
17178 Some(transaction_id)
17179 } else {
17180 None
17181 }
17182 }
17183
17184 pub fn modify_transaction_selection_history(
17185 &mut self,
17186 transaction_id: TransactionId,
17187 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17188 ) -> bool {
17189 self.selection_history
17190 .transaction_mut(transaction_id)
17191 .map(modify)
17192 .is_some()
17193 }
17194
17195 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17196 if self.selection_mark_mode {
17197 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17198 s.move_with(|_, sel| {
17199 sel.collapse_to(sel.head(), SelectionGoal::None);
17200 });
17201 })
17202 }
17203 self.selection_mark_mode = true;
17204 cx.notify();
17205 }
17206
17207 pub fn swap_selection_ends(
17208 &mut self,
17209 _: &actions::SwapSelectionEnds,
17210 window: &mut Window,
17211 cx: &mut Context<Self>,
17212 ) {
17213 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17214 s.move_with(|_, sel| {
17215 if sel.start != sel.end {
17216 sel.reversed = !sel.reversed
17217 }
17218 });
17219 });
17220 self.request_autoscroll(Autoscroll::newest(), cx);
17221 cx.notify();
17222 }
17223
17224 pub fn toggle_focus(
17225 workspace: &mut Workspace,
17226 _: &actions::ToggleFocus,
17227 window: &mut Window,
17228 cx: &mut Context<Workspace>,
17229 ) {
17230 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17231 return;
17232 };
17233 workspace.activate_item(&item, true, true, window, cx);
17234 }
17235
17236 pub fn toggle_fold(
17237 &mut self,
17238 _: &actions::ToggleFold,
17239 window: &mut Window,
17240 cx: &mut Context<Self>,
17241 ) {
17242 if self.is_singleton(cx) {
17243 let selection = self.selections.newest::<Point>(cx);
17244
17245 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17246 let range = if selection.is_empty() {
17247 let point = selection.head().to_display_point(&display_map);
17248 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17249 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17250 .to_point(&display_map);
17251 start..end
17252 } else {
17253 selection.range()
17254 };
17255 if display_map.folds_in_range(range).next().is_some() {
17256 self.unfold_lines(&Default::default(), window, cx)
17257 } else {
17258 self.fold(&Default::default(), window, cx)
17259 }
17260 } else {
17261 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17262 let buffer_ids: HashSet<_> = self
17263 .selections
17264 .disjoint_anchor_ranges()
17265 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17266 .collect();
17267
17268 let should_unfold = buffer_ids
17269 .iter()
17270 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17271
17272 for buffer_id in buffer_ids {
17273 if should_unfold {
17274 self.unfold_buffer(buffer_id, cx);
17275 } else {
17276 self.fold_buffer(buffer_id, cx);
17277 }
17278 }
17279 }
17280 }
17281
17282 pub fn toggle_fold_recursive(
17283 &mut self,
17284 _: &actions::ToggleFoldRecursive,
17285 window: &mut Window,
17286 cx: &mut Context<Self>,
17287 ) {
17288 let selection = self.selections.newest::<Point>(cx);
17289
17290 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17291 let range = if selection.is_empty() {
17292 let point = selection.head().to_display_point(&display_map);
17293 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17294 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17295 .to_point(&display_map);
17296 start..end
17297 } else {
17298 selection.range()
17299 };
17300 if display_map.folds_in_range(range).next().is_some() {
17301 self.unfold_recursive(&Default::default(), window, cx)
17302 } else {
17303 self.fold_recursive(&Default::default(), window, cx)
17304 }
17305 }
17306
17307 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17308 if self.is_singleton(cx) {
17309 let mut to_fold = Vec::new();
17310 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17311 let selections = self.selections.all_adjusted(cx);
17312
17313 for selection in selections {
17314 let range = selection.range().sorted();
17315 let buffer_start_row = range.start.row;
17316
17317 if range.start.row != range.end.row {
17318 let mut found = false;
17319 let mut row = range.start.row;
17320 while row <= range.end.row {
17321 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17322 {
17323 found = true;
17324 row = crease.range().end.row + 1;
17325 to_fold.push(crease);
17326 } else {
17327 row += 1
17328 }
17329 }
17330 if found {
17331 continue;
17332 }
17333 }
17334
17335 for row in (0..=range.start.row).rev() {
17336 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17337 if crease.range().end.row >= buffer_start_row {
17338 to_fold.push(crease);
17339 if row <= range.start.row {
17340 break;
17341 }
17342 }
17343 }
17344 }
17345 }
17346
17347 self.fold_creases(to_fold, true, window, cx);
17348 } else {
17349 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17350 let buffer_ids = self
17351 .selections
17352 .disjoint_anchor_ranges()
17353 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17354 .collect::<HashSet<_>>();
17355 for buffer_id in buffer_ids {
17356 self.fold_buffer(buffer_id, cx);
17357 }
17358 }
17359 }
17360
17361 pub fn toggle_fold_all(
17362 &mut self,
17363 _: &actions::ToggleFoldAll,
17364 window: &mut Window,
17365 cx: &mut Context<Self>,
17366 ) {
17367 if self.buffer.read(cx).is_singleton() {
17368 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17369 let has_folds = display_map
17370 .folds_in_range(0..display_map.buffer_snapshot.len())
17371 .next()
17372 .is_some();
17373
17374 if has_folds {
17375 self.unfold_all(&actions::UnfoldAll, window, cx);
17376 } else {
17377 self.fold_all(&actions::FoldAll, window, cx);
17378 }
17379 } else {
17380 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17381 let should_unfold = buffer_ids
17382 .iter()
17383 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17384
17385 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17386 editor
17387 .update_in(cx, |editor, _, cx| {
17388 for buffer_id in buffer_ids {
17389 if should_unfold {
17390 editor.unfold_buffer(buffer_id, cx);
17391 } else {
17392 editor.fold_buffer(buffer_id, cx);
17393 }
17394 }
17395 })
17396 .ok();
17397 });
17398 }
17399 }
17400
17401 fn fold_at_level(
17402 &mut self,
17403 fold_at: &FoldAtLevel,
17404 window: &mut Window,
17405 cx: &mut Context<Self>,
17406 ) {
17407 if !self.buffer.read(cx).is_singleton() {
17408 return;
17409 }
17410
17411 let fold_at_level = fold_at.0;
17412 let snapshot = self.buffer.read(cx).snapshot(cx);
17413 let mut to_fold = Vec::new();
17414 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17415
17416 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17417 while start_row < end_row {
17418 match self
17419 .snapshot(window, cx)
17420 .crease_for_buffer_row(MultiBufferRow(start_row))
17421 {
17422 Some(crease) => {
17423 let nested_start_row = crease.range().start.row + 1;
17424 let nested_end_row = crease.range().end.row;
17425
17426 if current_level < fold_at_level {
17427 stack.push((nested_start_row, nested_end_row, current_level + 1));
17428 } else if current_level == fold_at_level {
17429 to_fold.push(crease);
17430 }
17431
17432 start_row = nested_end_row + 1;
17433 }
17434 None => start_row += 1,
17435 }
17436 }
17437 }
17438
17439 self.fold_creases(to_fold, true, window, cx);
17440 }
17441
17442 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17443 if self.buffer.read(cx).is_singleton() {
17444 let mut fold_ranges = Vec::new();
17445 let snapshot = self.buffer.read(cx).snapshot(cx);
17446
17447 for row in 0..snapshot.max_row().0 {
17448 if let Some(foldable_range) = self
17449 .snapshot(window, cx)
17450 .crease_for_buffer_row(MultiBufferRow(row))
17451 {
17452 fold_ranges.push(foldable_range);
17453 }
17454 }
17455
17456 self.fold_creases(fold_ranges, true, window, cx);
17457 } else {
17458 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17459 editor
17460 .update_in(cx, |editor, _, cx| {
17461 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17462 editor.fold_buffer(buffer_id, cx);
17463 }
17464 })
17465 .ok();
17466 });
17467 }
17468 }
17469
17470 pub fn fold_function_bodies(
17471 &mut self,
17472 _: &actions::FoldFunctionBodies,
17473 window: &mut Window,
17474 cx: &mut Context<Self>,
17475 ) {
17476 let snapshot = self.buffer.read(cx).snapshot(cx);
17477
17478 let ranges = snapshot
17479 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17480 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17481 .collect::<Vec<_>>();
17482
17483 let creases = ranges
17484 .into_iter()
17485 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17486 .collect();
17487
17488 self.fold_creases(creases, true, window, cx);
17489 }
17490
17491 pub fn fold_recursive(
17492 &mut self,
17493 _: &actions::FoldRecursive,
17494 window: &mut Window,
17495 cx: &mut Context<Self>,
17496 ) {
17497 let mut to_fold = Vec::new();
17498 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17499 let selections = self.selections.all_adjusted(cx);
17500
17501 for selection in selections {
17502 let range = selection.range().sorted();
17503 let buffer_start_row = range.start.row;
17504
17505 if range.start.row != range.end.row {
17506 let mut found = false;
17507 for row in range.start.row..=range.end.row {
17508 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17509 found = true;
17510 to_fold.push(crease);
17511 }
17512 }
17513 if found {
17514 continue;
17515 }
17516 }
17517
17518 for row in (0..=range.start.row).rev() {
17519 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17520 if crease.range().end.row >= buffer_start_row {
17521 to_fold.push(crease);
17522 } else {
17523 break;
17524 }
17525 }
17526 }
17527 }
17528
17529 self.fold_creases(to_fold, true, window, cx);
17530 }
17531
17532 pub fn fold_at(
17533 &mut self,
17534 buffer_row: MultiBufferRow,
17535 window: &mut Window,
17536 cx: &mut Context<Self>,
17537 ) {
17538 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17539
17540 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17541 let autoscroll = self
17542 .selections
17543 .all::<Point>(cx)
17544 .iter()
17545 .any(|selection| crease.range().overlaps(&selection.range()));
17546
17547 self.fold_creases(vec![crease], autoscroll, window, cx);
17548 }
17549 }
17550
17551 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17552 if self.is_singleton(cx) {
17553 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17554 let buffer = &display_map.buffer_snapshot;
17555 let selections = self.selections.all::<Point>(cx);
17556 let ranges = selections
17557 .iter()
17558 .map(|s| {
17559 let range = s.display_range(&display_map).sorted();
17560 let mut start = range.start.to_point(&display_map);
17561 let mut end = range.end.to_point(&display_map);
17562 start.column = 0;
17563 end.column = buffer.line_len(MultiBufferRow(end.row));
17564 start..end
17565 })
17566 .collect::<Vec<_>>();
17567
17568 self.unfold_ranges(&ranges, true, true, cx);
17569 } else {
17570 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17571 let buffer_ids = self
17572 .selections
17573 .disjoint_anchor_ranges()
17574 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17575 .collect::<HashSet<_>>();
17576 for buffer_id in buffer_ids {
17577 self.unfold_buffer(buffer_id, cx);
17578 }
17579 }
17580 }
17581
17582 pub fn unfold_recursive(
17583 &mut self,
17584 _: &UnfoldRecursive,
17585 _window: &mut Window,
17586 cx: &mut Context<Self>,
17587 ) {
17588 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17589 let selections = self.selections.all::<Point>(cx);
17590 let ranges = selections
17591 .iter()
17592 .map(|s| {
17593 let mut range = s.display_range(&display_map).sorted();
17594 *range.start.column_mut() = 0;
17595 *range.end.column_mut() = display_map.line_len(range.end.row());
17596 let start = range.start.to_point(&display_map);
17597 let end = range.end.to_point(&display_map);
17598 start..end
17599 })
17600 .collect::<Vec<_>>();
17601
17602 self.unfold_ranges(&ranges, true, true, cx);
17603 }
17604
17605 pub fn unfold_at(
17606 &mut self,
17607 buffer_row: MultiBufferRow,
17608 _window: &mut Window,
17609 cx: &mut Context<Self>,
17610 ) {
17611 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17612
17613 let intersection_range = Point::new(buffer_row.0, 0)
17614 ..Point::new(
17615 buffer_row.0,
17616 display_map.buffer_snapshot.line_len(buffer_row),
17617 );
17618
17619 let autoscroll = self
17620 .selections
17621 .all::<Point>(cx)
17622 .iter()
17623 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17624
17625 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17626 }
17627
17628 pub fn unfold_all(
17629 &mut self,
17630 _: &actions::UnfoldAll,
17631 _window: &mut Window,
17632 cx: &mut Context<Self>,
17633 ) {
17634 if self.buffer.read(cx).is_singleton() {
17635 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17636 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17637 } else {
17638 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17639 editor
17640 .update(cx, |editor, cx| {
17641 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17642 editor.unfold_buffer(buffer_id, cx);
17643 }
17644 })
17645 .ok();
17646 });
17647 }
17648 }
17649
17650 pub fn fold_selected_ranges(
17651 &mut self,
17652 _: &FoldSelectedRanges,
17653 window: &mut Window,
17654 cx: &mut Context<Self>,
17655 ) {
17656 let selections = self.selections.all_adjusted(cx);
17657 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17658 let ranges = selections
17659 .into_iter()
17660 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17661 .collect::<Vec<_>>();
17662 self.fold_creases(ranges, true, window, cx);
17663 }
17664
17665 pub fn fold_ranges<T: ToOffset + Clone>(
17666 &mut self,
17667 ranges: Vec<Range<T>>,
17668 auto_scroll: bool,
17669 window: &mut Window,
17670 cx: &mut Context<Self>,
17671 ) {
17672 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17673 let ranges = ranges
17674 .into_iter()
17675 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17676 .collect::<Vec<_>>();
17677 self.fold_creases(ranges, auto_scroll, window, cx);
17678 }
17679
17680 pub fn fold_creases<T: ToOffset + Clone>(
17681 &mut self,
17682 creases: Vec<Crease<T>>,
17683 auto_scroll: bool,
17684 _window: &mut Window,
17685 cx: &mut Context<Self>,
17686 ) {
17687 if creases.is_empty() {
17688 return;
17689 }
17690
17691 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17692
17693 if auto_scroll {
17694 self.request_autoscroll(Autoscroll::fit(), cx);
17695 }
17696
17697 cx.notify();
17698
17699 self.scrollbar_marker_state.dirty = true;
17700 self.folds_did_change(cx);
17701 }
17702
17703 /// Removes any folds whose ranges intersect any of the given ranges.
17704 pub fn unfold_ranges<T: ToOffset + Clone>(
17705 &mut self,
17706 ranges: &[Range<T>],
17707 inclusive: bool,
17708 auto_scroll: bool,
17709 cx: &mut Context<Self>,
17710 ) {
17711 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17712 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17713 });
17714 self.folds_did_change(cx);
17715 }
17716
17717 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17718 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17719 return;
17720 }
17721 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17722 self.display_map.update(cx, |display_map, cx| {
17723 display_map.fold_buffers([buffer_id], cx)
17724 });
17725 cx.emit(EditorEvent::BufferFoldToggled {
17726 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17727 folded: true,
17728 });
17729 cx.notify();
17730 }
17731
17732 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17733 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17734 return;
17735 }
17736 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17737 self.display_map.update(cx, |display_map, cx| {
17738 display_map.unfold_buffers([buffer_id], cx);
17739 });
17740 cx.emit(EditorEvent::BufferFoldToggled {
17741 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17742 folded: false,
17743 });
17744 cx.notify();
17745 }
17746
17747 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17748 self.display_map.read(cx).is_buffer_folded(buffer)
17749 }
17750
17751 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17752 self.display_map.read(cx).folded_buffers()
17753 }
17754
17755 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17756 self.display_map.update(cx, |display_map, cx| {
17757 display_map.disable_header_for_buffer(buffer_id, cx);
17758 });
17759 cx.notify();
17760 }
17761
17762 /// Removes any folds with the given ranges.
17763 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17764 &mut self,
17765 ranges: &[Range<T>],
17766 type_id: TypeId,
17767 auto_scroll: bool,
17768 cx: &mut Context<Self>,
17769 ) {
17770 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17771 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17772 });
17773 self.folds_did_change(cx);
17774 }
17775
17776 fn remove_folds_with<T: ToOffset + Clone>(
17777 &mut self,
17778 ranges: &[Range<T>],
17779 auto_scroll: bool,
17780 cx: &mut Context<Self>,
17781 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17782 ) {
17783 if ranges.is_empty() {
17784 return;
17785 }
17786
17787 let mut buffers_affected = HashSet::default();
17788 let multi_buffer = self.buffer().read(cx);
17789 for range in ranges {
17790 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17791 buffers_affected.insert(buffer.read(cx).remote_id());
17792 };
17793 }
17794
17795 self.display_map.update(cx, update);
17796
17797 if auto_scroll {
17798 self.request_autoscroll(Autoscroll::fit(), cx);
17799 }
17800
17801 cx.notify();
17802 self.scrollbar_marker_state.dirty = true;
17803 self.active_indent_guides_state.dirty = true;
17804 }
17805
17806 pub fn update_renderer_widths(
17807 &mut self,
17808 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17809 cx: &mut Context<Self>,
17810 ) -> bool {
17811 self.display_map
17812 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17813 }
17814
17815 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17816 self.display_map.read(cx).fold_placeholder.clone()
17817 }
17818
17819 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17820 self.buffer.update(cx, |buffer, cx| {
17821 buffer.set_all_diff_hunks_expanded(cx);
17822 });
17823 }
17824
17825 pub fn expand_all_diff_hunks(
17826 &mut self,
17827 _: &ExpandAllDiffHunks,
17828 _window: &mut Window,
17829 cx: &mut Context<Self>,
17830 ) {
17831 self.buffer.update(cx, |buffer, cx| {
17832 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17833 });
17834 }
17835
17836 pub fn toggle_selected_diff_hunks(
17837 &mut self,
17838 _: &ToggleSelectedDiffHunks,
17839 _window: &mut Window,
17840 cx: &mut Context<Self>,
17841 ) {
17842 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17843 self.toggle_diff_hunks_in_ranges(ranges, cx);
17844 }
17845
17846 pub fn diff_hunks_in_ranges<'a>(
17847 &'a self,
17848 ranges: &'a [Range<Anchor>],
17849 buffer: &'a MultiBufferSnapshot,
17850 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17851 ranges.iter().flat_map(move |range| {
17852 let end_excerpt_id = range.end.excerpt_id;
17853 let range = range.to_point(buffer);
17854 let mut peek_end = range.end;
17855 if range.end.row < buffer.max_row().0 {
17856 peek_end = Point::new(range.end.row + 1, 0);
17857 }
17858 buffer
17859 .diff_hunks_in_range(range.start..peek_end)
17860 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17861 })
17862 }
17863
17864 pub fn has_stageable_diff_hunks_in_ranges(
17865 &self,
17866 ranges: &[Range<Anchor>],
17867 snapshot: &MultiBufferSnapshot,
17868 ) -> bool {
17869 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17870 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17871 }
17872
17873 pub fn toggle_staged_selected_diff_hunks(
17874 &mut self,
17875 _: &::git::ToggleStaged,
17876 _: &mut Window,
17877 cx: &mut Context<Self>,
17878 ) {
17879 let snapshot = self.buffer.read(cx).snapshot(cx);
17880 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17881 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17882 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17883 }
17884
17885 pub fn set_render_diff_hunk_controls(
17886 &mut self,
17887 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17888 cx: &mut Context<Self>,
17889 ) {
17890 self.render_diff_hunk_controls = render_diff_hunk_controls;
17891 cx.notify();
17892 }
17893
17894 pub fn stage_and_next(
17895 &mut self,
17896 _: &::git::StageAndNext,
17897 window: &mut Window,
17898 cx: &mut Context<Self>,
17899 ) {
17900 self.do_stage_or_unstage_and_next(true, window, cx);
17901 }
17902
17903 pub fn unstage_and_next(
17904 &mut self,
17905 _: &::git::UnstageAndNext,
17906 window: &mut Window,
17907 cx: &mut Context<Self>,
17908 ) {
17909 self.do_stage_or_unstage_and_next(false, window, cx);
17910 }
17911
17912 pub fn stage_or_unstage_diff_hunks(
17913 &mut self,
17914 stage: bool,
17915 ranges: Vec<Range<Anchor>>,
17916 cx: &mut Context<Self>,
17917 ) {
17918 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17919 cx.spawn(async move |this, cx| {
17920 task.await?;
17921 this.update(cx, |this, cx| {
17922 let snapshot = this.buffer.read(cx).snapshot(cx);
17923 let chunk_by = this
17924 .diff_hunks_in_ranges(&ranges, &snapshot)
17925 .chunk_by(|hunk| hunk.buffer_id);
17926 for (buffer_id, hunks) in &chunk_by {
17927 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17928 }
17929 })
17930 })
17931 .detach_and_log_err(cx);
17932 }
17933
17934 fn save_buffers_for_ranges_if_needed(
17935 &mut self,
17936 ranges: &[Range<Anchor>],
17937 cx: &mut Context<Editor>,
17938 ) -> Task<Result<()>> {
17939 let multibuffer = self.buffer.read(cx);
17940 let snapshot = multibuffer.read(cx);
17941 let buffer_ids: HashSet<_> = ranges
17942 .iter()
17943 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17944 .collect();
17945 drop(snapshot);
17946
17947 let mut buffers = HashSet::default();
17948 for buffer_id in buffer_ids {
17949 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17950 let buffer = buffer_entity.read(cx);
17951 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17952 {
17953 buffers.insert(buffer_entity);
17954 }
17955 }
17956 }
17957
17958 if let Some(project) = &self.project {
17959 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17960 } else {
17961 Task::ready(Ok(()))
17962 }
17963 }
17964
17965 fn do_stage_or_unstage_and_next(
17966 &mut self,
17967 stage: bool,
17968 window: &mut Window,
17969 cx: &mut Context<Self>,
17970 ) {
17971 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17972
17973 if ranges.iter().any(|range| range.start != range.end) {
17974 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17975 return;
17976 }
17977
17978 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17979 let snapshot = self.snapshot(window, cx);
17980 let position = self.selections.newest::<Point>(cx).head();
17981 let mut row = snapshot
17982 .buffer_snapshot
17983 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17984 .find(|hunk| hunk.row_range.start.0 > position.row)
17985 .map(|hunk| hunk.row_range.start);
17986
17987 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17988 // Outside of the project diff editor, wrap around to the beginning.
17989 if !all_diff_hunks_expanded {
17990 row = row.or_else(|| {
17991 snapshot
17992 .buffer_snapshot
17993 .diff_hunks_in_range(Point::zero()..position)
17994 .find(|hunk| hunk.row_range.end.0 < position.row)
17995 .map(|hunk| hunk.row_range.start)
17996 });
17997 }
17998
17999 if let Some(row) = row {
18000 let destination = Point::new(row.0, 0);
18001 let autoscroll = Autoscroll::center();
18002
18003 self.unfold_ranges(&[destination..destination], false, false, cx);
18004 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18005 s.select_ranges([destination..destination]);
18006 });
18007 }
18008 }
18009
18010 fn do_stage_or_unstage(
18011 &self,
18012 stage: bool,
18013 buffer_id: BufferId,
18014 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18015 cx: &mut App,
18016 ) -> Option<()> {
18017 let project = self.project.as_ref()?;
18018 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18019 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18020 let buffer_snapshot = buffer.read(cx).snapshot();
18021 let file_exists = buffer_snapshot
18022 .file()
18023 .is_some_and(|file| file.disk_state().exists());
18024 diff.update(cx, |diff, cx| {
18025 diff.stage_or_unstage_hunks(
18026 stage,
18027 &hunks
18028 .map(|hunk| buffer_diff::DiffHunk {
18029 buffer_range: hunk.buffer_range,
18030 diff_base_byte_range: hunk.diff_base_byte_range,
18031 secondary_status: hunk.secondary_status,
18032 range: Point::zero()..Point::zero(), // unused
18033 })
18034 .collect::<Vec<_>>(),
18035 &buffer_snapshot,
18036 file_exists,
18037 cx,
18038 )
18039 });
18040 None
18041 }
18042
18043 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18044 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18045 self.buffer
18046 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18047 }
18048
18049 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18050 self.buffer.update(cx, |buffer, cx| {
18051 let ranges = vec![Anchor::min()..Anchor::max()];
18052 if !buffer.all_diff_hunks_expanded()
18053 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18054 {
18055 buffer.collapse_diff_hunks(ranges, cx);
18056 true
18057 } else {
18058 false
18059 }
18060 })
18061 }
18062
18063 fn toggle_diff_hunks_in_ranges(
18064 &mut self,
18065 ranges: Vec<Range<Anchor>>,
18066 cx: &mut Context<Editor>,
18067 ) {
18068 self.buffer.update(cx, |buffer, cx| {
18069 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18070 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18071 })
18072 }
18073
18074 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18075 self.buffer.update(cx, |buffer, cx| {
18076 let snapshot = buffer.snapshot(cx);
18077 let excerpt_id = range.end.excerpt_id;
18078 let point_range = range.to_point(&snapshot);
18079 let expand = !buffer.single_hunk_is_expanded(range, cx);
18080 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18081 })
18082 }
18083
18084 pub(crate) fn apply_all_diff_hunks(
18085 &mut self,
18086 _: &ApplyAllDiffHunks,
18087 window: &mut Window,
18088 cx: &mut Context<Self>,
18089 ) {
18090 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18091
18092 let buffers = self.buffer.read(cx).all_buffers();
18093 for branch_buffer in buffers {
18094 branch_buffer.update(cx, |branch_buffer, cx| {
18095 branch_buffer.merge_into_base(Vec::new(), cx);
18096 });
18097 }
18098
18099 if let Some(project) = self.project.clone() {
18100 self.save(
18101 SaveOptions {
18102 format: true,
18103 autosave: false,
18104 },
18105 project,
18106 window,
18107 cx,
18108 )
18109 .detach_and_log_err(cx);
18110 }
18111 }
18112
18113 pub(crate) fn apply_selected_diff_hunks(
18114 &mut self,
18115 _: &ApplyDiffHunk,
18116 window: &mut Window,
18117 cx: &mut Context<Self>,
18118 ) {
18119 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18120 let snapshot = self.snapshot(window, cx);
18121 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18122 let mut ranges_by_buffer = HashMap::default();
18123 self.transact(window, cx, |editor, _window, cx| {
18124 for hunk in hunks {
18125 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18126 ranges_by_buffer
18127 .entry(buffer.clone())
18128 .or_insert_with(Vec::new)
18129 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18130 }
18131 }
18132
18133 for (buffer, ranges) in ranges_by_buffer {
18134 buffer.update(cx, |buffer, cx| {
18135 buffer.merge_into_base(ranges, cx);
18136 });
18137 }
18138 });
18139
18140 if let Some(project) = self.project.clone() {
18141 self.save(
18142 SaveOptions {
18143 format: true,
18144 autosave: false,
18145 },
18146 project,
18147 window,
18148 cx,
18149 )
18150 .detach_and_log_err(cx);
18151 }
18152 }
18153
18154 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18155 if hovered != self.gutter_hovered {
18156 self.gutter_hovered = hovered;
18157 cx.notify();
18158 }
18159 }
18160
18161 pub fn insert_blocks(
18162 &mut self,
18163 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18164 autoscroll: Option<Autoscroll>,
18165 cx: &mut Context<Self>,
18166 ) -> Vec<CustomBlockId> {
18167 let blocks = self
18168 .display_map
18169 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18170 if let Some(autoscroll) = autoscroll {
18171 self.request_autoscroll(autoscroll, cx);
18172 }
18173 cx.notify();
18174 blocks
18175 }
18176
18177 pub fn resize_blocks(
18178 &mut self,
18179 heights: HashMap<CustomBlockId, u32>,
18180 autoscroll: Option<Autoscroll>,
18181 cx: &mut Context<Self>,
18182 ) {
18183 self.display_map
18184 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18185 if let Some(autoscroll) = autoscroll {
18186 self.request_autoscroll(autoscroll, cx);
18187 }
18188 cx.notify();
18189 }
18190
18191 pub fn replace_blocks(
18192 &mut self,
18193 renderers: HashMap<CustomBlockId, RenderBlock>,
18194 autoscroll: Option<Autoscroll>,
18195 cx: &mut Context<Self>,
18196 ) {
18197 self.display_map
18198 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18199 if let Some(autoscroll) = autoscroll {
18200 self.request_autoscroll(autoscroll, cx);
18201 }
18202 cx.notify();
18203 }
18204
18205 pub fn remove_blocks(
18206 &mut self,
18207 block_ids: HashSet<CustomBlockId>,
18208 autoscroll: Option<Autoscroll>,
18209 cx: &mut Context<Self>,
18210 ) {
18211 self.display_map.update(cx, |display_map, cx| {
18212 display_map.remove_blocks(block_ids, cx)
18213 });
18214 if let Some(autoscroll) = autoscroll {
18215 self.request_autoscroll(autoscroll, cx);
18216 }
18217 cx.notify();
18218 }
18219
18220 pub fn row_for_block(
18221 &self,
18222 block_id: CustomBlockId,
18223 cx: &mut Context<Self>,
18224 ) -> Option<DisplayRow> {
18225 self.display_map
18226 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18227 }
18228
18229 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18230 self.focused_block = Some(focused_block);
18231 }
18232
18233 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18234 self.focused_block.take()
18235 }
18236
18237 pub fn insert_creases(
18238 &mut self,
18239 creases: impl IntoIterator<Item = Crease<Anchor>>,
18240 cx: &mut Context<Self>,
18241 ) -> Vec<CreaseId> {
18242 self.display_map
18243 .update(cx, |map, cx| map.insert_creases(creases, cx))
18244 }
18245
18246 pub fn remove_creases(
18247 &mut self,
18248 ids: impl IntoIterator<Item = CreaseId>,
18249 cx: &mut Context<Self>,
18250 ) -> Vec<(CreaseId, Range<Anchor>)> {
18251 self.display_map
18252 .update(cx, |map, cx| map.remove_creases(ids, cx))
18253 }
18254
18255 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18256 self.display_map
18257 .update(cx, |map, cx| map.snapshot(cx))
18258 .longest_row()
18259 }
18260
18261 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18262 self.display_map
18263 .update(cx, |map, cx| map.snapshot(cx))
18264 .max_point()
18265 }
18266
18267 pub fn text(&self, cx: &App) -> String {
18268 self.buffer.read(cx).read(cx).text()
18269 }
18270
18271 pub fn is_empty(&self, cx: &App) -> bool {
18272 self.buffer.read(cx).read(cx).is_empty()
18273 }
18274
18275 pub fn text_option(&self, cx: &App) -> Option<String> {
18276 let text = self.text(cx);
18277 let text = text.trim();
18278
18279 if text.is_empty() {
18280 return None;
18281 }
18282
18283 Some(text.to_string())
18284 }
18285
18286 pub fn set_text(
18287 &mut self,
18288 text: impl Into<Arc<str>>,
18289 window: &mut Window,
18290 cx: &mut Context<Self>,
18291 ) {
18292 self.transact(window, cx, |this, _, cx| {
18293 this.buffer
18294 .read(cx)
18295 .as_singleton()
18296 .expect("you can only call set_text on editors for singleton buffers")
18297 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18298 });
18299 }
18300
18301 pub fn display_text(&self, cx: &mut App) -> String {
18302 self.display_map
18303 .update(cx, |map, cx| map.snapshot(cx))
18304 .text()
18305 }
18306
18307 fn create_minimap(
18308 &self,
18309 minimap_settings: MinimapSettings,
18310 window: &mut Window,
18311 cx: &mut Context<Self>,
18312 ) -> Option<Entity<Self>> {
18313 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18314 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18315 }
18316
18317 fn initialize_new_minimap(
18318 &self,
18319 minimap_settings: MinimapSettings,
18320 window: &mut Window,
18321 cx: &mut Context<Self>,
18322 ) -> Entity<Self> {
18323 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18324
18325 let mut minimap = Editor::new_internal(
18326 EditorMode::Minimap {
18327 parent: cx.weak_entity(),
18328 },
18329 self.buffer.clone(),
18330 None,
18331 Some(self.display_map.clone()),
18332 window,
18333 cx,
18334 );
18335 minimap.scroll_manager.clone_state(&self.scroll_manager);
18336 minimap.set_text_style_refinement(TextStyleRefinement {
18337 font_size: Some(MINIMAP_FONT_SIZE),
18338 font_weight: Some(MINIMAP_FONT_WEIGHT),
18339 ..Default::default()
18340 });
18341 minimap.update_minimap_configuration(minimap_settings, cx);
18342 cx.new(|_| minimap)
18343 }
18344
18345 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18346 let current_line_highlight = minimap_settings
18347 .current_line_highlight
18348 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18349 self.set_current_line_highlight(Some(current_line_highlight));
18350 }
18351
18352 pub fn minimap(&self) -> Option<&Entity<Self>> {
18353 self.minimap
18354 .as_ref()
18355 .filter(|_| self.minimap_visibility.visible())
18356 }
18357
18358 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18359 let mut wrap_guides = smallvec![];
18360
18361 if self.show_wrap_guides == Some(false) {
18362 return wrap_guides;
18363 }
18364
18365 let settings = self.buffer.read(cx).language_settings(cx);
18366 if settings.show_wrap_guides {
18367 match self.soft_wrap_mode(cx) {
18368 SoftWrap::Column(soft_wrap) => {
18369 wrap_guides.push((soft_wrap as usize, true));
18370 }
18371 SoftWrap::Bounded(soft_wrap) => {
18372 wrap_guides.push((soft_wrap as usize, true));
18373 }
18374 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18375 }
18376 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18377 }
18378
18379 wrap_guides
18380 }
18381
18382 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18383 let settings = self.buffer.read(cx).language_settings(cx);
18384 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18385 match mode {
18386 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18387 SoftWrap::None
18388 }
18389 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18390 language_settings::SoftWrap::PreferredLineLength => {
18391 SoftWrap::Column(settings.preferred_line_length)
18392 }
18393 language_settings::SoftWrap::Bounded => {
18394 SoftWrap::Bounded(settings.preferred_line_length)
18395 }
18396 }
18397 }
18398
18399 pub fn set_soft_wrap_mode(
18400 &mut self,
18401 mode: language_settings::SoftWrap,
18402
18403 cx: &mut Context<Self>,
18404 ) {
18405 self.soft_wrap_mode_override = Some(mode);
18406 cx.notify();
18407 }
18408
18409 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18410 self.hard_wrap = hard_wrap;
18411 cx.notify();
18412 }
18413
18414 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18415 self.text_style_refinement = Some(style);
18416 }
18417
18418 /// called by the Element so we know what style we were most recently rendered with.
18419 pub(crate) fn set_style(
18420 &mut self,
18421 style: EditorStyle,
18422 window: &mut Window,
18423 cx: &mut Context<Self>,
18424 ) {
18425 // We intentionally do not inform the display map about the minimap style
18426 // so that wrapping is not recalculated and stays consistent for the editor
18427 // and its linked minimap.
18428 if !self.mode.is_minimap() {
18429 let rem_size = window.rem_size();
18430 self.display_map.update(cx, |map, cx| {
18431 map.set_font(
18432 style.text.font(),
18433 style.text.font_size.to_pixels(rem_size),
18434 cx,
18435 )
18436 });
18437 }
18438 self.style = Some(style);
18439 }
18440
18441 pub fn style(&self) -> Option<&EditorStyle> {
18442 self.style.as_ref()
18443 }
18444
18445 // Called by the element. This method is not designed to be called outside of the editor
18446 // element's layout code because it does not notify when rewrapping is computed synchronously.
18447 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18448 self.display_map
18449 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18450 }
18451
18452 pub fn set_soft_wrap(&mut self) {
18453 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18454 }
18455
18456 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18457 if self.soft_wrap_mode_override.is_some() {
18458 self.soft_wrap_mode_override.take();
18459 } else {
18460 let soft_wrap = match self.soft_wrap_mode(cx) {
18461 SoftWrap::GitDiff => return,
18462 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18463 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18464 language_settings::SoftWrap::None
18465 }
18466 };
18467 self.soft_wrap_mode_override = Some(soft_wrap);
18468 }
18469 cx.notify();
18470 }
18471
18472 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18473 let Some(workspace) = self.workspace() else {
18474 return;
18475 };
18476 let fs = workspace.read(cx).app_state().fs.clone();
18477 let current_show = TabBarSettings::get_global(cx).show;
18478 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18479 setting.show = Some(!current_show);
18480 });
18481 }
18482
18483 pub fn toggle_indent_guides(
18484 &mut self,
18485 _: &ToggleIndentGuides,
18486 _: &mut Window,
18487 cx: &mut Context<Self>,
18488 ) {
18489 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18490 self.buffer
18491 .read(cx)
18492 .language_settings(cx)
18493 .indent_guides
18494 .enabled
18495 });
18496 self.show_indent_guides = Some(!currently_enabled);
18497 cx.notify();
18498 }
18499
18500 fn should_show_indent_guides(&self) -> Option<bool> {
18501 self.show_indent_guides
18502 }
18503
18504 pub fn toggle_line_numbers(
18505 &mut self,
18506 _: &ToggleLineNumbers,
18507 _: &mut Window,
18508 cx: &mut Context<Self>,
18509 ) {
18510 let mut editor_settings = EditorSettings::get_global(cx).clone();
18511 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18512 EditorSettings::override_global(editor_settings, cx);
18513 }
18514
18515 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18516 if let Some(show_line_numbers) = self.show_line_numbers {
18517 return show_line_numbers;
18518 }
18519 EditorSettings::get_global(cx).gutter.line_numbers
18520 }
18521
18522 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18523 self.use_relative_line_numbers
18524 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18525 }
18526
18527 pub fn toggle_relative_line_numbers(
18528 &mut self,
18529 _: &ToggleRelativeLineNumbers,
18530 _: &mut Window,
18531 cx: &mut Context<Self>,
18532 ) {
18533 let is_relative = self.should_use_relative_line_numbers(cx);
18534 self.set_relative_line_number(Some(!is_relative), cx)
18535 }
18536
18537 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18538 self.use_relative_line_numbers = is_relative;
18539 cx.notify();
18540 }
18541
18542 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18543 self.show_gutter = show_gutter;
18544 cx.notify();
18545 }
18546
18547 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18548 self.show_scrollbars = ScrollbarAxes {
18549 horizontal: show,
18550 vertical: show,
18551 };
18552 cx.notify();
18553 }
18554
18555 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18556 self.show_scrollbars.vertical = show;
18557 cx.notify();
18558 }
18559
18560 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18561 self.show_scrollbars.horizontal = show;
18562 cx.notify();
18563 }
18564
18565 pub fn set_minimap_visibility(
18566 &mut self,
18567 minimap_visibility: MinimapVisibility,
18568 window: &mut Window,
18569 cx: &mut Context<Self>,
18570 ) {
18571 if self.minimap_visibility != minimap_visibility {
18572 if minimap_visibility.visible() && self.minimap.is_none() {
18573 let minimap_settings = EditorSettings::get_global(cx).minimap;
18574 self.minimap =
18575 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18576 }
18577 self.minimap_visibility = minimap_visibility;
18578 cx.notify();
18579 }
18580 }
18581
18582 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18583 self.set_show_scrollbars(false, cx);
18584 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18585 }
18586
18587 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18588 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18589 }
18590
18591 /// Normally the text in full mode and auto height editors is padded on the
18592 /// left side by roughly half a character width for improved hit testing.
18593 ///
18594 /// Use this method to disable this for cases where this is not wanted (e.g.
18595 /// if you want to align the editor text with some other text above or below)
18596 /// or if you want to add this padding to single-line editors.
18597 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18598 self.offset_content = offset_content;
18599 cx.notify();
18600 }
18601
18602 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18603 self.show_line_numbers = Some(show_line_numbers);
18604 cx.notify();
18605 }
18606
18607 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18608 self.disable_expand_excerpt_buttons = true;
18609 cx.notify();
18610 }
18611
18612 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18613 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18614 cx.notify();
18615 }
18616
18617 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18618 self.show_code_actions = Some(show_code_actions);
18619 cx.notify();
18620 }
18621
18622 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18623 self.show_runnables = Some(show_runnables);
18624 cx.notify();
18625 }
18626
18627 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18628 self.show_breakpoints = Some(show_breakpoints);
18629 cx.notify();
18630 }
18631
18632 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18633 if self.display_map.read(cx).masked != masked {
18634 self.display_map.update(cx, |map, _| map.masked = masked);
18635 }
18636 cx.notify()
18637 }
18638
18639 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18640 self.show_wrap_guides = Some(show_wrap_guides);
18641 cx.notify();
18642 }
18643
18644 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18645 self.show_indent_guides = Some(show_indent_guides);
18646 cx.notify();
18647 }
18648
18649 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18650 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18651 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18652 if let Some(dir) = file.abs_path(cx).parent() {
18653 return Some(dir.to_owned());
18654 }
18655 }
18656
18657 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18658 return Some(project_path.path.to_path_buf());
18659 }
18660 }
18661
18662 None
18663 }
18664
18665 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18666 self.active_excerpt(cx)?
18667 .1
18668 .read(cx)
18669 .file()
18670 .and_then(|f| f.as_local())
18671 }
18672
18673 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18674 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18675 let buffer = buffer.read(cx);
18676 if let Some(project_path) = buffer.project_path(cx) {
18677 let project = self.project.as_ref()?.read(cx);
18678 project.absolute_path(&project_path, cx)
18679 } else {
18680 buffer
18681 .file()
18682 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18683 }
18684 })
18685 }
18686
18687 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18688 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18689 let project_path = buffer.read(cx).project_path(cx)?;
18690 let project = self.project.as_ref()?.read(cx);
18691 let entry = project.entry_for_path(&project_path, cx)?;
18692 let path = entry.path.to_path_buf();
18693 Some(path)
18694 })
18695 }
18696
18697 pub fn reveal_in_finder(
18698 &mut self,
18699 _: &RevealInFileManager,
18700 _window: &mut Window,
18701 cx: &mut Context<Self>,
18702 ) {
18703 if let Some(target) = self.target_file(cx) {
18704 cx.reveal_path(&target.abs_path(cx));
18705 }
18706 }
18707
18708 pub fn copy_path(
18709 &mut self,
18710 _: &zed_actions::workspace::CopyPath,
18711 _window: &mut Window,
18712 cx: &mut Context<Self>,
18713 ) {
18714 if let Some(path) = self.target_file_abs_path(cx) {
18715 if let Some(path) = path.to_str() {
18716 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18717 }
18718 }
18719 }
18720
18721 pub fn copy_relative_path(
18722 &mut self,
18723 _: &zed_actions::workspace::CopyRelativePath,
18724 _window: &mut Window,
18725 cx: &mut Context<Self>,
18726 ) {
18727 if let Some(path) = self.target_file_path(cx) {
18728 if let Some(path) = path.to_str() {
18729 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18730 }
18731 }
18732 }
18733
18734 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18735 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18736 buffer.read(cx).project_path(cx)
18737 } else {
18738 None
18739 }
18740 }
18741
18742 // Returns true if the editor handled a go-to-line request
18743 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18744 maybe!({
18745 let breakpoint_store = self.breakpoint_store.as_ref()?;
18746
18747 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18748 else {
18749 self.clear_row_highlights::<ActiveDebugLine>();
18750 return None;
18751 };
18752
18753 let position = active_stack_frame.position;
18754 let buffer_id = position.buffer_id?;
18755 let snapshot = self
18756 .project
18757 .as_ref()?
18758 .read(cx)
18759 .buffer_for_id(buffer_id, cx)?
18760 .read(cx)
18761 .snapshot();
18762
18763 let mut handled = false;
18764 for (id, ExcerptRange { context, .. }) in
18765 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18766 {
18767 if context.start.cmp(&position, &snapshot).is_ge()
18768 || context.end.cmp(&position, &snapshot).is_lt()
18769 {
18770 continue;
18771 }
18772 let snapshot = self.buffer.read(cx).snapshot(cx);
18773 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18774
18775 handled = true;
18776 self.clear_row_highlights::<ActiveDebugLine>();
18777
18778 self.go_to_line::<ActiveDebugLine>(
18779 multibuffer_anchor,
18780 Some(cx.theme().colors().editor_debugger_active_line_background),
18781 window,
18782 cx,
18783 );
18784
18785 cx.notify();
18786 }
18787
18788 handled.then_some(())
18789 })
18790 .is_some()
18791 }
18792
18793 pub fn copy_file_name_without_extension(
18794 &mut self,
18795 _: &CopyFileNameWithoutExtension,
18796 _: &mut Window,
18797 cx: &mut Context<Self>,
18798 ) {
18799 if let Some(file) = self.target_file(cx) {
18800 if let Some(file_stem) = file.path().file_stem() {
18801 if let Some(name) = file_stem.to_str() {
18802 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18803 }
18804 }
18805 }
18806 }
18807
18808 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18809 if let Some(file) = self.target_file(cx) {
18810 if let Some(file_name) = file.path().file_name() {
18811 if let Some(name) = file_name.to_str() {
18812 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18813 }
18814 }
18815 }
18816 }
18817
18818 pub fn toggle_git_blame(
18819 &mut self,
18820 _: &::git::Blame,
18821 window: &mut Window,
18822 cx: &mut Context<Self>,
18823 ) {
18824 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18825
18826 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18827 self.start_git_blame(true, window, cx);
18828 }
18829
18830 cx.notify();
18831 }
18832
18833 pub fn toggle_git_blame_inline(
18834 &mut self,
18835 _: &ToggleGitBlameInline,
18836 window: &mut Window,
18837 cx: &mut Context<Self>,
18838 ) {
18839 self.toggle_git_blame_inline_internal(true, window, cx);
18840 cx.notify();
18841 }
18842
18843 pub fn open_git_blame_commit(
18844 &mut self,
18845 _: &OpenGitBlameCommit,
18846 window: &mut Window,
18847 cx: &mut Context<Self>,
18848 ) {
18849 self.open_git_blame_commit_internal(window, cx);
18850 }
18851
18852 fn open_git_blame_commit_internal(
18853 &mut self,
18854 window: &mut Window,
18855 cx: &mut Context<Self>,
18856 ) -> Option<()> {
18857 let blame = self.blame.as_ref()?;
18858 let snapshot = self.snapshot(window, cx);
18859 let cursor = self.selections.newest::<Point>(cx).head();
18860 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18861 let blame_entry = blame
18862 .update(cx, |blame, cx| {
18863 blame
18864 .blame_for_rows(
18865 &[RowInfo {
18866 buffer_id: Some(buffer.remote_id()),
18867 buffer_row: Some(point.row),
18868 ..Default::default()
18869 }],
18870 cx,
18871 )
18872 .next()
18873 })
18874 .flatten()?;
18875 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18876 let repo = blame.read(cx).repository(cx)?;
18877 let workspace = self.workspace()?.downgrade();
18878 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18879 None
18880 }
18881
18882 pub fn git_blame_inline_enabled(&self) -> bool {
18883 self.git_blame_inline_enabled
18884 }
18885
18886 pub fn toggle_selection_menu(
18887 &mut self,
18888 _: &ToggleSelectionMenu,
18889 _: &mut Window,
18890 cx: &mut Context<Self>,
18891 ) {
18892 self.show_selection_menu = self
18893 .show_selection_menu
18894 .map(|show_selections_menu| !show_selections_menu)
18895 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18896
18897 cx.notify();
18898 }
18899
18900 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18901 self.show_selection_menu
18902 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18903 }
18904
18905 fn start_git_blame(
18906 &mut self,
18907 user_triggered: bool,
18908 window: &mut Window,
18909 cx: &mut Context<Self>,
18910 ) {
18911 if let Some(project) = self.project.as_ref() {
18912 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18913 return;
18914 };
18915
18916 if buffer.read(cx).file().is_none() {
18917 return;
18918 }
18919
18920 let focused = self.focus_handle(cx).contains_focused(window, cx);
18921
18922 let project = project.clone();
18923 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18924 self.blame_subscription =
18925 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18926 self.blame = Some(blame);
18927 }
18928 }
18929
18930 fn toggle_git_blame_inline_internal(
18931 &mut self,
18932 user_triggered: bool,
18933 window: &mut Window,
18934 cx: &mut Context<Self>,
18935 ) {
18936 if self.git_blame_inline_enabled {
18937 self.git_blame_inline_enabled = false;
18938 self.show_git_blame_inline = false;
18939 self.show_git_blame_inline_delay_task.take();
18940 } else {
18941 self.git_blame_inline_enabled = true;
18942 self.start_git_blame_inline(user_triggered, window, cx);
18943 }
18944
18945 cx.notify();
18946 }
18947
18948 fn start_git_blame_inline(
18949 &mut self,
18950 user_triggered: bool,
18951 window: &mut Window,
18952 cx: &mut Context<Self>,
18953 ) {
18954 self.start_git_blame(user_triggered, window, cx);
18955
18956 if ProjectSettings::get_global(cx)
18957 .git
18958 .inline_blame_delay()
18959 .is_some()
18960 {
18961 self.start_inline_blame_timer(window, cx);
18962 } else {
18963 self.show_git_blame_inline = true
18964 }
18965 }
18966
18967 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18968 self.blame.as_ref()
18969 }
18970
18971 pub fn show_git_blame_gutter(&self) -> bool {
18972 self.show_git_blame_gutter
18973 }
18974
18975 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18976 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18977 }
18978
18979 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18980 self.show_git_blame_inline
18981 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18982 && !self.newest_selection_head_on_empty_line(cx)
18983 && self.has_blame_entries(cx)
18984 }
18985
18986 fn has_blame_entries(&self, cx: &App) -> bool {
18987 self.blame()
18988 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18989 }
18990
18991 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18992 let cursor_anchor = self.selections.newest_anchor().head();
18993
18994 let snapshot = self.buffer.read(cx).snapshot(cx);
18995 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18996
18997 snapshot.line_len(buffer_row) == 0
18998 }
18999
19000 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19001 let buffer_and_selection = maybe!({
19002 let selection = self.selections.newest::<Point>(cx);
19003 let selection_range = selection.range();
19004
19005 let multi_buffer = self.buffer().read(cx);
19006 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19007 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19008
19009 let (buffer, range, _) = if selection.reversed {
19010 buffer_ranges.first()
19011 } else {
19012 buffer_ranges.last()
19013 }?;
19014
19015 let selection = text::ToPoint::to_point(&range.start, &buffer).row
19016 ..text::ToPoint::to_point(&range.end, &buffer).row;
19017 Some((
19018 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
19019 selection,
19020 ))
19021 });
19022
19023 let Some((buffer, selection)) = buffer_and_selection else {
19024 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19025 };
19026
19027 let Some(project) = self.project.as_ref() else {
19028 return Task::ready(Err(anyhow!("editor does not have project")));
19029 };
19030
19031 project.update(cx, |project, cx| {
19032 project.get_permalink_to_line(&buffer, selection, cx)
19033 })
19034 }
19035
19036 pub fn copy_permalink_to_line(
19037 &mut self,
19038 _: &CopyPermalinkToLine,
19039 window: &mut Window,
19040 cx: &mut Context<Self>,
19041 ) {
19042 let permalink_task = self.get_permalink_to_line(cx);
19043 let workspace = self.workspace();
19044
19045 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19046 Ok(permalink) => {
19047 cx.update(|_, cx| {
19048 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19049 })
19050 .ok();
19051 }
19052 Err(err) => {
19053 let message = format!("Failed to copy permalink: {err}");
19054
19055 anyhow::Result::<()>::Err(err).log_err();
19056
19057 if let Some(workspace) = workspace {
19058 workspace
19059 .update_in(cx, |workspace, _, cx| {
19060 struct CopyPermalinkToLine;
19061
19062 workspace.show_toast(
19063 Toast::new(
19064 NotificationId::unique::<CopyPermalinkToLine>(),
19065 message,
19066 ),
19067 cx,
19068 )
19069 })
19070 .ok();
19071 }
19072 }
19073 })
19074 .detach();
19075 }
19076
19077 pub fn copy_file_location(
19078 &mut self,
19079 _: &CopyFileLocation,
19080 _: &mut Window,
19081 cx: &mut Context<Self>,
19082 ) {
19083 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19084 if let Some(file) = self.target_file(cx) {
19085 if let Some(path) = file.path().to_str() {
19086 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19087 }
19088 }
19089 }
19090
19091 pub fn open_permalink_to_line(
19092 &mut self,
19093 _: &OpenPermalinkToLine,
19094 window: &mut Window,
19095 cx: &mut Context<Self>,
19096 ) {
19097 let permalink_task = self.get_permalink_to_line(cx);
19098 let workspace = self.workspace();
19099
19100 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19101 Ok(permalink) => {
19102 cx.update(|_, cx| {
19103 cx.open_url(permalink.as_ref());
19104 })
19105 .ok();
19106 }
19107 Err(err) => {
19108 let message = format!("Failed to open permalink: {err}");
19109
19110 anyhow::Result::<()>::Err(err).log_err();
19111
19112 if let Some(workspace) = workspace {
19113 workspace
19114 .update(cx, |workspace, cx| {
19115 struct OpenPermalinkToLine;
19116
19117 workspace.show_toast(
19118 Toast::new(
19119 NotificationId::unique::<OpenPermalinkToLine>(),
19120 message,
19121 ),
19122 cx,
19123 )
19124 })
19125 .ok();
19126 }
19127 }
19128 })
19129 .detach();
19130 }
19131
19132 pub fn insert_uuid_v4(
19133 &mut self,
19134 _: &InsertUuidV4,
19135 window: &mut Window,
19136 cx: &mut Context<Self>,
19137 ) {
19138 self.insert_uuid(UuidVersion::V4, window, cx);
19139 }
19140
19141 pub fn insert_uuid_v7(
19142 &mut self,
19143 _: &InsertUuidV7,
19144 window: &mut Window,
19145 cx: &mut Context<Self>,
19146 ) {
19147 self.insert_uuid(UuidVersion::V7, window, cx);
19148 }
19149
19150 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19151 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19152 self.transact(window, cx, |this, window, cx| {
19153 let edits = this
19154 .selections
19155 .all::<Point>(cx)
19156 .into_iter()
19157 .map(|selection| {
19158 let uuid = match version {
19159 UuidVersion::V4 => uuid::Uuid::new_v4(),
19160 UuidVersion::V7 => uuid::Uuid::now_v7(),
19161 };
19162
19163 (selection.range(), uuid.to_string())
19164 });
19165 this.edit(edits, cx);
19166 this.refresh_edit_prediction(true, false, window, cx);
19167 });
19168 }
19169
19170 pub fn open_selections_in_multibuffer(
19171 &mut self,
19172 _: &OpenSelectionsInMultibuffer,
19173 window: &mut Window,
19174 cx: &mut Context<Self>,
19175 ) {
19176 let multibuffer = self.buffer.read(cx);
19177
19178 let Some(buffer) = multibuffer.as_singleton() else {
19179 return;
19180 };
19181
19182 let Some(workspace) = self.workspace() else {
19183 return;
19184 };
19185
19186 let title = multibuffer.title(cx).to_string();
19187
19188 let locations = self
19189 .selections
19190 .all_anchors(cx)
19191 .into_iter()
19192 .map(|selection| Location {
19193 buffer: buffer.clone(),
19194 range: selection.start.text_anchor..selection.end.text_anchor,
19195 })
19196 .collect::<Vec<_>>();
19197
19198 cx.spawn_in(window, async move |_, cx| {
19199 workspace.update_in(cx, |workspace, window, cx| {
19200 Self::open_locations_in_multibuffer(
19201 workspace,
19202 locations,
19203 format!("Selections for '{title}'"),
19204 false,
19205 MultibufferSelectionMode::All,
19206 window,
19207 cx,
19208 );
19209 })
19210 })
19211 .detach();
19212 }
19213
19214 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19215 /// last highlight added will be used.
19216 ///
19217 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19218 pub fn highlight_rows<T: 'static>(
19219 &mut self,
19220 range: Range<Anchor>,
19221 color: Hsla,
19222 options: RowHighlightOptions,
19223 cx: &mut Context<Self>,
19224 ) {
19225 let snapshot = self.buffer().read(cx).snapshot(cx);
19226 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19227 let ix = row_highlights.binary_search_by(|highlight| {
19228 Ordering::Equal
19229 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19230 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19231 });
19232
19233 if let Err(mut ix) = ix {
19234 let index = post_inc(&mut self.highlight_order);
19235
19236 // If this range intersects with the preceding highlight, then merge it with
19237 // the preceding highlight. Otherwise insert a new highlight.
19238 let mut merged = false;
19239 if ix > 0 {
19240 let prev_highlight = &mut row_highlights[ix - 1];
19241 if prev_highlight
19242 .range
19243 .end
19244 .cmp(&range.start, &snapshot)
19245 .is_ge()
19246 {
19247 ix -= 1;
19248 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19249 prev_highlight.range.end = range.end;
19250 }
19251 merged = true;
19252 prev_highlight.index = index;
19253 prev_highlight.color = color;
19254 prev_highlight.options = options;
19255 }
19256 }
19257
19258 if !merged {
19259 row_highlights.insert(
19260 ix,
19261 RowHighlight {
19262 range: range.clone(),
19263 index,
19264 color,
19265 options,
19266 type_id: TypeId::of::<T>(),
19267 },
19268 );
19269 }
19270
19271 // If any of the following highlights intersect with this one, merge them.
19272 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19273 let highlight = &row_highlights[ix];
19274 if next_highlight
19275 .range
19276 .start
19277 .cmp(&highlight.range.end, &snapshot)
19278 .is_le()
19279 {
19280 if next_highlight
19281 .range
19282 .end
19283 .cmp(&highlight.range.end, &snapshot)
19284 .is_gt()
19285 {
19286 row_highlights[ix].range.end = next_highlight.range.end;
19287 }
19288 row_highlights.remove(ix + 1);
19289 } else {
19290 break;
19291 }
19292 }
19293 }
19294 }
19295
19296 /// Remove any highlighted row ranges of the given type that intersect the
19297 /// given ranges.
19298 pub fn remove_highlighted_rows<T: 'static>(
19299 &mut self,
19300 ranges_to_remove: Vec<Range<Anchor>>,
19301 cx: &mut Context<Self>,
19302 ) {
19303 let snapshot = self.buffer().read(cx).snapshot(cx);
19304 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19305 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19306 row_highlights.retain(|highlight| {
19307 while let Some(range_to_remove) = ranges_to_remove.peek() {
19308 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19309 Ordering::Less | Ordering::Equal => {
19310 ranges_to_remove.next();
19311 }
19312 Ordering::Greater => {
19313 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19314 Ordering::Less | Ordering::Equal => {
19315 return false;
19316 }
19317 Ordering::Greater => break,
19318 }
19319 }
19320 }
19321 }
19322
19323 true
19324 })
19325 }
19326
19327 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19328 pub fn clear_row_highlights<T: 'static>(&mut self) {
19329 self.highlighted_rows.remove(&TypeId::of::<T>());
19330 }
19331
19332 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19333 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19334 self.highlighted_rows
19335 .get(&TypeId::of::<T>())
19336 .map_or(&[] as &[_], |vec| vec.as_slice())
19337 .iter()
19338 .map(|highlight| (highlight.range.clone(), highlight.color))
19339 }
19340
19341 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19342 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19343 /// Allows to ignore certain kinds of highlights.
19344 pub fn highlighted_display_rows(
19345 &self,
19346 window: &mut Window,
19347 cx: &mut App,
19348 ) -> BTreeMap<DisplayRow, LineHighlight> {
19349 let snapshot = self.snapshot(window, cx);
19350 let mut used_highlight_orders = HashMap::default();
19351 self.highlighted_rows
19352 .iter()
19353 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19354 .fold(
19355 BTreeMap::<DisplayRow, LineHighlight>::new(),
19356 |mut unique_rows, highlight| {
19357 let start = highlight.range.start.to_display_point(&snapshot);
19358 let end = highlight.range.end.to_display_point(&snapshot);
19359 let start_row = start.row().0;
19360 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19361 && end.column() == 0
19362 {
19363 end.row().0.saturating_sub(1)
19364 } else {
19365 end.row().0
19366 };
19367 for row in start_row..=end_row {
19368 let used_index =
19369 used_highlight_orders.entry(row).or_insert(highlight.index);
19370 if highlight.index >= *used_index {
19371 *used_index = highlight.index;
19372 unique_rows.insert(
19373 DisplayRow(row),
19374 LineHighlight {
19375 include_gutter: highlight.options.include_gutter,
19376 border: None,
19377 background: highlight.color.into(),
19378 type_id: Some(highlight.type_id),
19379 },
19380 );
19381 }
19382 }
19383 unique_rows
19384 },
19385 )
19386 }
19387
19388 pub fn highlighted_display_row_for_autoscroll(
19389 &self,
19390 snapshot: &DisplaySnapshot,
19391 ) -> Option<DisplayRow> {
19392 self.highlighted_rows
19393 .values()
19394 .flat_map(|highlighted_rows| highlighted_rows.iter())
19395 .filter_map(|highlight| {
19396 if highlight.options.autoscroll {
19397 Some(highlight.range.start.to_display_point(snapshot).row())
19398 } else {
19399 None
19400 }
19401 })
19402 .min()
19403 }
19404
19405 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19406 self.highlight_background::<SearchWithinRange>(
19407 ranges,
19408 |colors| colors.colors().editor_document_highlight_read_background,
19409 cx,
19410 )
19411 }
19412
19413 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19414 self.breadcrumb_header = Some(new_header);
19415 }
19416
19417 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19418 self.clear_background_highlights::<SearchWithinRange>(cx);
19419 }
19420
19421 pub fn highlight_background<T: 'static>(
19422 &mut self,
19423 ranges: &[Range<Anchor>],
19424 color_fetcher: fn(&Theme) -> Hsla,
19425 cx: &mut Context<Self>,
19426 ) {
19427 self.background_highlights.insert(
19428 HighlightKey::Type(TypeId::of::<T>()),
19429 (color_fetcher, Arc::from(ranges)),
19430 );
19431 self.scrollbar_marker_state.dirty = true;
19432 cx.notify();
19433 }
19434
19435 pub fn highlight_background_key<T: 'static>(
19436 &mut self,
19437 key: usize,
19438 ranges: &[Range<Anchor>],
19439 color_fetcher: fn(&Theme) -> Hsla,
19440 cx: &mut Context<Self>,
19441 ) {
19442 self.background_highlights.insert(
19443 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19444 (color_fetcher, Arc::from(ranges)),
19445 );
19446 self.scrollbar_marker_state.dirty = true;
19447 cx.notify();
19448 }
19449
19450 pub fn clear_background_highlights<T: 'static>(
19451 &mut self,
19452 cx: &mut Context<Self>,
19453 ) -> Option<BackgroundHighlight> {
19454 let text_highlights = self
19455 .background_highlights
19456 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19457 if !text_highlights.1.is_empty() {
19458 self.scrollbar_marker_state.dirty = true;
19459 cx.notify();
19460 }
19461 Some(text_highlights)
19462 }
19463
19464 pub fn highlight_gutter<T: 'static>(
19465 &mut self,
19466 ranges: impl Into<Vec<Range<Anchor>>>,
19467 color_fetcher: fn(&App) -> Hsla,
19468 cx: &mut Context<Self>,
19469 ) {
19470 self.gutter_highlights
19471 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19472 cx.notify();
19473 }
19474
19475 pub fn clear_gutter_highlights<T: 'static>(
19476 &mut self,
19477 cx: &mut Context<Self>,
19478 ) -> Option<GutterHighlight> {
19479 cx.notify();
19480 self.gutter_highlights.remove(&TypeId::of::<T>())
19481 }
19482
19483 pub fn insert_gutter_highlight<T: 'static>(
19484 &mut self,
19485 range: Range<Anchor>,
19486 color_fetcher: fn(&App) -> Hsla,
19487 cx: &mut Context<Self>,
19488 ) {
19489 let snapshot = self.buffer().read(cx).snapshot(cx);
19490 let mut highlights = self
19491 .gutter_highlights
19492 .remove(&TypeId::of::<T>())
19493 .map(|(_, highlights)| highlights)
19494 .unwrap_or_default();
19495 let ix = highlights.binary_search_by(|highlight| {
19496 Ordering::Equal
19497 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19498 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19499 });
19500 if let Err(ix) = ix {
19501 highlights.insert(ix, range);
19502 }
19503 self.gutter_highlights
19504 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19505 }
19506
19507 pub fn remove_gutter_highlights<T: 'static>(
19508 &mut self,
19509 ranges_to_remove: Vec<Range<Anchor>>,
19510 cx: &mut Context<Self>,
19511 ) {
19512 let snapshot = self.buffer().read(cx).snapshot(cx);
19513 let Some((color_fetcher, mut gutter_highlights)) =
19514 self.gutter_highlights.remove(&TypeId::of::<T>())
19515 else {
19516 return;
19517 };
19518 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19519 gutter_highlights.retain(|highlight| {
19520 while let Some(range_to_remove) = ranges_to_remove.peek() {
19521 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19522 Ordering::Less | Ordering::Equal => {
19523 ranges_to_remove.next();
19524 }
19525 Ordering::Greater => {
19526 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19527 Ordering::Less | Ordering::Equal => {
19528 return false;
19529 }
19530 Ordering::Greater => break,
19531 }
19532 }
19533 }
19534 }
19535
19536 true
19537 });
19538 self.gutter_highlights
19539 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19540 }
19541
19542 #[cfg(feature = "test-support")]
19543 pub fn all_text_highlights(
19544 &self,
19545 window: &mut Window,
19546 cx: &mut Context<Self>,
19547 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19548 let snapshot = self.snapshot(window, cx);
19549 self.display_map.update(cx, |display_map, _| {
19550 display_map
19551 .all_text_highlights()
19552 .map(|highlight| {
19553 let (style, ranges) = highlight.as_ref();
19554 (
19555 *style,
19556 ranges
19557 .iter()
19558 .map(|range| range.clone().to_display_points(&snapshot))
19559 .collect(),
19560 )
19561 })
19562 .collect()
19563 })
19564 }
19565
19566 #[cfg(feature = "test-support")]
19567 pub fn all_text_background_highlights(
19568 &self,
19569 window: &mut Window,
19570 cx: &mut Context<Self>,
19571 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19572 let snapshot = self.snapshot(window, cx);
19573 let buffer = &snapshot.buffer_snapshot;
19574 let start = buffer.anchor_before(0);
19575 let end = buffer.anchor_after(buffer.len());
19576 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19577 }
19578
19579 #[cfg(feature = "test-support")]
19580 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19581 let snapshot = self.buffer().read(cx).snapshot(cx);
19582
19583 let highlights = self
19584 .background_highlights
19585 .get(&HighlightKey::Type(TypeId::of::<
19586 items::BufferSearchHighlights,
19587 >()));
19588
19589 if let Some((_color, ranges)) = highlights {
19590 ranges
19591 .iter()
19592 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19593 .collect_vec()
19594 } else {
19595 vec![]
19596 }
19597 }
19598
19599 fn document_highlights_for_position<'a>(
19600 &'a self,
19601 position: Anchor,
19602 buffer: &'a MultiBufferSnapshot,
19603 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19604 let read_highlights = self
19605 .background_highlights
19606 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19607 .map(|h| &h.1);
19608 let write_highlights = self
19609 .background_highlights
19610 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19611 .map(|h| &h.1);
19612 let left_position = position.bias_left(buffer);
19613 let right_position = position.bias_right(buffer);
19614 read_highlights
19615 .into_iter()
19616 .chain(write_highlights)
19617 .flat_map(move |ranges| {
19618 let start_ix = match ranges.binary_search_by(|probe| {
19619 let cmp = probe.end.cmp(&left_position, buffer);
19620 if cmp.is_ge() {
19621 Ordering::Greater
19622 } else {
19623 Ordering::Less
19624 }
19625 }) {
19626 Ok(i) | Err(i) => i,
19627 };
19628
19629 ranges[start_ix..]
19630 .iter()
19631 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19632 })
19633 }
19634
19635 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19636 self.background_highlights
19637 .get(&HighlightKey::Type(TypeId::of::<T>()))
19638 .map_or(false, |(_, highlights)| !highlights.is_empty())
19639 }
19640
19641 pub fn background_highlights_in_range(
19642 &self,
19643 search_range: Range<Anchor>,
19644 display_snapshot: &DisplaySnapshot,
19645 theme: &Theme,
19646 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19647 let mut results = Vec::new();
19648 for (color_fetcher, ranges) in self.background_highlights.values() {
19649 let color = color_fetcher(theme);
19650 let start_ix = match ranges.binary_search_by(|probe| {
19651 let cmp = probe
19652 .end
19653 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19654 if cmp.is_gt() {
19655 Ordering::Greater
19656 } else {
19657 Ordering::Less
19658 }
19659 }) {
19660 Ok(i) | Err(i) => i,
19661 };
19662 for range in &ranges[start_ix..] {
19663 if range
19664 .start
19665 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19666 .is_ge()
19667 {
19668 break;
19669 }
19670
19671 let start = range.start.to_display_point(display_snapshot);
19672 let end = range.end.to_display_point(display_snapshot);
19673 results.push((start..end, color))
19674 }
19675 }
19676 results
19677 }
19678
19679 pub fn background_highlight_row_ranges<T: 'static>(
19680 &self,
19681 search_range: Range<Anchor>,
19682 display_snapshot: &DisplaySnapshot,
19683 count: usize,
19684 ) -> Vec<RangeInclusive<DisplayPoint>> {
19685 let mut results = Vec::new();
19686 let Some((_, ranges)) = self
19687 .background_highlights
19688 .get(&HighlightKey::Type(TypeId::of::<T>()))
19689 else {
19690 return vec![];
19691 };
19692
19693 let start_ix = match ranges.binary_search_by(|probe| {
19694 let cmp = probe
19695 .end
19696 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19697 if cmp.is_gt() {
19698 Ordering::Greater
19699 } else {
19700 Ordering::Less
19701 }
19702 }) {
19703 Ok(i) | Err(i) => i,
19704 };
19705 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19706 if let (Some(start_display), Some(end_display)) = (start, end) {
19707 results.push(
19708 start_display.to_display_point(display_snapshot)
19709 ..=end_display.to_display_point(display_snapshot),
19710 );
19711 }
19712 };
19713 let mut start_row: Option<Point> = None;
19714 let mut end_row: Option<Point> = None;
19715 if ranges.len() > count {
19716 return Vec::new();
19717 }
19718 for range in &ranges[start_ix..] {
19719 if range
19720 .start
19721 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19722 .is_ge()
19723 {
19724 break;
19725 }
19726 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19727 if let Some(current_row) = &end_row {
19728 if end.row == current_row.row {
19729 continue;
19730 }
19731 }
19732 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19733 if start_row.is_none() {
19734 assert_eq!(end_row, None);
19735 start_row = Some(start);
19736 end_row = Some(end);
19737 continue;
19738 }
19739 if let Some(current_end) = end_row.as_mut() {
19740 if start.row > current_end.row + 1 {
19741 push_region(start_row, end_row);
19742 start_row = Some(start);
19743 end_row = Some(end);
19744 } else {
19745 // Merge two hunks.
19746 *current_end = end;
19747 }
19748 } else {
19749 unreachable!();
19750 }
19751 }
19752 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19753 push_region(start_row, end_row);
19754 results
19755 }
19756
19757 pub fn gutter_highlights_in_range(
19758 &self,
19759 search_range: Range<Anchor>,
19760 display_snapshot: &DisplaySnapshot,
19761 cx: &App,
19762 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19763 let mut results = Vec::new();
19764 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19765 let color = color_fetcher(cx);
19766 let start_ix = match ranges.binary_search_by(|probe| {
19767 let cmp = probe
19768 .end
19769 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19770 if cmp.is_gt() {
19771 Ordering::Greater
19772 } else {
19773 Ordering::Less
19774 }
19775 }) {
19776 Ok(i) | Err(i) => i,
19777 };
19778 for range in &ranges[start_ix..] {
19779 if range
19780 .start
19781 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19782 .is_ge()
19783 {
19784 break;
19785 }
19786
19787 let start = range.start.to_display_point(display_snapshot);
19788 let end = range.end.to_display_point(display_snapshot);
19789 results.push((start..end, color))
19790 }
19791 }
19792 results
19793 }
19794
19795 /// Get the text ranges corresponding to the redaction query
19796 pub fn redacted_ranges(
19797 &self,
19798 search_range: Range<Anchor>,
19799 display_snapshot: &DisplaySnapshot,
19800 cx: &App,
19801 ) -> Vec<Range<DisplayPoint>> {
19802 display_snapshot
19803 .buffer_snapshot
19804 .redacted_ranges(search_range, |file| {
19805 if let Some(file) = file {
19806 file.is_private()
19807 && EditorSettings::get(
19808 Some(SettingsLocation {
19809 worktree_id: file.worktree_id(cx),
19810 path: file.path().as_ref(),
19811 }),
19812 cx,
19813 )
19814 .redact_private_values
19815 } else {
19816 false
19817 }
19818 })
19819 .map(|range| {
19820 range.start.to_display_point(display_snapshot)
19821 ..range.end.to_display_point(display_snapshot)
19822 })
19823 .collect()
19824 }
19825
19826 pub fn highlight_text_key<T: 'static>(
19827 &mut self,
19828 key: usize,
19829 ranges: Vec<Range<Anchor>>,
19830 style: HighlightStyle,
19831 cx: &mut Context<Self>,
19832 ) {
19833 self.display_map.update(cx, |map, _| {
19834 map.highlight_text(
19835 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19836 ranges,
19837 style,
19838 );
19839 });
19840 cx.notify();
19841 }
19842
19843 pub fn highlight_text<T: 'static>(
19844 &mut self,
19845 ranges: Vec<Range<Anchor>>,
19846 style: HighlightStyle,
19847 cx: &mut Context<Self>,
19848 ) {
19849 self.display_map.update(cx, |map, _| {
19850 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19851 });
19852 cx.notify();
19853 }
19854
19855 pub(crate) fn highlight_inlays<T: 'static>(
19856 &mut self,
19857 highlights: Vec<InlayHighlight>,
19858 style: HighlightStyle,
19859 cx: &mut Context<Self>,
19860 ) {
19861 self.display_map.update(cx, |map, _| {
19862 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19863 });
19864 cx.notify();
19865 }
19866
19867 pub fn text_highlights<'a, T: 'static>(
19868 &'a self,
19869 cx: &'a App,
19870 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19871 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19872 }
19873
19874 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19875 let cleared = self
19876 .display_map
19877 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19878 if cleared {
19879 cx.notify();
19880 }
19881 }
19882
19883 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19884 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19885 && self.focus_handle.is_focused(window)
19886 }
19887
19888 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19889 self.show_cursor_when_unfocused = is_enabled;
19890 cx.notify();
19891 }
19892
19893 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19894 cx.notify();
19895 }
19896
19897 fn on_debug_session_event(
19898 &mut self,
19899 _session: Entity<Session>,
19900 event: &SessionEvent,
19901 cx: &mut Context<Self>,
19902 ) {
19903 match event {
19904 SessionEvent::InvalidateInlineValue => {
19905 self.refresh_inline_values(cx);
19906 }
19907 _ => {}
19908 }
19909 }
19910
19911 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19912 let Some(project) = self.project.clone() else {
19913 return;
19914 };
19915
19916 if !self.inline_value_cache.enabled {
19917 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19918 self.splice_inlays(&inlays, Vec::new(), cx);
19919 return;
19920 }
19921
19922 let current_execution_position = self
19923 .highlighted_rows
19924 .get(&TypeId::of::<ActiveDebugLine>())
19925 .and_then(|lines| lines.last().map(|line| line.range.end));
19926
19927 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19928 let inline_values = editor
19929 .update(cx, |editor, cx| {
19930 let Some(current_execution_position) = current_execution_position else {
19931 return Some(Task::ready(Ok(Vec::new())));
19932 };
19933
19934 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19935 let snapshot = buffer.snapshot(cx);
19936
19937 let excerpt = snapshot.excerpt_containing(
19938 current_execution_position..current_execution_position,
19939 )?;
19940
19941 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19942 })?;
19943
19944 let range =
19945 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19946
19947 project.inline_values(buffer, range, cx)
19948 })
19949 .ok()
19950 .flatten()?
19951 .await
19952 .context("refreshing debugger inlays")
19953 .log_err()?;
19954
19955 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19956
19957 for (buffer_id, inline_value) in inline_values
19958 .into_iter()
19959 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19960 {
19961 buffer_inline_values
19962 .entry(buffer_id)
19963 .or_default()
19964 .push(inline_value);
19965 }
19966
19967 editor
19968 .update(cx, |editor, cx| {
19969 let snapshot = editor.buffer.read(cx).snapshot(cx);
19970 let mut new_inlays = Vec::default();
19971
19972 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19973 let buffer_id = buffer_snapshot.remote_id();
19974 buffer_inline_values
19975 .get(&buffer_id)
19976 .into_iter()
19977 .flatten()
19978 .for_each(|hint| {
19979 let inlay = Inlay::debugger(
19980 post_inc(&mut editor.next_inlay_id),
19981 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19982 hint.text(),
19983 );
19984 if !inlay.text.chars().contains(&'\n') {
19985 new_inlays.push(inlay);
19986 }
19987 });
19988 }
19989
19990 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19991 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19992
19993 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19994 })
19995 .ok()?;
19996 Some(())
19997 });
19998 }
19999
20000 fn on_buffer_event(
20001 &mut self,
20002 multibuffer: &Entity<MultiBuffer>,
20003 event: &multi_buffer::Event,
20004 window: &mut Window,
20005 cx: &mut Context<Self>,
20006 ) {
20007 match event {
20008 multi_buffer::Event::Edited {
20009 singleton_buffer_edited,
20010 edited_buffer,
20011 } => {
20012 self.scrollbar_marker_state.dirty = true;
20013 self.active_indent_guides_state.dirty = true;
20014 self.refresh_active_diagnostics(cx);
20015 self.refresh_code_actions(window, cx);
20016 self.refresh_selected_text_highlights(true, window, cx);
20017 self.refresh_single_line_folds(window, cx);
20018 refresh_matching_bracket_highlights(self, window, cx);
20019 if self.has_active_edit_prediction() {
20020 self.update_visible_edit_prediction(window, cx);
20021 }
20022 if let Some(project) = self.project.as_ref() {
20023 if let Some(edited_buffer) = edited_buffer {
20024 project.update(cx, |project, cx| {
20025 self.registered_buffers
20026 .entry(edited_buffer.read(cx).remote_id())
20027 .or_insert_with(|| {
20028 project
20029 .register_buffer_with_language_servers(&edited_buffer, cx)
20030 });
20031 });
20032 }
20033 }
20034 cx.emit(EditorEvent::BufferEdited);
20035 cx.emit(SearchEvent::MatchesInvalidated);
20036
20037 if let Some(buffer) = edited_buffer {
20038 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20039 }
20040
20041 if *singleton_buffer_edited {
20042 if let Some(buffer) = edited_buffer {
20043 if buffer.read(cx).file().is_none() {
20044 cx.emit(EditorEvent::TitleChanged);
20045 }
20046 }
20047 if let Some(project) = &self.project {
20048 #[allow(clippy::mutable_key_type)]
20049 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20050 multibuffer
20051 .all_buffers()
20052 .into_iter()
20053 .filter_map(|buffer| {
20054 buffer.update(cx, |buffer, cx| {
20055 let language = buffer.language()?;
20056 let should_discard = project.update(cx, |project, cx| {
20057 project.is_local()
20058 && !project.has_language_servers_for(buffer, cx)
20059 });
20060 should_discard.not().then_some(language.clone())
20061 })
20062 })
20063 .collect::<HashSet<_>>()
20064 });
20065 if !languages_affected.is_empty() {
20066 self.refresh_inlay_hints(
20067 InlayHintRefreshReason::BufferEdited(languages_affected),
20068 cx,
20069 );
20070 }
20071 }
20072 }
20073
20074 let Some(project) = &self.project else { return };
20075 let (telemetry, is_via_ssh) = {
20076 let project = project.read(cx);
20077 let telemetry = project.client().telemetry().clone();
20078 let is_via_ssh = project.is_via_ssh();
20079 (telemetry, is_via_ssh)
20080 };
20081 refresh_linked_ranges(self, window, cx);
20082 telemetry.log_edit_event("editor", is_via_ssh);
20083 }
20084 multi_buffer::Event::ExcerptsAdded {
20085 buffer,
20086 predecessor,
20087 excerpts,
20088 } => {
20089 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20090 let buffer_id = buffer.read(cx).remote_id();
20091 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
20092 if let Some(project) = &self.project {
20093 update_uncommitted_diff_for_buffer(
20094 cx.entity(),
20095 project,
20096 [buffer.clone()],
20097 self.buffer.clone(),
20098 cx,
20099 )
20100 .detach();
20101 }
20102 }
20103 self.update_lsp_data(false, Some(buffer_id), window, cx);
20104 cx.emit(EditorEvent::ExcerptsAdded {
20105 buffer: buffer.clone(),
20106 predecessor: *predecessor,
20107 excerpts: excerpts.clone(),
20108 });
20109 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20110 }
20111 multi_buffer::Event::ExcerptsRemoved {
20112 ids,
20113 removed_buffer_ids,
20114 } => {
20115 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20116 let buffer = self.buffer.read(cx);
20117 self.registered_buffers
20118 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20119 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20120 cx.emit(EditorEvent::ExcerptsRemoved {
20121 ids: ids.clone(),
20122 removed_buffer_ids: removed_buffer_ids.clone(),
20123 });
20124 }
20125 multi_buffer::Event::ExcerptsEdited {
20126 excerpt_ids,
20127 buffer_ids,
20128 } => {
20129 self.display_map.update(cx, |map, cx| {
20130 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20131 });
20132 cx.emit(EditorEvent::ExcerptsEdited {
20133 ids: excerpt_ids.clone(),
20134 });
20135 }
20136 multi_buffer::Event::ExcerptsExpanded { ids } => {
20137 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20138 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20139 }
20140 multi_buffer::Event::Reparsed(buffer_id) => {
20141 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20142 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20143
20144 cx.emit(EditorEvent::Reparsed(*buffer_id));
20145 }
20146 multi_buffer::Event::DiffHunksToggled => {
20147 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20148 }
20149 multi_buffer::Event::LanguageChanged(buffer_id) => {
20150 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20151 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20152 cx.emit(EditorEvent::Reparsed(*buffer_id));
20153 cx.notify();
20154 }
20155 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20156 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20157 multi_buffer::Event::FileHandleChanged
20158 | multi_buffer::Event::Reloaded
20159 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20160 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20161 multi_buffer::Event::DiagnosticsUpdated => {
20162 self.update_diagnostics_state(window, cx);
20163 }
20164 _ => {}
20165 };
20166 }
20167
20168 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20169 if !self.diagnostics_enabled() {
20170 return;
20171 }
20172 self.refresh_active_diagnostics(cx);
20173 self.refresh_inline_diagnostics(true, window, cx);
20174 self.scrollbar_marker_state.dirty = true;
20175 cx.notify();
20176 }
20177
20178 pub fn start_temporary_diff_override(&mut self) {
20179 self.load_diff_task.take();
20180 self.temporary_diff_override = true;
20181 }
20182
20183 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20184 self.temporary_diff_override = false;
20185 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20186 self.buffer.update(cx, |buffer, cx| {
20187 buffer.set_all_diff_hunks_collapsed(cx);
20188 });
20189
20190 if let Some(project) = self.project.clone() {
20191 self.load_diff_task = Some(
20192 update_uncommitted_diff_for_buffer(
20193 cx.entity(),
20194 &project,
20195 self.buffer.read(cx).all_buffers(),
20196 self.buffer.clone(),
20197 cx,
20198 )
20199 .shared(),
20200 );
20201 }
20202 }
20203
20204 fn on_display_map_changed(
20205 &mut self,
20206 _: Entity<DisplayMap>,
20207 _: &mut Window,
20208 cx: &mut Context<Self>,
20209 ) {
20210 cx.notify();
20211 }
20212
20213 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20214 if self.diagnostics_enabled() {
20215 let new_severity = EditorSettings::get_global(cx)
20216 .diagnostics_max_severity
20217 .unwrap_or(DiagnosticSeverity::Hint);
20218 self.set_max_diagnostics_severity(new_severity, cx);
20219 }
20220 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20221 self.update_edit_prediction_settings(cx);
20222 self.refresh_edit_prediction(true, false, window, cx);
20223 self.refresh_inline_values(cx);
20224 self.refresh_inlay_hints(
20225 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20226 self.selections.newest_anchor().head(),
20227 &self.buffer.read(cx).snapshot(cx),
20228 cx,
20229 )),
20230 cx,
20231 );
20232
20233 let old_cursor_shape = self.cursor_shape;
20234 let old_show_breadcrumbs = self.show_breadcrumbs;
20235
20236 {
20237 let editor_settings = EditorSettings::get_global(cx);
20238 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20239 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20240 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20241 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20242 }
20243
20244 if old_cursor_shape != self.cursor_shape {
20245 cx.emit(EditorEvent::CursorShapeChanged);
20246 }
20247
20248 if old_show_breadcrumbs != self.show_breadcrumbs {
20249 cx.emit(EditorEvent::BreadcrumbsChanged);
20250 }
20251
20252 let project_settings = ProjectSettings::get_global(cx);
20253 self.serialize_dirty_buffers =
20254 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20255
20256 if self.mode.is_full() {
20257 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20258 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20259 if self.show_inline_diagnostics != show_inline_diagnostics {
20260 self.show_inline_diagnostics = show_inline_diagnostics;
20261 self.refresh_inline_diagnostics(false, window, cx);
20262 }
20263
20264 if self.git_blame_inline_enabled != inline_blame_enabled {
20265 self.toggle_git_blame_inline_internal(false, window, cx);
20266 }
20267
20268 let minimap_settings = EditorSettings::get_global(cx).minimap;
20269 if self.minimap_visibility != MinimapVisibility::Disabled {
20270 if self.minimap_visibility.settings_visibility()
20271 != minimap_settings.minimap_enabled()
20272 {
20273 self.set_minimap_visibility(
20274 MinimapVisibility::for_mode(self.mode(), cx),
20275 window,
20276 cx,
20277 );
20278 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20279 minimap_entity.update(cx, |minimap_editor, cx| {
20280 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20281 })
20282 }
20283 }
20284 }
20285
20286 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20287 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20288 }) {
20289 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20290 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20291 }
20292 self.refresh_colors(false, None, window, cx);
20293 }
20294
20295 cx.notify();
20296 }
20297
20298 pub fn set_searchable(&mut self, searchable: bool) {
20299 self.searchable = searchable;
20300 }
20301
20302 pub fn searchable(&self) -> bool {
20303 self.searchable
20304 }
20305
20306 fn open_proposed_changes_editor(
20307 &mut self,
20308 _: &OpenProposedChangesEditor,
20309 window: &mut Window,
20310 cx: &mut Context<Self>,
20311 ) {
20312 let Some(workspace) = self.workspace() else {
20313 cx.propagate();
20314 return;
20315 };
20316
20317 let selections = self.selections.all::<usize>(cx);
20318 let multi_buffer = self.buffer.read(cx);
20319 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20320 let mut new_selections_by_buffer = HashMap::default();
20321 for selection in selections {
20322 for (buffer, range, _) in
20323 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20324 {
20325 let mut range = range.to_point(buffer);
20326 range.start.column = 0;
20327 range.end.column = buffer.line_len(range.end.row);
20328 new_selections_by_buffer
20329 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20330 .or_insert(Vec::new())
20331 .push(range)
20332 }
20333 }
20334
20335 let proposed_changes_buffers = new_selections_by_buffer
20336 .into_iter()
20337 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20338 .collect::<Vec<_>>();
20339 let proposed_changes_editor = cx.new(|cx| {
20340 ProposedChangesEditor::new(
20341 "Proposed changes",
20342 proposed_changes_buffers,
20343 self.project.clone(),
20344 window,
20345 cx,
20346 )
20347 });
20348
20349 window.defer(cx, move |window, cx| {
20350 workspace.update(cx, |workspace, cx| {
20351 workspace.active_pane().update(cx, |pane, cx| {
20352 pane.add_item(
20353 Box::new(proposed_changes_editor),
20354 true,
20355 true,
20356 None,
20357 window,
20358 cx,
20359 );
20360 });
20361 });
20362 });
20363 }
20364
20365 pub fn open_excerpts_in_split(
20366 &mut self,
20367 _: &OpenExcerptsSplit,
20368 window: &mut Window,
20369 cx: &mut Context<Self>,
20370 ) {
20371 self.open_excerpts_common(None, true, window, cx)
20372 }
20373
20374 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20375 self.open_excerpts_common(None, false, window, cx)
20376 }
20377
20378 fn open_excerpts_common(
20379 &mut self,
20380 jump_data: Option<JumpData>,
20381 split: bool,
20382 window: &mut Window,
20383 cx: &mut Context<Self>,
20384 ) {
20385 let Some(workspace) = self.workspace() else {
20386 cx.propagate();
20387 return;
20388 };
20389
20390 if self.buffer.read(cx).is_singleton() {
20391 cx.propagate();
20392 return;
20393 }
20394
20395 let mut new_selections_by_buffer = HashMap::default();
20396 match &jump_data {
20397 Some(JumpData::MultiBufferPoint {
20398 excerpt_id,
20399 position,
20400 anchor,
20401 line_offset_from_top,
20402 }) => {
20403 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20404 if let Some(buffer) = multi_buffer_snapshot
20405 .buffer_id_for_excerpt(*excerpt_id)
20406 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20407 {
20408 let buffer_snapshot = buffer.read(cx).snapshot();
20409 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20410 language::ToPoint::to_point(anchor, &buffer_snapshot)
20411 } else {
20412 buffer_snapshot.clip_point(*position, Bias::Left)
20413 };
20414 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20415 new_selections_by_buffer.insert(
20416 buffer,
20417 (
20418 vec![jump_to_offset..jump_to_offset],
20419 Some(*line_offset_from_top),
20420 ),
20421 );
20422 }
20423 }
20424 Some(JumpData::MultiBufferRow {
20425 row,
20426 line_offset_from_top,
20427 }) => {
20428 let point = MultiBufferPoint::new(row.0, 0);
20429 if let Some((buffer, buffer_point, _)) =
20430 self.buffer.read(cx).point_to_buffer_point(point, cx)
20431 {
20432 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20433 new_selections_by_buffer
20434 .entry(buffer)
20435 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20436 .0
20437 .push(buffer_offset..buffer_offset)
20438 }
20439 }
20440 None => {
20441 let selections = self.selections.all::<usize>(cx);
20442 let multi_buffer = self.buffer.read(cx);
20443 for selection in selections {
20444 for (snapshot, range, _, anchor) in multi_buffer
20445 .snapshot(cx)
20446 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20447 {
20448 if let Some(anchor) = anchor {
20449 // selection is in a deleted hunk
20450 let Some(buffer_id) = anchor.buffer_id else {
20451 continue;
20452 };
20453 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20454 continue;
20455 };
20456 let offset = text::ToOffset::to_offset(
20457 &anchor.text_anchor,
20458 &buffer_handle.read(cx).snapshot(),
20459 );
20460 let range = offset..offset;
20461 new_selections_by_buffer
20462 .entry(buffer_handle)
20463 .or_insert((Vec::new(), None))
20464 .0
20465 .push(range)
20466 } else {
20467 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20468 else {
20469 continue;
20470 };
20471 new_selections_by_buffer
20472 .entry(buffer_handle)
20473 .or_insert((Vec::new(), None))
20474 .0
20475 .push(range)
20476 }
20477 }
20478 }
20479 }
20480 }
20481
20482 new_selections_by_buffer
20483 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20484
20485 if new_selections_by_buffer.is_empty() {
20486 return;
20487 }
20488
20489 // We defer the pane interaction because we ourselves are a workspace item
20490 // and activating a new item causes the pane to call a method on us reentrantly,
20491 // which panics if we're on the stack.
20492 window.defer(cx, move |window, cx| {
20493 workspace.update(cx, |workspace, cx| {
20494 let pane = if split {
20495 workspace.adjacent_pane(window, cx)
20496 } else {
20497 workspace.active_pane().clone()
20498 };
20499
20500 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20501 let editor = buffer
20502 .read(cx)
20503 .file()
20504 .is_none()
20505 .then(|| {
20506 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20507 // so `workspace.open_project_item` will never find them, always opening a new editor.
20508 // Instead, we try to activate the existing editor in the pane first.
20509 let (editor, pane_item_index) =
20510 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20511 let editor = item.downcast::<Editor>()?;
20512 let singleton_buffer =
20513 editor.read(cx).buffer().read(cx).as_singleton()?;
20514 if singleton_buffer == buffer {
20515 Some((editor, i))
20516 } else {
20517 None
20518 }
20519 })?;
20520 pane.update(cx, |pane, cx| {
20521 pane.activate_item(pane_item_index, true, true, window, cx)
20522 });
20523 Some(editor)
20524 })
20525 .flatten()
20526 .unwrap_or_else(|| {
20527 workspace.open_project_item::<Self>(
20528 pane.clone(),
20529 buffer,
20530 true,
20531 true,
20532 window,
20533 cx,
20534 )
20535 });
20536
20537 editor.update(cx, |editor, cx| {
20538 let autoscroll = match scroll_offset {
20539 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20540 None => Autoscroll::newest(),
20541 };
20542 let nav_history = editor.nav_history.take();
20543 editor.change_selections(
20544 SelectionEffects::scroll(autoscroll),
20545 window,
20546 cx,
20547 |s| {
20548 s.select_ranges(ranges);
20549 },
20550 );
20551 editor.nav_history = nav_history;
20552 });
20553 }
20554 })
20555 });
20556 }
20557
20558 // For now, don't allow opening excerpts in buffers that aren't backed by
20559 // regular project files.
20560 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20561 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20562 }
20563
20564 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20565 let snapshot = self.buffer.read(cx).read(cx);
20566 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20567 Some(
20568 ranges
20569 .iter()
20570 .map(move |range| {
20571 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20572 })
20573 .collect(),
20574 )
20575 }
20576
20577 fn selection_replacement_ranges(
20578 &self,
20579 range: Range<OffsetUtf16>,
20580 cx: &mut App,
20581 ) -> Vec<Range<OffsetUtf16>> {
20582 let selections = self.selections.all::<OffsetUtf16>(cx);
20583 let newest_selection = selections
20584 .iter()
20585 .max_by_key(|selection| selection.id)
20586 .unwrap();
20587 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20588 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20589 let snapshot = self.buffer.read(cx).read(cx);
20590 selections
20591 .into_iter()
20592 .map(|mut selection| {
20593 selection.start.0 =
20594 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20595 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20596 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20597 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20598 })
20599 .collect()
20600 }
20601
20602 fn report_editor_event(
20603 &self,
20604 reported_event: ReportEditorEvent,
20605 file_extension: Option<String>,
20606 cx: &App,
20607 ) {
20608 if cfg!(any(test, feature = "test-support")) {
20609 return;
20610 }
20611
20612 let Some(project) = &self.project else { return };
20613
20614 // If None, we are in a file without an extension
20615 let file = self
20616 .buffer
20617 .read(cx)
20618 .as_singleton()
20619 .and_then(|b| b.read(cx).file());
20620 let file_extension = file_extension.or(file
20621 .as_ref()
20622 .and_then(|file| Path::new(file.file_name(cx)).extension())
20623 .and_then(|e| e.to_str())
20624 .map(|a| a.to_string()));
20625
20626 let vim_mode = vim_enabled(cx);
20627
20628 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20629 let copilot_enabled = edit_predictions_provider
20630 == language::language_settings::EditPredictionProvider::Copilot;
20631 let copilot_enabled_for_language = self
20632 .buffer
20633 .read(cx)
20634 .language_settings(cx)
20635 .show_edit_predictions;
20636
20637 let project = project.read(cx);
20638 let event_type = reported_event.event_type();
20639
20640 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20641 telemetry::event!(
20642 event_type,
20643 type = if auto_saved {"autosave"} else {"manual"},
20644 file_extension,
20645 vim_mode,
20646 copilot_enabled,
20647 copilot_enabled_for_language,
20648 edit_predictions_provider,
20649 is_via_ssh = project.is_via_ssh(),
20650 );
20651 } else {
20652 telemetry::event!(
20653 event_type,
20654 file_extension,
20655 vim_mode,
20656 copilot_enabled,
20657 copilot_enabled_for_language,
20658 edit_predictions_provider,
20659 is_via_ssh = project.is_via_ssh(),
20660 );
20661 };
20662 }
20663
20664 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20665 /// with each line being an array of {text, highlight} objects.
20666 fn copy_highlight_json(
20667 &mut self,
20668 _: &CopyHighlightJson,
20669 window: &mut Window,
20670 cx: &mut Context<Self>,
20671 ) {
20672 #[derive(Serialize)]
20673 struct Chunk<'a> {
20674 text: String,
20675 highlight: Option<&'a str>,
20676 }
20677
20678 let snapshot = self.buffer.read(cx).snapshot(cx);
20679 let range = self
20680 .selected_text_range(false, window, cx)
20681 .and_then(|selection| {
20682 if selection.range.is_empty() {
20683 None
20684 } else {
20685 Some(selection.range)
20686 }
20687 })
20688 .unwrap_or_else(|| 0..snapshot.len());
20689
20690 let chunks = snapshot.chunks(range, true);
20691 let mut lines = Vec::new();
20692 let mut line: VecDeque<Chunk> = VecDeque::new();
20693
20694 let Some(style) = self.style.as_ref() else {
20695 return;
20696 };
20697
20698 for chunk in chunks {
20699 let highlight = chunk
20700 .syntax_highlight_id
20701 .and_then(|id| id.name(&style.syntax));
20702 let mut chunk_lines = chunk.text.split('\n').peekable();
20703 while let Some(text) = chunk_lines.next() {
20704 let mut merged_with_last_token = false;
20705 if let Some(last_token) = line.back_mut() {
20706 if last_token.highlight == highlight {
20707 last_token.text.push_str(text);
20708 merged_with_last_token = true;
20709 }
20710 }
20711
20712 if !merged_with_last_token {
20713 line.push_back(Chunk {
20714 text: text.into(),
20715 highlight,
20716 });
20717 }
20718
20719 if chunk_lines.peek().is_some() {
20720 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20721 line.pop_front();
20722 }
20723 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20724 line.pop_back();
20725 }
20726
20727 lines.push(mem::take(&mut line));
20728 }
20729 }
20730 }
20731
20732 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20733 return;
20734 };
20735 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20736 }
20737
20738 pub fn open_context_menu(
20739 &mut self,
20740 _: &OpenContextMenu,
20741 window: &mut Window,
20742 cx: &mut Context<Self>,
20743 ) {
20744 self.request_autoscroll(Autoscroll::newest(), cx);
20745 let position = self.selections.newest_display(cx).start;
20746 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20747 }
20748
20749 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20750 &self.inlay_hint_cache
20751 }
20752
20753 pub fn replay_insert_event(
20754 &mut self,
20755 text: &str,
20756 relative_utf16_range: Option<Range<isize>>,
20757 window: &mut Window,
20758 cx: &mut Context<Self>,
20759 ) {
20760 if !self.input_enabled {
20761 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20762 return;
20763 }
20764 if let Some(relative_utf16_range) = relative_utf16_range {
20765 let selections = self.selections.all::<OffsetUtf16>(cx);
20766 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20767 let new_ranges = selections.into_iter().map(|range| {
20768 let start = OffsetUtf16(
20769 range
20770 .head()
20771 .0
20772 .saturating_add_signed(relative_utf16_range.start),
20773 );
20774 let end = OffsetUtf16(
20775 range
20776 .head()
20777 .0
20778 .saturating_add_signed(relative_utf16_range.end),
20779 );
20780 start..end
20781 });
20782 s.select_ranges(new_ranges);
20783 });
20784 }
20785
20786 self.handle_input(text, window, cx);
20787 }
20788
20789 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20790 let Some(provider) = self.semantics_provider.as_ref() else {
20791 return false;
20792 };
20793
20794 let mut supports = false;
20795 self.buffer().update(cx, |this, cx| {
20796 this.for_each_buffer(|buffer| {
20797 supports |= provider.supports_inlay_hints(buffer, cx);
20798 });
20799 });
20800
20801 supports
20802 }
20803
20804 pub fn is_focused(&self, window: &Window) -> bool {
20805 self.focus_handle.is_focused(window)
20806 }
20807
20808 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20809 cx.emit(EditorEvent::Focused);
20810
20811 if let Some(descendant) = self
20812 .last_focused_descendant
20813 .take()
20814 .and_then(|descendant| descendant.upgrade())
20815 {
20816 window.focus(&descendant);
20817 } else {
20818 if let Some(blame) = self.blame.as_ref() {
20819 blame.update(cx, GitBlame::focus)
20820 }
20821
20822 self.blink_manager.update(cx, BlinkManager::enable);
20823 self.show_cursor_names(window, cx);
20824 self.buffer.update(cx, |buffer, cx| {
20825 buffer.finalize_last_transaction(cx);
20826 if self.leader_id.is_none() {
20827 buffer.set_active_selections(
20828 &self.selections.disjoint_anchors(),
20829 self.selections.line_mode,
20830 self.cursor_shape,
20831 cx,
20832 );
20833 }
20834 });
20835 }
20836 }
20837
20838 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20839 cx.emit(EditorEvent::FocusedIn)
20840 }
20841
20842 fn handle_focus_out(
20843 &mut self,
20844 event: FocusOutEvent,
20845 _window: &mut Window,
20846 cx: &mut Context<Self>,
20847 ) {
20848 if event.blurred != self.focus_handle {
20849 self.last_focused_descendant = Some(event.blurred);
20850 }
20851 self.selection_drag_state = SelectionDragState::None;
20852 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20853 }
20854
20855 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20856 self.blink_manager.update(cx, BlinkManager::disable);
20857 self.buffer
20858 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20859
20860 if let Some(blame) = self.blame.as_ref() {
20861 blame.update(cx, GitBlame::blur)
20862 }
20863 if !self.hover_state.focused(window, cx) {
20864 hide_hover(self, cx);
20865 }
20866 if !self
20867 .context_menu
20868 .borrow()
20869 .as_ref()
20870 .is_some_and(|context_menu| context_menu.focused(window, cx))
20871 {
20872 self.hide_context_menu(window, cx);
20873 }
20874 self.discard_edit_prediction(false, cx);
20875 cx.emit(EditorEvent::Blurred);
20876 cx.notify();
20877 }
20878
20879 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20880 let mut pending: String = window
20881 .pending_input_keystrokes()
20882 .into_iter()
20883 .flatten()
20884 .filter_map(|keystroke| {
20885 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20886 keystroke.key_char.clone()
20887 } else {
20888 None
20889 }
20890 })
20891 .collect();
20892
20893 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20894 pending = "".to_string();
20895 }
20896
20897 let existing_pending = self
20898 .text_highlights::<PendingInput>(cx)
20899 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20900 if existing_pending.is_none() && pending.is_empty() {
20901 return;
20902 }
20903 let transaction =
20904 self.transact(window, cx, |this, window, cx| {
20905 let selections = this.selections.all::<usize>(cx);
20906 let edits = selections
20907 .iter()
20908 .map(|selection| (selection.end..selection.end, pending.clone()));
20909 this.edit(edits, cx);
20910 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20911 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20912 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20913 }));
20914 });
20915 if let Some(existing_ranges) = existing_pending {
20916 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20917 this.edit(edits, cx);
20918 }
20919 });
20920
20921 let snapshot = self.snapshot(window, cx);
20922 let ranges = self
20923 .selections
20924 .all::<usize>(cx)
20925 .into_iter()
20926 .map(|selection| {
20927 snapshot.buffer_snapshot.anchor_after(selection.end)
20928 ..snapshot
20929 .buffer_snapshot
20930 .anchor_before(selection.end + pending.len())
20931 })
20932 .collect();
20933
20934 if pending.is_empty() {
20935 self.clear_highlights::<PendingInput>(cx);
20936 } else {
20937 self.highlight_text::<PendingInput>(
20938 ranges,
20939 HighlightStyle {
20940 underline: Some(UnderlineStyle {
20941 thickness: px(1.),
20942 color: None,
20943 wavy: false,
20944 }),
20945 ..Default::default()
20946 },
20947 cx,
20948 );
20949 }
20950
20951 self.ime_transaction = self.ime_transaction.or(transaction);
20952 if let Some(transaction) = self.ime_transaction {
20953 self.buffer.update(cx, |buffer, cx| {
20954 buffer.group_until_transaction(transaction, cx);
20955 });
20956 }
20957
20958 if self.text_highlights::<PendingInput>(cx).is_none() {
20959 self.ime_transaction.take();
20960 }
20961 }
20962
20963 pub fn register_action_renderer(
20964 &mut self,
20965 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20966 ) -> Subscription {
20967 let id = self.next_editor_action_id.post_inc();
20968 self.editor_actions
20969 .borrow_mut()
20970 .insert(id, Box::new(listener));
20971
20972 let editor_actions = self.editor_actions.clone();
20973 Subscription::new(move || {
20974 editor_actions.borrow_mut().remove(&id);
20975 })
20976 }
20977
20978 pub fn register_action<A: Action>(
20979 &mut self,
20980 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20981 ) -> Subscription {
20982 let id = self.next_editor_action_id.post_inc();
20983 let listener = Arc::new(listener);
20984 self.editor_actions.borrow_mut().insert(
20985 id,
20986 Box::new(move |_, window, _| {
20987 let listener = listener.clone();
20988 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20989 let action = action.downcast_ref().unwrap();
20990 if phase == DispatchPhase::Bubble {
20991 listener(action, window, cx)
20992 }
20993 })
20994 }),
20995 );
20996
20997 let editor_actions = self.editor_actions.clone();
20998 Subscription::new(move || {
20999 editor_actions.borrow_mut().remove(&id);
21000 })
21001 }
21002
21003 pub fn file_header_size(&self) -> u32 {
21004 FILE_HEADER_HEIGHT
21005 }
21006
21007 pub fn restore(
21008 &mut self,
21009 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21010 window: &mut Window,
21011 cx: &mut Context<Self>,
21012 ) {
21013 let workspace = self.workspace();
21014 let project = self.project.as_ref();
21015 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21016 let mut tasks = Vec::new();
21017 for (buffer_id, changes) in revert_changes {
21018 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21019 buffer.update(cx, |buffer, cx| {
21020 buffer.edit(
21021 changes
21022 .into_iter()
21023 .map(|(range, text)| (range, text.to_string())),
21024 None,
21025 cx,
21026 );
21027 });
21028
21029 if let Some(project) =
21030 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21031 {
21032 project.update(cx, |project, cx| {
21033 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21034 })
21035 }
21036 }
21037 }
21038 tasks
21039 });
21040 cx.spawn_in(window, async move |_, cx| {
21041 for (buffer, task) in save_tasks {
21042 let result = task.await;
21043 if result.is_err() {
21044 let Some(path) = buffer
21045 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21046 .ok()
21047 else {
21048 continue;
21049 };
21050 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21051 let Some(task) = cx
21052 .update_window_entity(&workspace, |workspace, window, cx| {
21053 workspace
21054 .open_path_preview(path, None, false, false, false, window, cx)
21055 })
21056 .ok()
21057 else {
21058 continue;
21059 };
21060 task.await.log_err();
21061 }
21062 }
21063 }
21064 })
21065 .detach();
21066 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21067 selections.refresh()
21068 });
21069 }
21070
21071 pub fn to_pixel_point(
21072 &self,
21073 source: multi_buffer::Anchor,
21074 editor_snapshot: &EditorSnapshot,
21075 window: &mut Window,
21076 ) -> Option<gpui::Point<Pixels>> {
21077 let source_point = source.to_display_point(editor_snapshot);
21078 self.display_to_pixel_point(source_point, editor_snapshot, window)
21079 }
21080
21081 pub fn display_to_pixel_point(
21082 &self,
21083 source: DisplayPoint,
21084 editor_snapshot: &EditorSnapshot,
21085 window: &mut Window,
21086 ) -> Option<gpui::Point<Pixels>> {
21087 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21088 let text_layout_details = self.text_layout_details(window);
21089 let scroll_top = text_layout_details
21090 .scroll_anchor
21091 .scroll_position(editor_snapshot)
21092 .y;
21093
21094 if source.row().as_f32() < scroll_top.floor() {
21095 return None;
21096 }
21097 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21098 let source_y = line_height * (source.row().as_f32() - scroll_top);
21099 Some(gpui::Point::new(source_x, source_y))
21100 }
21101
21102 pub fn has_visible_completions_menu(&self) -> bool {
21103 !self.edit_prediction_preview_is_active()
21104 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
21105 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21106 })
21107 }
21108
21109 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21110 if self.mode.is_minimap() {
21111 return;
21112 }
21113 self.addons
21114 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21115 }
21116
21117 pub fn unregister_addon<T: Addon>(&mut self) {
21118 self.addons.remove(&std::any::TypeId::of::<T>());
21119 }
21120
21121 pub fn addon<T: Addon>(&self) -> Option<&T> {
21122 let type_id = std::any::TypeId::of::<T>();
21123 self.addons
21124 .get(&type_id)
21125 .and_then(|item| item.to_any().downcast_ref::<T>())
21126 }
21127
21128 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21129 let type_id = std::any::TypeId::of::<T>();
21130 self.addons
21131 .get_mut(&type_id)
21132 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21133 }
21134
21135 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21136 let text_layout_details = self.text_layout_details(window);
21137 let style = &text_layout_details.editor_style;
21138 let font_id = window.text_system().resolve_font(&style.text.font());
21139 let font_size = style.text.font_size.to_pixels(window.rem_size());
21140 let line_height = style.text.line_height_in_pixels(window.rem_size());
21141 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21142 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21143
21144 CharacterDimensions {
21145 em_width,
21146 em_advance,
21147 line_height,
21148 }
21149 }
21150
21151 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21152 self.load_diff_task.clone()
21153 }
21154
21155 fn read_metadata_from_db(
21156 &mut self,
21157 item_id: u64,
21158 workspace_id: WorkspaceId,
21159 window: &mut Window,
21160 cx: &mut Context<Editor>,
21161 ) {
21162 if self.is_singleton(cx)
21163 && !self.mode.is_minimap()
21164 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21165 {
21166 let buffer_snapshot = OnceCell::new();
21167
21168 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21169 if !folds.is_empty() {
21170 let snapshot =
21171 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21172 self.fold_ranges(
21173 folds
21174 .into_iter()
21175 .map(|(start, end)| {
21176 snapshot.clip_offset(start, Bias::Left)
21177 ..snapshot.clip_offset(end, Bias::Right)
21178 })
21179 .collect(),
21180 false,
21181 window,
21182 cx,
21183 );
21184 }
21185 }
21186
21187 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21188 if !selections.is_empty() {
21189 let snapshot =
21190 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21191 // skip adding the initial selection to selection history
21192 self.selection_history.mode = SelectionHistoryMode::Skipping;
21193 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21194 s.select_ranges(selections.into_iter().map(|(start, end)| {
21195 snapshot.clip_offset(start, Bias::Left)
21196 ..snapshot.clip_offset(end, Bias::Right)
21197 }));
21198 });
21199 self.selection_history.mode = SelectionHistoryMode::Normal;
21200 }
21201 };
21202 }
21203
21204 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21205 }
21206
21207 fn update_lsp_data(
21208 &mut self,
21209 ignore_cache: bool,
21210 for_buffer: Option<BufferId>,
21211 window: &mut Window,
21212 cx: &mut Context<'_, Self>,
21213 ) {
21214 self.pull_diagnostics(for_buffer, window, cx);
21215 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21216 }
21217}
21218
21219fn vim_enabled(cx: &App) -> bool {
21220 cx.global::<SettingsStore>()
21221 .raw_user_settings()
21222 .get("vim_mode")
21223 == Some(&serde_json::Value::Bool(true))
21224}
21225
21226fn process_completion_for_edit(
21227 completion: &Completion,
21228 intent: CompletionIntent,
21229 buffer: &Entity<Buffer>,
21230 cursor_position: &text::Anchor,
21231 cx: &mut Context<Editor>,
21232) -> CompletionEdit {
21233 let buffer = buffer.read(cx);
21234 let buffer_snapshot = buffer.snapshot();
21235 let (snippet, new_text) = if completion.is_snippet() {
21236 // Workaround for typescript language server issues so that methods don't expand within
21237 // strings and functions with type expressions. The previous point is used because the query
21238 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21239 let mut snippet_source = completion.new_text.clone();
21240 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21241 previous_point.column = previous_point.column.saturating_sub(1);
21242 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21243 if scope.prefers_label_for_snippet_in_completion() {
21244 if let Some(label) = completion.label() {
21245 if matches!(
21246 completion.kind(),
21247 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21248 ) {
21249 snippet_source = label;
21250 }
21251 }
21252 }
21253 }
21254 match Snippet::parse(&snippet_source).log_err() {
21255 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21256 None => (None, completion.new_text.clone()),
21257 }
21258 } else {
21259 (None, completion.new_text.clone())
21260 };
21261
21262 let mut range_to_replace = {
21263 let replace_range = &completion.replace_range;
21264 if let CompletionSource::Lsp {
21265 insert_range: Some(insert_range),
21266 ..
21267 } = &completion.source
21268 {
21269 debug_assert_eq!(
21270 insert_range.start, replace_range.start,
21271 "insert_range and replace_range should start at the same position"
21272 );
21273 debug_assert!(
21274 insert_range
21275 .start
21276 .cmp(&cursor_position, &buffer_snapshot)
21277 .is_le(),
21278 "insert_range should start before or at cursor position"
21279 );
21280 debug_assert!(
21281 replace_range
21282 .start
21283 .cmp(&cursor_position, &buffer_snapshot)
21284 .is_le(),
21285 "replace_range should start before or at cursor position"
21286 );
21287
21288 let should_replace = match intent {
21289 CompletionIntent::CompleteWithInsert => false,
21290 CompletionIntent::CompleteWithReplace => true,
21291 CompletionIntent::Complete | CompletionIntent::Compose => {
21292 let insert_mode =
21293 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21294 .completions
21295 .lsp_insert_mode;
21296 match insert_mode {
21297 LspInsertMode::Insert => false,
21298 LspInsertMode::Replace => true,
21299 LspInsertMode::ReplaceSubsequence => {
21300 let mut text_to_replace = buffer.chars_for_range(
21301 buffer.anchor_before(replace_range.start)
21302 ..buffer.anchor_after(replace_range.end),
21303 );
21304 let mut current_needle = text_to_replace.next();
21305 for haystack_ch in completion.label.text.chars() {
21306 if let Some(needle_ch) = current_needle {
21307 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21308 current_needle = text_to_replace.next();
21309 }
21310 }
21311 }
21312 current_needle.is_none()
21313 }
21314 LspInsertMode::ReplaceSuffix => {
21315 if replace_range
21316 .end
21317 .cmp(&cursor_position, &buffer_snapshot)
21318 .is_gt()
21319 {
21320 let range_after_cursor = *cursor_position..replace_range.end;
21321 let text_after_cursor = buffer
21322 .text_for_range(
21323 buffer.anchor_before(range_after_cursor.start)
21324 ..buffer.anchor_after(range_after_cursor.end),
21325 )
21326 .collect::<String>()
21327 .to_ascii_lowercase();
21328 completion
21329 .label
21330 .text
21331 .to_ascii_lowercase()
21332 .ends_with(&text_after_cursor)
21333 } else {
21334 true
21335 }
21336 }
21337 }
21338 }
21339 };
21340
21341 if should_replace {
21342 replace_range.clone()
21343 } else {
21344 insert_range.clone()
21345 }
21346 } else {
21347 replace_range.clone()
21348 }
21349 };
21350
21351 if range_to_replace
21352 .end
21353 .cmp(&cursor_position, &buffer_snapshot)
21354 .is_lt()
21355 {
21356 range_to_replace.end = *cursor_position;
21357 }
21358
21359 CompletionEdit {
21360 new_text,
21361 replace_range: range_to_replace.to_offset(&buffer),
21362 snippet,
21363 }
21364}
21365
21366struct CompletionEdit {
21367 new_text: String,
21368 replace_range: Range<usize>,
21369 snippet: Option<Snippet>,
21370}
21371
21372fn insert_extra_newline_brackets(
21373 buffer: &MultiBufferSnapshot,
21374 range: Range<usize>,
21375 language: &language::LanguageScope,
21376) -> bool {
21377 let leading_whitespace_len = buffer
21378 .reversed_chars_at(range.start)
21379 .take_while(|c| c.is_whitespace() && *c != '\n')
21380 .map(|c| c.len_utf8())
21381 .sum::<usize>();
21382 let trailing_whitespace_len = buffer
21383 .chars_at(range.end)
21384 .take_while(|c| c.is_whitespace() && *c != '\n')
21385 .map(|c| c.len_utf8())
21386 .sum::<usize>();
21387 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21388
21389 language.brackets().any(|(pair, enabled)| {
21390 let pair_start = pair.start.trim_end();
21391 let pair_end = pair.end.trim_start();
21392
21393 enabled
21394 && pair.newline
21395 && buffer.contains_str_at(range.end, pair_end)
21396 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21397 })
21398}
21399
21400fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21401 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21402 [(buffer, range, _)] => (*buffer, range.clone()),
21403 _ => return false,
21404 };
21405 let pair = {
21406 let mut result: Option<BracketMatch> = None;
21407
21408 for pair in buffer
21409 .all_bracket_ranges(range.clone())
21410 .filter(move |pair| {
21411 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21412 })
21413 {
21414 let len = pair.close_range.end - pair.open_range.start;
21415
21416 if let Some(existing) = &result {
21417 let existing_len = existing.close_range.end - existing.open_range.start;
21418 if len > existing_len {
21419 continue;
21420 }
21421 }
21422
21423 result = Some(pair);
21424 }
21425
21426 result
21427 };
21428 let Some(pair) = pair else {
21429 return false;
21430 };
21431 pair.newline_only
21432 && buffer
21433 .chars_for_range(pair.open_range.end..range.start)
21434 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21435 .all(|c| c.is_whitespace() && c != '\n')
21436}
21437
21438fn update_uncommitted_diff_for_buffer(
21439 editor: Entity<Editor>,
21440 project: &Entity<Project>,
21441 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21442 buffer: Entity<MultiBuffer>,
21443 cx: &mut App,
21444) -> Task<()> {
21445 let mut tasks = Vec::new();
21446 project.update(cx, |project, cx| {
21447 for buffer in buffers {
21448 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21449 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21450 }
21451 }
21452 });
21453 cx.spawn(async move |cx| {
21454 let diffs = future::join_all(tasks).await;
21455 if editor
21456 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21457 .unwrap_or(false)
21458 {
21459 return;
21460 }
21461
21462 buffer
21463 .update(cx, |buffer, cx| {
21464 for diff in diffs.into_iter().flatten() {
21465 buffer.add_diff(diff, cx);
21466 }
21467 })
21468 .ok();
21469 })
21470}
21471
21472fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21473 let tab_size = tab_size.get() as usize;
21474 let mut width = offset;
21475
21476 for ch in text.chars() {
21477 width += if ch == '\t' {
21478 tab_size - (width % tab_size)
21479 } else {
21480 1
21481 };
21482 }
21483
21484 width - offset
21485}
21486
21487#[cfg(test)]
21488mod tests {
21489 use super::*;
21490
21491 #[test]
21492 fn test_string_size_with_expanded_tabs() {
21493 let nz = |val| NonZeroU32::new(val).unwrap();
21494 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21495 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21496 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21497 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21498 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21499 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21500 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21501 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21502 }
21503}
21504
21505/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21506struct WordBreakingTokenizer<'a> {
21507 input: &'a str,
21508}
21509
21510impl<'a> WordBreakingTokenizer<'a> {
21511 fn new(input: &'a str) -> Self {
21512 Self { input }
21513 }
21514}
21515
21516fn is_char_ideographic(ch: char) -> bool {
21517 use unicode_script::Script::*;
21518 use unicode_script::UnicodeScript;
21519 matches!(ch.script(), Han | Tangut | Yi)
21520}
21521
21522fn is_grapheme_ideographic(text: &str) -> bool {
21523 text.chars().any(is_char_ideographic)
21524}
21525
21526fn is_grapheme_whitespace(text: &str) -> bool {
21527 text.chars().any(|x| x.is_whitespace())
21528}
21529
21530fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21531 text.chars().next().map_or(false, |ch| {
21532 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21533 })
21534}
21535
21536#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21537enum WordBreakToken<'a> {
21538 Word { token: &'a str, grapheme_len: usize },
21539 InlineWhitespace { token: &'a str, grapheme_len: usize },
21540 Newline,
21541}
21542
21543impl<'a> Iterator for WordBreakingTokenizer<'a> {
21544 /// Yields a span, the count of graphemes in the token, and whether it was
21545 /// whitespace. Note that it also breaks at word boundaries.
21546 type Item = WordBreakToken<'a>;
21547
21548 fn next(&mut self) -> Option<Self::Item> {
21549 use unicode_segmentation::UnicodeSegmentation;
21550 if self.input.is_empty() {
21551 return None;
21552 }
21553
21554 let mut iter = self.input.graphemes(true).peekable();
21555 let mut offset = 0;
21556 let mut grapheme_len = 0;
21557 if let Some(first_grapheme) = iter.next() {
21558 let is_newline = first_grapheme == "\n";
21559 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21560 offset += first_grapheme.len();
21561 grapheme_len += 1;
21562 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21563 if let Some(grapheme) = iter.peek().copied() {
21564 if should_stay_with_preceding_ideograph(grapheme) {
21565 offset += grapheme.len();
21566 grapheme_len += 1;
21567 }
21568 }
21569 } else {
21570 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21571 let mut next_word_bound = words.peek().copied();
21572 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21573 next_word_bound = words.next();
21574 }
21575 while let Some(grapheme) = iter.peek().copied() {
21576 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21577 break;
21578 };
21579 if is_grapheme_whitespace(grapheme) != is_whitespace
21580 || (grapheme == "\n") != is_newline
21581 {
21582 break;
21583 };
21584 offset += grapheme.len();
21585 grapheme_len += 1;
21586 iter.next();
21587 }
21588 }
21589 let token = &self.input[..offset];
21590 self.input = &self.input[offset..];
21591 if token == "\n" {
21592 Some(WordBreakToken::Newline)
21593 } else if is_whitespace {
21594 Some(WordBreakToken::InlineWhitespace {
21595 token,
21596 grapheme_len,
21597 })
21598 } else {
21599 Some(WordBreakToken::Word {
21600 token,
21601 grapheme_len,
21602 })
21603 }
21604 } else {
21605 None
21606 }
21607 }
21608}
21609
21610#[test]
21611fn test_word_breaking_tokenizer() {
21612 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21613 ("", &[]),
21614 (" ", &[whitespace(" ", 2)]),
21615 ("Ʒ", &[word("Ʒ", 1)]),
21616 ("Ǽ", &[word("Ǽ", 1)]),
21617 ("⋑", &[word("⋑", 1)]),
21618 ("⋑⋑", &[word("⋑⋑", 2)]),
21619 (
21620 "原理,进而",
21621 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21622 ),
21623 (
21624 "hello world",
21625 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21626 ),
21627 (
21628 "hello, world",
21629 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21630 ),
21631 (
21632 " hello world",
21633 &[
21634 whitespace(" ", 2),
21635 word("hello", 5),
21636 whitespace(" ", 1),
21637 word("world", 5),
21638 ],
21639 ),
21640 (
21641 "这是什么 \n 钢笔",
21642 &[
21643 word("这", 1),
21644 word("是", 1),
21645 word("什", 1),
21646 word("么", 1),
21647 whitespace(" ", 1),
21648 newline(),
21649 whitespace(" ", 1),
21650 word("钢", 1),
21651 word("笔", 1),
21652 ],
21653 ),
21654 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21655 ];
21656
21657 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21658 WordBreakToken::Word {
21659 token,
21660 grapheme_len,
21661 }
21662 }
21663
21664 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21665 WordBreakToken::InlineWhitespace {
21666 token,
21667 grapheme_len,
21668 }
21669 }
21670
21671 fn newline() -> WordBreakToken<'static> {
21672 WordBreakToken::Newline
21673 }
21674
21675 for (input, result) in tests {
21676 assert_eq!(
21677 WordBreakingTokenizer::new(input)
21678 .collect::<Vec<_>>()
21679 .as_slice(),
21680 *result,
21681 );
21682 }
21683}
21684
21685fn wrap_with_prefix(
21686 first_line_prefix: String,
21687 subsequent_lines_prefix: String,
21688 unwrapped_text: String,
21689 wrap_column: usize,
21690 tab_size: NonZeroU32,
21691 preserve_existing_whitespace: bool,
21692) -> String {
21693 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21694 let subsequent_lines_prefix_len =
21695 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21696 let mut wrapped_text = String::new();
21697 let mut current_line = first_line_prefix.clone();
21698 let mut is_first_line = true;
21699
21700 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21701 let mut current_line_len = first_line_prefix_len;
21702 let mut in_whitespace = false;
21703 for token in tokenizer {
21704 let have_preceding_whitespace = in_whitespace;
21705 match token {
21706 WordBreakToken::Word {
21707 token,
21708 grapheme_len,
21709 } => {
21710 in_whitespace = false;
21711 let current_prefix_len = if is_first_line {
21712 first_line_prefix_len
21713 } else {
21714 subsequent_lines_prefix_len
21715 };
21716 if current_line_len + grapheme_len > wrap_column
21717 && current_line_len != current_prefix_len
21718 {
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 }
21725 current_line.push_str(token);
21726 current_line_len += grapheme_len;
21727 }
21728 WordBreakToken::InlineWhitespace {
21729 mut token,
21730 mut grapheme_len,
21731 } => {
21732 in_whitespace = true;
21733 if have_preceding_whitespace && !preserve_existing_whitespace {
21734 continue;
21735 }
21736 if !preserve_existing_whitespace {
21737 token = " ";
21738 grapheme_len = 1;
21739 }
21740 let current_prefix_len = if is_first_line {
21741 first_line_prefix_len
21742 } else {
21743 subsequent_lines_prefix_len
21744 };
21745 if current_line_len + grapheme_len > wrap_column {
21746 wrapped_text.push_str(current_line.trim_end());
21747 wrapped_text.push('\n');
21748 is_first_line = false;
21749 current_line = subsequent_lines_prefix.clone();
21750 current_line_len = subsequent_lines_prefix_len;
21751 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21752 current_line.push_str(token);
21753 current_line_len += grapheme_len;
21754 }
21755 }
21756 WordBreakToken::Newline => {
21757 in_whitespace = true;
21758 let current_prefix_len = if is_first_line {
21759 first_line_prefix_len
21760 } else {
21761 subsequent_lines_prefix_len
21762 };
21763 if preserve_existing_whitespace {
21764 wrapped_text.push_str(current_line.trim_end());
21765 wrapped_text.push('\n');
21766 is_first_line = false;
21767 current_line = subsequent_lines_prefix.clone();
21768 current_line_len = subsequent_lines_prefix_len;
21769 } else if have_preceding_whitespace {
21770 continue;
21771 } else if current_line_len + 1 > wrap_column
21772 && current_line_len != current_prefix_len
21773 {
21774 wrapped_text.push_str(current_line.trim_end());
21775 wrapped_text.push('\n');
21776 is_first_line = false;
21777 current_line = subsequent_lines_prefix.clone();
21778 current_line_len = subsequent_lines_prefix_len;
21779 } else if current_line_len != current_prefix_len {
21780 current_line.push(' ');
21781 current_line_len += 1;
21782 }
21783 }
21784 }
21785 }
21786
21787 if !current_line.is_empty() {
21788 wrapped_text.push_str(¤t_line);
21789 }
21790 wrapped_text
21791}
21792
21793#[test]
21794fn test_wrap_with_prefix() {
21795 assert_eq!(
21796 wrap_with_prefix(
21797 "# ".to_string(),
21798 "# ".to_string(),
21799 "abcdefg".to_string(),
21800 4,
21801 NonZeroU32::new(4).unwrap(),
21802 false,
21803 ),
21804 "# abcdefg"
21805 );
21806 assert_eq!(
21807 wrap_with_prefix(
21808 "".to_string(),
21809 "".to_string(),
21810 "\thello world".to_string(),
21811 8,
21812 NonZeroU32::new(4).unwrap(),
21813 false,
21814 ),
21815 "hello\nworld"
21816 );
21817 assert_eq!(
21818 wrap_with_prefix(
21819 "// ".to_string(),
21820 "// ".to_string(),
21821 "xx \nyy zz aa bb cc".to_string(),
21822 12,
21823 NonZeroU32::new(4).unwrap(),
21824 false,
21825 ),
21826 "// xx yy zz\n// aa bb cc"
21827 );
21828 assert_eq!(
21829 wrap_with_prefix(
21830 String::new(),
21831 String::new(),
21832 "这是什么 \n 钢笔".to_string(),
21833 3,
21834 NonZeroU32::new(4).unwrap(),
21835 false,
21836 ),
21837 "这是什\n么 钢\n笔"
21838 );
21839}
21840
21841pub trait CollaborationHub {
21842 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21843 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21844 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21845}
21846
21847impl CollaborationHub for Entity<Project> {
21848 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21849 self.read(cx).collaborators()
21850 }
21851
21852 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21853 self.read(cx).user_store().read(cx).participant_indices()
21854 }
21855
21856 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21857 let this = self.read(cx);
21858 let user_ids = this.collaborators().values().map(|c| c.user_id);
21859 this.user_store().read(cx).participant_names(user_ids, cx)
21860 }
21861}
21862
21863pub trait SemanticsProvider {
21864 fn hover(
21865 &self,
21866 buffer: &Entity<Buffer>,
21867 position: text::Anchor,
21868 cx: &mut App,
21869 ) -> Option<Task<Vec<project::Hover>>>;
21870
21871 fn inline_values(
21872 &self,
21873 buffer_handle: Entity<Buffer>,
21874 range: Range<text::Anchor>,
21875 cx: &mut App,
21876 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21877
21878 fn inlay_hints(
21879 &self,
21880 buffer_handle: Entity<Buffer>,
21881 range: Range<text::Anchor>,
21882 cx: &mut App,
21883 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21884
21885 fn resolve_inlay_hint(
21886 &self,
21887 hint: InlayHint,
21888 buffer_handle: Entity<Buffer>,
21889 server_id: LanguageServerId,
21890 cx: &mut App,
21891 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21892
21893 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21894
21895 fn document_highlights(
21896 &self,
21897 buffer: &Entity<Buffer>,
21898 position: text::Anchor,
21899 cx: &mut App,
21900 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21901
21902 fn definitions(
21903 &self,
21904 buffer: &Entity<Buffer>,
21905 position: text::Anchor,
21906 kind: GotoDefinitionKind,
21907 cx: &mut App,
21908 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21909
21910 fn range_for_rename(
21911 &self,
21912 buffer: &Entity<Buffer>,
21913 position: text::Anchor,
21914 cx: &mut App,
21915 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21916
21917 fn perform_rename(
21918 &self,
21919 buffer: &Entity<Buffer>,
21920 position: text::Anchor,
21921 new_name: String,
21922 cx: &mut App,
21923 ) -> Option<Task<Result<ProjectTransaction>>>;
21924}
21925
21926pub trait CompletionProvider {
21927 fn completions(
21928 &self,
21929 excerpt_id: ExcerptId,
21930 buffer: &Entity<Buffer>,
21931 buffer_position: text::Anchor,
21932 trigger: CompletionContext,
21933 window: &mut Window,
21934 cx: &mut Context<Editor>,
21935 ) -> Task<Result<Vec<CompletionResponse>>>;
21936
21937 fn resolve_completions(
21938 &self,
21939 _buffer: Entity<Buffer>,
21940 _completion_indices: Vec<usize>,
21941 _completions: Rc<RefCell<Box<[Completion]>>>,
21942 _cx: &mut Context<Editor>,
21943 ) -> Task<Result<bool>> {
21944 Task::ready(Ok(false))
21945 }
21946
21947 fn apply_additional_edits_for_completion(
21948 &self,
21949 _buffer: Entity<Buffer>,
21950 _completions: Rc<RefCell<Box<[Completion]>>>,
21951 _completion_index: usize,
21952 _push_to_history: bool,
21953 _cx: &mut Context<Editor>,
21954 ) -> Task<Result<Option<language::Transaction>>> {
21955 Task::ready(Ok(None))
21956 }
21957
21958 fn is_completion_trigger(
21959 &self,
21960 buffer: &Entity<Buffer>,
21961 position: language::Anchor,
21962 text: &str,
21963 trigger_in_words: bool,
21964 menu_is_open: bool,
21965 cx: &mut Context<Editor>,
21966 ) -> bool;
21967
21968 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21969
21970 fn sort_completions(&self) -> bool {
21971 true
21972 }
21973
21974 fn filter_completions(&self) -> bool {
21975 true
21976 }
21977}
21978
21979pub trait CodeActionProvider {
21980 fn id(&self) -> Arc<str>;
21981
21982 fn code_actions(
21983 &self,
21984 buffer: &Entity<Buffer>,
21985 range: Range<text::Anchor>,
21986 window: &mut Window,
21987 cx: &mut App,
21988 ) -> Task<Result<Vec<CodeAction>>>;
21989
21990 fn apply_code_action(
21991 &self,
21992 buffer_handle: Entity<Buffer>,
21993 action: CodeAction,
21994 excerpt_id: ExcerptId,
21995 push_to_history: bool,
21996 window: &mut Window,
21997 cx: &mut App,
21998 ) -> Task<Result<ProjectTransaction>>;
21999}
22000
22001impl CodeActionProvider for Entity<Project> {
22002 fn id(&self) -> Arc<str> {
22003 "project".into()
22004 }
22005
22006 fn code_actions(
22007 &self,
22008 buffer: &Entity<Buffer>,
22009 range: Range<text::Anchor>,
22010 _window: &mut Window,
22011 cx: &mut App,
22012 ) -> Task<Result<Vec<CodeAction>>> {
22013 self.update(cx, |project, cx| {
22014 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22015 let code_actions = project.code_actions(buffer, range, None, cx);
22016 cx.background_spawn(async move {
22017 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22018 Ok(code_lens_actions
22019 .context("code lens fetch")?
22020 .into_iter()
22021 .chain(code_actions.context("code action fetch")?)
22022 .collect())
22023 })
22024 })
22025 }
22026
22027 fn apply_code_action(
22028 &self,
22029 buffer_handle: Entity<Buffer>,
22030 action: CodeAction,
22031 _excerpt_id: ExcerptId,
22032 push_to_history: bool,
22033 _window: &mut Window,
22034 cx: &mut App,
22035 ) -> Task<Result<ProjectTransaction>> {
22036 self.update(cx, |project, cx| {
22037 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22038 })
22039 }
22040}
22041
22042fn snippet_completions(
22043 project: &Project,
22044 buffer: &Entity<Buffer>,
22045 buffer_position: text::Anchor,
22046 cx: &mut App,
22047) -> Task<Result<CompletionResponse>> {
22048 let languages = buffer.read(cx).languages_at(buffer_position);
22049 let snippet_store = project.snippets().read(cx);
22050
22051 let scopes: Vec<_> = languages
22052 .iter()
22053 .filter_map(|language| {
22054 let language_name = language.lsp_id();
22055 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22056
22057 if snippets.is_empty() {
22058 None
22059 } else {
22060 Some((language.default_scope(), snippets))
22061 }
22062 })
22063 .collect();
22064
22065 if scopes.is_empty() {
22066 return Task::ready(Ok(CompletionResponse {
22067 completions: vec![],
22068 is_incomplete: false,
22069 }));
22070 }
22071
22072 let snapshot = buffer.read(cx).text_snapshot();
22073 let chars: String = snapshot
22074 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22075 .collect();
22076 let executor = cx.background_executor().clone();
22077
22078 cx.background_spawn(async move {
22079 let mut is_incomplete = false;
22080 let mut completions: Vec<Completion> = Vec::new();
22081 for (scope, snippets) in scopes.into_iter() {
22082 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22083 let mut last_word = chars
22084 .chars()
22085 .take_while(|c| classifier.is_word(*c))
22086 .collect::<String>();
22087 last_word = last_word.chars().rev().collect();
22088
22089 if last_word.is_empty() {
22090 return Ok(CompletionResponse {
22091 completions: vec![],
22092 is_incomplete: true,
22093 });
22094 }
22095
22096 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22097 let to_lsp = |point: &text::Anchor| {
22098 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22099 point_to_lsp(end)
22100 };
22101 let lsp_end = to_lsp(&buffer_position);
22102
22103 let candidates = snippets
22104 .iter()
22105 .enumerate()
22106 .flat_map(|(ix, snippet)| {
22107 snippet
22108 .prefix
22109 .iter()
22110 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
22111 })
22112 .collect::<Vec<StringMatchCandidate>>();
22113
22114 const MAX_RESULTS: usize = 100;
22115 let mut matches = fuzzy::match_strings(
22116 &candidates,
22117 &last_word,
22118 last_word.chars().any(|c| c.is_uppercase()),
22119 true,
22120 MAX_RESULTS,
22121 &Default::default(),
22122 executor.clone(),
22123 )
22124 .await;
22125
22126 if matches.len() >= MAX_RESULTS {
22127 is_incomplete = true;
22128 }
22129
22130 // Remove all candidates where the query's start does not match the start of any word in the candidate
22131 if let Some(query_start) = last_word.chars().next() {
22132 matches.retain(|string_match| {
22133 split_words(&string_match.string).any(|word| {
22134 // Check that the first codepoint of the word as lowercase matches the first
22135 // codepoint of the query as lowercase
22136 word.chars()
22137 .flat_map(|codepoint| codepoint.to_lowercase())
22138 .zip(query_start.to_lowercase())
22139 .all(|(word_cp, query_cp)| word_cp == query_cp)
22140 })
22141 });
22142 }
22143
22144 let matched_strings = matches
22145 .into_iter()
22146 .map(|m| m.string)
22147 .collect::<HashSet<_>>();
22148
22149 completions.extend(snippets.iter().filter_map(|snippet| {
22150 let matching_prefix = snippet
22151 .prefix
22152 .iter()
22153 .find(|prefix| matched_strings.contains(*prefix))?;
22154 let start = as_offset - last_word.len();
22155 let start = snapshot.anchor_before(start);
22156 let range = start..buffer_position;
22157 let lsp_start = to_lsp(&start);
22158 let lsp_range = lsp::Range {
22159 start: lsp_start,
22160 end: lsp_end,
22161 };
22162 Some(Completion {
22163 replace_range: range,
22164 new_text: snippet.body.clone(),
22165 source: CompletionSource::Lsp {
22166 insert_range: None,
22167 server_id: LanguageServerId(usize::MAX),
22168 resolved: true,
22169 lsp_completion: Box::new(lsp::CompletionItem {
22170 label: snippet.prefix.first().unwrap().clone(),
22171 kind: Some(CompletionItemKind::SNIPPET),
22172 label_details: snippet.description.as_ref().map(|description| {
22173 lsp::CompletionItemLabelDetails {
22174 detail: Some(description.clone()),
22175 description: None,
22176 }
22177 }),
22178 insert_text_format: Some(InsertTextFormat::SNIPPET),
22179 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22180 lsp::InsertReplaceEdit {
22181 new_text: snippet.body.clone(),
22182 insert: lsp_range,
22183 replace: lsp_range,
22184 },
22185 )),
22186 filter_text: Some(snippet.body.clone()),
22187 sort_text: Some(char::MAX.to_string()),
22188 ..lsp::CompletionItem::default()
22189 }),
22190 lsp_defaults: None,
22191 },
22192 label: CodeLabel {
22193 text: matching_prefix.clone(),
22194 runs: Vec::new(),
22195 filter_range: 0..matching_prefix.len(),
22196 },
22197 icon_path: None,
22198 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22199 single_line: snippet.name.clone().into(),
22200 plain_text: snippet
22201 .description
22202 .clone()
22203 .map(|description| description.into()),
22204 }),
22205 insert_text_mode: None,
22206 confirm: None,
22207 })
22208 }))
22209 }
22210
22211 Ok(CompletionResponse {
22212 completions,
22213 is_incomplete,
22214 })
22215 })
22216}
22217
22218impl CompletionProvider for Entity<Project> {
22219 fn completions(
22220 &self,
22221 _excerpt_id: ExcerptId,
22222 buffer: &Entity<Buffer>,
22223 buffer_position: text::Anchor,
22224 options: CompletionContext,
22225 _window: &mut Window,
22226 cx: &mut Context<Editor>,
22227 ) -> Task<Result<Vec<CompletionResponse>>> {
22228 self.update(cx, |project, cx| {
22229 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22230 let project_completions = project.completions(buffer, buffer_position, options, cx);
22231 cx.background_spawn(async move {
22232 let mut responses = project_completions.await?;
22233 let snippets = snippets.await?;
22234 if !snippets.completions.is_empty() {
22235 responses.push(snippets);
22236 }
22237 Ok(responses)
22238 })
22239 })
22240 }
22241
22242 fn resolve_completions(
22243 &self,
22244 buffer: Entity<Buffer>,
22245 completion_indices: Vec<usize>,
22246 completions: Rc<RefCell<Box<[Completion]>>>,
22247 cx: &mut Context<Editor>,
22248 ) -> Task<Result<bool>> {
22249 self.update(cx, |project, cx| {
22250 project.lsp_store().update(cx, |lsp_store, cx| {
22251 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22252 })
22253 })
22254 }
22255
22256 fn apply_additional_edits_for_completion(
22257 &self,
22258 buffer: Entity<Buffer>,
22259 completions: Rc<RefCell<Box<[Completion]>>>,
22260 completion_index: usize,
22261 push_to_history: bool,
22262 cx: &mut Context<Editor>,
22263 ) -> Task<Result<Option<language::Transaction>>> {
22264 self.update(cx, |project, cx| {
22265 project.lsp_store().update(cx, |lsp_store, cx| {
22266 lsp_store.apply_additional_edits_for_completion(
22267 buffer,
22268 completions,
22269 completion_index,
22270 push_to_history,
22271 cx,
22272 )
22273 })
22274 })
22275 }
22276
22277 fn is_completion_trigger(
22278 &self,
22279 buffer: &Entity<Buffer>,
22280 position: language::Anchor,
22281 text: &str,
22282 trigger_in_words: bool,
22283 menu_is_open: bool,
22284 cx: &mut Context<Editor>,
22285 ) -> bool {
22286 let mut chars = text.chars();
22287 let char = if let Some(char) = chars.next() {
22288 char
22289 } else {
22290 return false;
22291 };
22292 if chars.next().is_some() {
22293 return false;
22294 }
22295
22296 let buffer = buffer.read(cx);
22297 let snapshot = buffer.snapshot();
22298 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22299 return false;
22300 }
22301 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22302 if trigger_in_words && classifier.is_word(char) {
22303 return true;
22304 }
22305
22306 buffer.completion_triggers().contains(text)
22307 }
22308}
22309
22310impl SemanticsProvider for Entity<Project> {
22311 fn hover(
22312 &self,
22313 buffer: &Entity<Buffer>,
22314 position: text::Anchor,
22315 cx: &mut App,
22316 ) -> Option<Task<Vec<project::Hover>>> {
22317 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22318 }
22319
22320 fn document_highlights(
22321 &self,
22322 buffer: &Entity<Buffer>,
22323 position: text::Anchor,
22324 cx: &mut App,
22325 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22326 Some(self.update(cx, |project, cx| {
22327 project.document_highlights(buffer, position, cx)
22328 }))
22329 }
22330
22331 fn definitions(
22332 &self,
22333 buffer: &Entity<Buffer>,
22334 position: text::Anchor,
22335 kind: GotoDefinitionKind,
22336 cx: &mut App,
22337 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22338 Some(self.update(cx, |project, cx| match kind {
22339 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22340 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22341 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22342 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22343 }))
22344 }
22345
22346 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22347 self.update(cx, |project, cx| {
22348 if project
22349 .active_debug_session(cx)
22350 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22351 {
22352 return true;
22353 }
22354
22355 buffer.update(cx, |buffer, cx| {
22356 project.any_language_server_supports_inlay_hints(buffer, cx)
22357 })
22358 })
22359 }
22360
22361 fn inline_values(
22362 &self,
22363 buffer_handle: Entity<Buffer>,
22364 range: Range<text::Anchor>,
22365 cx: &mut App,
22366 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22367 self.update(cx, |project, cx| {
22368 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22369
22370 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22371 })
22372 }
22373
22374 fn inlay_hints(
22375 &self,
22376 buffer_handle: Entity<Buffer>,
22377 range: Range<text::Anchor>,
22378 cx: &mut App,
22379 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22380 Some(self.update(cx, |project, cx| {
22381 project.inlay_hints(buffer_handle, range, cx)
22382 }))
22383 }
22384
22385 fn resolve_inlay_hint(
22386 &self,
22387 hint: InlayHint,
22388 buffer_handle: Entity<Buffer>,
22389 server_id: LanguageServerId,
22390 cx: &mut App,
22391 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22392 Some(self.update(cx, |project, cx| {
22393 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22394 }))
22395 }
22396
22397 fn range_for_rename(
22398 &self,
22399 buffer: &Entity<Buffer>,
22400 position: text::Anchor,
22401 cx: &mut App,
22402 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22403 Some(self.update(cx, |project, cx| {
22404 let buffer = buffer.clone();
22405 let task = project.prepare_rename(buffer.clone(), position, cx);
22406 cx.spawn(async move |_, cx| {
22407 Ok(match task.await? {
22408 PrepareRenameResponse::Success(range) => Some(range),
22409 PrepareRenameResponse::InvalidPosition => None,
22410 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22411 // Fallback on using TreeSitter info to determine identifier range
22412 buffer.read_with(cx, |buffer, _| {
22413 let snapshot = buffer.snapshot();
22414 let (range, kind) = snapshot.surrounding_word(position, false);
22415 if kind != Some(CharKind::Word) {
22416 return None;
22417 }
22418 Some(
22419 snapshot.anchor_before(range.start)
22420 ..snapshot.anchor_after(range.end),
22421 )
22422 })?
22423 }
22424 })
22425 })
22426 }))
22427 }
22428
22429 fn perform_rename(
22430 &self,
22431 buffer: &Entity<Buffer>,
22432 position: text::Anchor,
22433 new_name: String,
22434 cx: &mut App,
22435 ) -> Option<Task<Result<ProjectTransaction>>> {
22436 Some(self.update(cx, |project, cx| {
22437 project.perform_rename(buffer.clone(), position, new_name, cx)
22438 }))
22439 }
22440}
22441
22442fn inlay_hint_settings(
22443 location: Anchor,
22444 snapshot: &MultiBufferSnapshot,
22445 cx: &mut Context<Editor>,
22446) -> InlayHintSettings {
22447 let file = snapshot.file_at(location);
22448 let language = snapshot.language_at(location).map(|l| l.name());
22449 language_settings(language, file, cx).inlay_hints
22450}
22451
22452fn consume_contiguous_rows(
22453 contiguous_row_selections: &mut Vec<Selection<Point>>,
22454 selection: &Selection<Point>,
22455 display_map: &DisplaySnapshot,
22456 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22457) -> (MultiBufferRow, MultiBufferRow) {
22458 contiguous_row_selections.push(selection.clone());
22459 let start_row = starting_row(selection, display_map);
22460 let mut end_row = ending_row(selection, display_map);
22461
22462 while let Some(next_selection) = selections.peek() {
22463 if next_selection.start.row <= end_row.0 {
22464 end_row = ending_row(next_selection, display_map);
22465 contiguous_row_selections.push(selections.next().unwrap().clone());
22466 } else {
22467 break;
22468 }
22469 }
22470 (start_row, end_row)
22471}
22472
22473fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22474 if selection.start.column > 0 {
22475 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22476 } else {
22477 MultiBufferRow(selection.start.row)
22478 }
22479}
22480
22481fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22482 if next_selection.end.column > 0 || next_selection.is_empty() {
22483 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22484 } else {
22485 MultiBufferRow(next_selection.end.row)
22486 }
22487}
22488
22489impl EditorSnapshot {
22490 pub fn remote_selections_in_range<'a>(
22491 &'a self,
22492 range: &'a Range<Anchor>,
22493 collaboration_hub: &dyn CollaborationHub,
22494 cx: &'a App,
22495 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22496 let participant_names = collaboration_hub.user_names(cx);
22497 let participant_indices = collaboration_hub.user_participant_indices(cx);
22498 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22499 let collaborators_by_replica_id = collaborators_by_peer_id
22500 .values()
22501 .map(|collaborator| (collaborator.replica_id, collaborator))
22502 .collect::<HashMap<_, _>>();
22503 self.buffer_snapshot
22504 .selections_in_range(range, false)
22505 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22506 if replica_id == AGENT_REPLICA_ID {
22507 Some(RemoteSelection {
22508 replica_id,
22509 selection,
22510 cursor_shape,
22511 line_mode,
22512 collaborator_id: CollaboratorId::Agent,
22513 user_name: Some("Agent".into()),
22514 color: cx.theme().players().agent(),
22515 })
22516 } else {
22517 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22518 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22519 let user_name = participant_names.get(&collaborator.user_id).cloned();
22520 Some(RemoteSelection {
22521 replica_id,
22522 selection,
22523 cursor_shape,
22524 line_mode,
22525 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22526 user_name,
22527 color: if let Some(index) = participant_index {
22528 cx.theme().players().color_for_participant(index.0)
22529 } else {
22530 cx.theme().players().absent()
22531 },
22532 })
22533 }
22534 })
22535 }
22536
22537 pub fn hunks_for_ranges(
22538 &self,
22539 ranges: impl IntoIterator<Item = Range<Point>>,
22540 ) -> Vec<MultiBufferDiffHunk> {
22541 let mut hunks = Vec::new();
22542 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22543 HashMap::default();
22544 for query_range in ranges {
22545 let query_rows =
22546 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22547 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22548 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22549 ) {
22550 // Include deleted hunks that are adjacent to the query range, because
22551 // otherwise they would be missed.
22552 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22553 if hunk.status().is_deleted() {
22554 intersects_range |= hunk.row_range.start == query_rows.end;
22555 intersects_range |= hunk.row_range.end == query_rows.start;
22556 }
22557 if intersects_range {
22558 if !processed_buffer_rows
22559 .entry(hunk.buffer_id)
22560 .or_default()
22561 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22562 {
22563 continue;
22564 }
22565 hunks.push(hunk);
22566 }
22567 }
22568 }
22569
22570 hunks
22571 }
22572
22573 fn display_diff_hunks_for_rows<'a>(
22574 &'a self,
22575 display_rows: Range<DisplayRow>,
22576 folded_buffers: &'a HashSet<BufferId>,
22577 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22578 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22579 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22580
22581 self.buffer_snapshot
22582 .diff_hunks_in_range(buffer_start..buffer_end)
22583 .filter_map(|hunk| {
22584 if folded_buffers.contains(&hunk.buffer_id) {
22585 return None;
22586 }
22587
22588 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22589 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22590
22591 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22592 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22593
22594 let display_hunk = if hunk_display_start.column() != 0 {
22595 DisplayDiffHunk::Folded {
22596 display_row: hunk_display_start.row(),
22597 }
22598 } else {
22599 let mut end_row = hunk_display_end.row();
22600 if hunk_display_end.column() > 0 {
22601 end_row.0 += 1;
22602 }
22603 let is_created_file = hunk.is_created_file();
22604 DisplayDiffHunk::Unfolded {
22605 status: hunk.status(),
22606 diff_base_byte_range: hunk.diff_base_byte_range,
22607 display_row_range: hunk_display_start.row()..end_row,
22608 multi_buffer_range: Anchor::range_in_buffer(
22609 hunk.excerpt_id,
22610 hunk.buffer_id,
22611 hunk.buffer_range,
22612 ),
22613 is_created_file,
22614 }
22615 };
22616
22617 Some(display_hunk)
22618 })
22619 }
22620
22621 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22622 self.display_snapshot.buffer_snapshot.language_at(position)
22623 }
22624
22625 pub fn is_focused(&self) -> bool {
22626 self.is_focused
22627 }
22628
22629 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22630 self.placeholder_text.as_ref()
22631 }
22632
22633 pub fn scroll_position(&self) -> gpui::Point<f32> {
22634 self.scroll_anchor.scroll_position(&self.display_snapshot)
22635 }
22636
22637 fn gutter_dimensions(
22638 &self,
22639 font_id: FontId,
22640 font_size: Pixels,
22641 max_line_number_width: Pixels,
22642 cx: &App,
22643 ) -> Option<GutterDimensions> {
22644 if !self.show_gutter {
22645 return None;
22646 }
22647
22648 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22649 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22650
22651 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22652 matches!(
22653 ProjectSettings::get_global(cx).git.git_gutter,
22654 Some(GitGutterSetting::TrackedFiles)
22655 )
22656 });
22657 let gutter_settings = EditorSettings::get_global(cx).gutter;
22658 let show_line_numbers = self
22659 .show_line_numbers
22660 .unwrap_or(gutter_settings.line_numbers);
22661 let line_gutter_width = if show_line_numbers {
22662 // Avoid flicker-like gutter resizes when the line number gains another digit by
22663 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22664 let min_width_for_number_on_gutter =
22665 ch_advance * gutter_settings.min_line_number_digits as f32;
22666 max_line_number_width.max(min_width_for_number_on_gutter)
22667 } else {
22668 0.0.into()
22669 };
22670
22671 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22672 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22673
22674 let git_blame_entries_width =
22675 self.git_blame_gutter_max_author_length
22676 .map(|max_author_length| {
22677 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22678 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22679
22680 /// The number of characters to dedicate to gaps and margins.
22681 const SPACING_WIDTH: usize = 4;
22682
22683 let max_char_count = max_author_length.min(renderer.max_author_length())
22684 + ::git::SHORT_SHA_LENGTH
22685 + MAX_RELATIVE_TIMESTAMP.len()
22686 + SPACING_WIDTH;
22687
22688 ch_advance * max_char_count
22689 });
22690
22691 let is_singleton = self.buffer_snapshot.is_singleton();
22692
22693 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22694 left_padding += if !is_singleton {
22695 ch_width * 4.0
22696 } else if show_runnables || show_breakpoints {
22697 ch_width * 3.0
22698 } else if show_git_gutter && show_line_numbers {
22699 ch_width * 2.0
22700 } else if show_git_gutter || show_line_numbers {
22701 ch_width
22702 } else {
22703 px(0.)
22704 };
22705
22706 let shows_folds = is_singleton && gutter_settings.folds;
22707
22708 let right_padding = if shows_folds && show_line_numbers {
22709 ch_width * 4.0
22710 } else if shows_folds || (!is_singleton && show_line_numbers) {
22711 ch_width * 3.0
22712 } else if show_line_numbers {
22713 ch_width
22714 } else {
22715 px(0.)
22716 };
22717
22718 Some(GutterDimensions {
22719 left_padding,
22720 right_padding,
22721 width: line_gutter_width + left_padding + right_padding,
22722 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22723 git_blame_entries_width,
22724 })
22725 }
22726
22727 pub fn render_crease_toggle(
22728 &self,
22729 buffer_row: MultiBufferRow,
22730 row_contains_cursor: bool,
22731 editor: Entity<Editor>,
22732 window: &mut Window,
22733 cx: &mut App,
22734 ) -> Option<AnyElement> {
22735 let folded = self.is_line_folded(buffer_row);
22736 let mut is_foldable = false;
22737
22738 if let Some(crease) = self
22739 .crease_snapshot
22740 .query_row(buffer_row, &self.buffer_snapshot)
22741 {
22742 is_foldable = true;
22743 match crease {
22744 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22745 if let Some(render_toggle) = render_toggle {
22746 let toggle_callback =
22747 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22748 if folded {
22749 editor.update(cx, |editor, cx| {
22750 editor.fold_at(buffer_row, window, cx)
22751 });
22752 } else {
22753 editor.update(cx, |editor, cx| {
22754 editor.unfold_at(buffer_row, window, cx)
22755 });
22756 }
22757 });
22758 return Some((render_toggle)(
22759 buffer_row,
22760 folded,
22761 toggle_callback,
22762 window,
22763 cx,
22764 ));
22765 }
22766 }
22767 }
22768 }
22769
22770 is_foldable |= self.starts_indent(buffer_row);
22771
22772 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22773 Some(
22774 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22775 .toggle_state(folded)
22776 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22777 if folded {
22778 this.unfold_at(buffer_row, window, cx);
22779 } else {
22780 this.fold_at(buffer_row, window, cx);
22781 }
22782 }))
22783 .into_any_element(),
22784 )
22785 } else {
22786 None
22787 }
22788 }
22789
22790 pub fn render_crease_trailer(
22791 &self,
22792 buffer_row: MultiBufferRow,
22793 window: &mut Window,
22794 cx: &mut App,
22795 ) -> Option<AnyElement> {
22796 let folded = self.is_line_folded(buffer_row);
22797 if let Crease::Inline { render_trailer, .. } = self
22798 .crease_snapshot
22799 .query_row(buffer_row, &self.buffer_snapshot)?
22800 {
22801 let render_trailer = render_trailer.as_ref()?;
22802 Some(render_trailer(buffer_row, folded, window, cx))
22803 } else {
22804 None
22805 }
22806 }
22807}
22808
22809impl Deref for EditorSnapshot {
22810 type Target = DisplaySnapshot;
22811
22812 fn deref(&self) -> &Self::Target {
22813 &self.display_snapshot
22814 }
22815}
22816
22817#[derive(Clone, Debug, PartialEq, Eq)]
22818pub enum EditorEvent {
22819 InputIgnored {
22820 text: Arc<str>,
22821 },
22822 InputHandled {
22823 utf16_range_to_replace: Option<Range<isize>>,
22824 text: Arc<str>,
22825 },
22826 ExcerptsAdded {
22827 buffer: Entity<Buffer>,
22828 predecessor: ExcerptId,
22829 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22830 },
22831 ExcerptsRemoved {
22832 ids: Vec<ExcerptId>,
22833 removed_buffer_ids: Vec<BufferId>,
22834 },
22835 BufferFoldToggled {
22836 ids: Vec<ExcerptId>,
22837 folded: bool,
22838 },
22839 ExcerptsEdited {
22840 ids: Vec<ExcerptId>,
22841 },
22842 ExcerptsExpanded {
22843 ids: Vec<ExcerptId>,
22844 },
22845 BufferEdited,
22846 Edited {
22847 transaction_id: clock::Lamport,
22848 },
22849 Reparsed(BufferId),
22850 Focused,
22851 FocusedIn,
22852 Blurred,
22853 DirtyChanged,
22854 Saved,
22855 TitleChanged,
22856 DiffBaseChanged,
22857 SelectionsChanged {
22858 local: bool,
22859 },
22860 ScrollPositionChanged {
22861 local: bool,
22862 autoscroll: bool,
22863 },
22864 Closed,
22865 TransactionUndone {
22866 transaction_id: clock::Lamport,
22867 },
22868 TransactionBegun {
22869 transaction_id: clock::Lamport,
22870 },
22871 Reloaded,
22872 CursorShapeChanged,
22873 BreadcrumbsChanged,
22874 PushedToNavHistory {
22875 anchor: Anchor,
22876 is_deactivate: bool,
22877 },
22878}
22879
22880impl EventEmitter<EditorEvent> for Editor {}
22881
22882impl Focusable for Editor {
22883 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22884 self.focus_handle.clone()
22885 }
22886}
22887
22888impl Render for Editor {
22889 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22890 let settings = ThemeSettings::get_global(cx);
22891
22892 let mut text_style = match self.mode {
22893 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22894 color: cx.theme().colors().editor_foreground,
22895 font_family: settings.ui_font.family.clone(),
22896 font_features: settings.ui_font.features.clone(),
22897 font_fallbacks: settings.ui_font.fallbacks.clone(),
22898 font_size: rems(0.875).into(),
22899 font_weight: settings.ui_font.weight,
22900 line_height: relative(settings.buffer_line_height.value()),
22901 ..Default::default()
22902 },
22903 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22904 color: cx.theme().colors().editor_foreground,
22905 font_family: settings.buffer_font.family.clone(),
22906 font_features: settings.buffer_font.features.clone(),
22907 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22908 font_size: settings.buffer_font_size(cx).into(),
22909 font_weight: settings.buffer_font.weight,
22910 line_height: relative(settings.buffer_line_height.value()),
22911 ..Default::default()
22912 },
22913 };
22914 if let Some(text_style_refinement) = &self.text_style_refinement {
22915 text_style.refine(text_style_refinement)
22916 }
22917
22918 let background = match self.mode {
22919 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22920 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22921 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22922 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22923 };
22924
22925 EditorElement::new(
22926 &cx.entity(),
22927 EditorStyle {
22928 background,
22929 border: cx.theme().colors().border,
22930 local_player: cx.theme().players().local(),
22931 text: text_style,
22932 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22933 syntax: cx.theme().syntax().clone(),
22934 status: cx.theme().status().clone(),
22935 inlay_hints_style: make_inlay_hints_style(cx),
22936 edit_prediction_styles: make_suggestion_styles(cx),
22937 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22938 show_underlines: self.diagnostics_enabled(),
22939 },
22940 )
22941 }
22942}
22943
22944impl EntityInputHandler for Editor {
22945 fn text_for_range(
22946 &mut self,
22947 range_utf16: Range<usize>,
22948 adjusted_range: &mut Option<Range<usize>>,
22949 _: &mut Window,
22950 cx: &mut Context<Self>,
22951 ) -> Option<String> {
22952 let snapshot = self.buffer.read(cx).read(cx);
22953 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22954 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22955 if (start.0..end.0) != range_utf16 {
22956 adjusted_range.replace(start.0..end.0);
22957 }
22958 Some(snapshot.text_for_range(start..end).collect())
22959 }
22960
22961 fn selected_text_range(
22962 &mut self,
22963 ignore_disabled_input: bool,
22964 _: &mut Window,
22965 cx: &mut Context<Self>,
22966 ) -> Option<UTF16Selection> {
22967 // Prevent the IME menu from appearing when holding down an alphabetic key
22968 // while input is disabled.
22969 if !ignore_disabled_input && !self.input_enabled {
22970 return None;
22971 }
22972
22973 let selection = self.selections.newest::<OffsetUtf16>(cx);
22974 let range = selection.range();
22975
22976 Some(UTF16Selection {
22977 range: range.start.0..range.end.0,
22978 reversed: selection.reversed,
22979 })
22980 }
22981
22982 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22983 let snapshot = self.buffer.read(cx).read(cx);
22984 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22985 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22986 }
22987
22988 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22989 self.clear_highlights::<InputComposition>(cx);
22990 self.ime_transaction.take();
22991 }
22992
22993 fn replace_text_in_range(
22994 &mut self,
22995 range_utf16: Option<Range<usize>>,
22996 text: &str,
22997 window: &mut Window,
22998 cx: &mut Context<Self>,
22999 ) {
23000 if !self.input_enabled {
23001 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23002 return;
23003 }
23004
23005 self.transact(window, cx, |this, window, cx| {
23006 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23007 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23008 Some(this.selection_replacement_ranges(range_utf16, cx))
23009 } else {
23010 this.marked_text_ranges(cx)
23011 };
23012
23013 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23014 let newest_selection_id = this.selections.newest_anchor().id;
23015 this.selections
23016 .all::<OffsetUtf16>(cx)
23017 .iter()
23018 .zip(ranges_to_replace.iter())
23019 .find_map(|(selection, range)| {
23020 if selection.id == newest_selection_id {
23021 Some(
23022 (range.start.0 as isize - selection.head().0 as isize)
23023 ..(range.end.0 as isize - selection.head().0 as isize),
23024 )
23025 } else {
23026 None
23027 }
23028 })
23029 });
23030
23031 cx.emit(EditorEvent::InputHandled {
23032 utf16_range_to_replace: range_to_replace,
23033 text: text.into(),
23034 });
23035
23036 if let Some(new_selected_ranges) = new_selected_ranges {
23037 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23038 selections.select_ranges(new_selected_ranges)
23039 });
23040 this.backspace(&Default::default(), window, cx);
23041 }
23042
23043 this.handle_input(text, window, cx);
23044 });
23045
23046 if let Some(transaction) = self.ime_transaction {
23047 self.buffer.update(cx, |buffer, cx| {
23048 buffer.group_until_transaction(transaction, cx);
23049 });
23050 }
23051
23052 self.unmark_text(window, cx);
23053 }
23054
23055 fn replace_and_mark_text_in_range(
23056 &mut self,
23057 range_utf16: Option<Range<usize>>,
23058 text: &str,
23059 new_selected_range_utf16: Option<Range<usize>>,
23060 window: &mut Window,
23061 cx: &mut Context<Self>,
23062 ) {
23063 if !self.input_enabled {
23064 return;
23065 }
23066
23067 let transaction = self.transact(window, cx, |this, window, cx| {
23068 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23069 let snapshot = this.buffer.read(cx).read(cx);
23070 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23071 for marked_range in &mut marked_ranges {
23072 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23073 marked_range.start.0 += relative_range_utf16.start;
23074 marked_range.start =
23075 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23076 marked_range.end =
23077 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23078 }
23079 }
23080 Some(marked_ranges)
23081 } else if let Some(range_utf16) = range_utf16 {
23082 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23083 Some(this.selection_replacement_ranges(range_utf16, cx))
23084 } else {
23085 None
23086 };
23087
23088 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23089 let newest_selection_id = this.selections.newest_anchor().id;
23090 this.selections
23091 .all::<OffsetUtf16>(cx)
23092 .iter()
23093 .zip(ranges_to_replace.iter())
23094 .find_map(|(selection, range)| {
23095 if selection.id == newest_selection_id {
23096 Some(
23097 (range.start.0 as isize - selection.head().0 as isize)
23098 ..(range.end.0 as isize - selection.head().0 as isize),
23099 )
23100 } else {
23101 None
23102 }
23103 })
23104 });
23105
23106 cx.emit(EditorEvent::InputHandled {
23107 utf16_range_to_replace: range_to_replace,
23108 text: text.into(),
23109 });
23110
23111 if let Some(ranges) = ranges_to_replace {
23112 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23113 s.select_ranges(ranges)
23114 });
23115 }
23116
23117 let marked_ranges = {
23118 let snapshot = this.buffer.read(cx).read(cx);
23119 this.selections
23120 .disjoint_anchors()
23121 .iter()
23122 .map(|selection| {
23123 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23124 })
23125 .collect::<Vec<_>>()
23126 };
23127
23128 if text.is_empty() {
23129 this.unmark_text(window, cx);
23130 } else {
23131 this.highlight_text::<InputComposition>(
23132 marked_ranges.clone(),
23133 HighlightStyle {
23134 underline: Some(UnderlineStyle {
23135 thickness: px(1.),
23136 color: None,
23137 wavy: false,
23138 }),
23139 ..Default::default()
23140 },
23141 cx,
23142 );
23143 }
23144
23145 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23146 let use_autoclose = this.use_autoclose;
23147 let use_auto_surround = this.use_auto_surround;
23148 this.set_use_autoclose(false);
23149 this.set_use_auto_surround(false);
23150 this.handle_input(text, window, cx);
23151 this.set_use_autoclose(use_autoclose);
23152 this.set_use_auto_surround(use_auto_surround);
23153
23154 if let Some(new_selected_range) = new_selected_range_utf16 {
23155 let snapshot = this.buffer.read(cx).read(cx);
23156 let new_selected_ranges = marked_ranges
23157 .into_iter()
23158 .map(|marked_range| {
23159 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23160 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23161 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23162 snapshot.clip_offset_utf16(new_start, Bias::Left)
23163 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23164 })
23165 .collect::<Vec<_>>();
23166
23167 drop(snapshot);
23168 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23169 selections.select_ranges(new_selected_ranges)
23170 });
23171 }
23172 });
23173
23174 self.ime_transaction = self.ime_transaction.or(transaction);
23175 if let Some(transaction) = self.ime_transaction {
23176 self.buffer.update(cx, |buffer, cx| {
23177 buffer.group_until_transaction(transaction, cx);
23178 });
23179 }
23180
23181 if self.text_highlights::<InputComposition>(cx).is_none() {
23182 self.ime_transaction.take();
23183 }
23184 }
23185
23186 fn bounds_for_range(
23187 &mut self,
23188 range_utf16: Range<usize>,
23189 element_bounds: gpui::Bounds<Pixels>,
23190 window: &mut Window,
23191 cx: &mut Context<Self>,
23192 ) -> Option<gpui::Bounds<Pixels>> {
23193 let text_layout_details = self.text_layout_details(window);
23194 let CharacterDimensions {
23195 em_width,
23196 em_advance,
23197 line_height,
23198 } = self.character_dimensions(window);
23199
23200 let snapshot = self.snapshot(window, cx);
23201 let scroll_position = snapshot.scroll_position();
23202 let scroll_left = scroll_position.x * em_advance;
23203
23204 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23205 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23206 + self.gutter_dimensions.full_width();
23207 let y = line_height * (start.row().as_f32() - scroll_position.y);
23208
23209 Some(Bounds {
23210 origin: element_bounds.origin + point(x, y),
23211 size: size(em_width, line_height),
23212 })
23213 }
23214
23215 fn character_index_for_point(
23216 &mut self,
23217 point: gpui::Point<Pixels>,
23218 _window: &mut Window,
23219 _cx: &mut Context<Self>,
23220 ) -> Option<usize> {
23221 let position_map = self.last_position_map.as_ref()?;
23222 if !position_map.text_hitbox.contains(&point) {
23223 return None;
23224 }
23225 let display_point = position_map.point_for_position(point).previous_valid;
23226 let anchor = position_map
23227 .snapshot
23228 .display_point_to_anchor(display_point, Bias::Left);
23229 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23230 Some(utf16_offset.0)
23231 }
23232}
23233
23234trait SelectionExt {
23235 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23236 fn spanned_rows(
23237 &self,
23238 include_end_if_at_line_start: bool,
23239 map: &DisplaySnapshot,
23240 ) -> Range<MultiBufferRow>;
23241}
23242
23243impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23244 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23245 let start = self
23246 .start
23247 .to_point(&map.buffer_snapshot)
23248 .to_display_point(map);
23249 let end = self
23250 .end
23251 .to_point(&map.buffer_snapshot)
23252 .to_display_point(map);
23253 if self.reversed {
23254 end..start
23255 } else {
23256 start..end
23257 }
23258 }
23259
23260 fn spanned_rows(
23261 &self,
23262 include_end_if_at_line_start: bool,
23263 map: &DisplaySnapshot,
23264 ) -> Range<MultiBufferRow> {
23265 let start = self.start.to_point(&map.buffer_snapshot);
23266 let mut end = self.end.to_point(&map.buffer_snapshot);
23267 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23268 end.row -= 1;
23269 }
23270
23271 let buffer_start = map.prev_line_boundary(start).0;
23272 let buffer_end = map.next_line_boundary(end).0;
23273 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23274 }
23275}
23276
23277impl<T: InvalidationRegion> InvalidationStack<T> {
23278 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23279 where
23280 S: Clone + ToOffset,
23281 {
23282 while let Some(region) = self.last() {
23283 let all_selections_inside_invalidation_ranges =
23284 if selections.len() == region.ranges().len() {
23285 selections
23286 .iter()
23287 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23288 .all(|(selection, invalidation_range)| {
23289 let head = selection.head().to_offset(buffer);
23290 invalidation_range.start <= head && invalidation_range.end >= head
23291 })
23292 } else {
23293 false
23294 };
23295
23296 if all_selections_inside_invalidation_ranges {
23297 break;
23298 } else {
23299 self.pop();
23300 }
23301 }
23302 }
23303}
23304
23305impl<T> Default for InvalidationStack<T> {
23306 fn default() -> Self {
23307 Self(Default::default())
23308 }
23309}
23310
23311impl<T> Deref for InvalidationStack<T> {
23312 type Target = Vec<T>;
23313
23314 fn deref(&self) -> &Self::Target {
23315 &self.0
23316 }
23317}
23318
23319impl<T> DerefMut for InvalidationStack<T> {
23320 fn deref_mut(&mut self) -> &mut Self::Target {
23321 &mut self.0
23322 }
23323}
23324
23325impl InvalidationRegion for SnippetState {
23326 fn ranges(&self) -> &[Range<Anchor>] {
23327 &self.ranges[self.active_index]
23328 }
23329}
23330
23331fn edit_prediction_edit_text(
23332 current_snapshot: &BufferSnapshot,
23333 edits: &[(Range<Anchor>, String)],
23334 edit_preview: &EditPreview,
23335 include_deletions: bool,
23336 cx: &App,
23337) -> HighlightedText {
23338 let edits = edits
23339 .iter()
23340 .map(|(anchor, text)| {
23341 (
23342 anchor.start.text_anchor..anchor.end.text_anchor,
23343 text.clone(),
23344 )
23345 })
23346 .collect::<Vec<_>>();
23347
23348 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23349}
23350
23351fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23352 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23353 // Just show the raw edit text with basic styling
23354 let mut text = String::new();
23355 let mut highlights = Vec::new();
23356
23357 let insertion_highlight_style = HighlightStyle {
23358 color: Some(cx.theme().colors().text),
23359 ..Default::default()
23360 };
23361
23362 for (_, edit_text) in edits {
23363 let start_offset = text.len();
23364 text.push_str(edit_text);
23365 let end_offset = text.len();
23366
23367 if start_offset < end_offset {
23368 highlights.push((start_offset..end_offset, insertion_highlight_style));
23369 }
23370 }
23371
23372 HighlightedText {
23373 text: text.into(),
23374 highlights,
23375 }
23376}
23377
23378pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23379 match severity {
23380 lsp::DiagnosticSeverity::ERROR => colors.error,
23381 lsp::DiagnosticSeverity::WARNING => colors.warning,
23382 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23383 lsp::DiagnosticSeverity::HINT => colors.info,
23384 _ => colors.ignored,
23385 }
23386}
23387
23388pub fn styled_runs_for_code_label<'a>(
23389 label: &'a CodeLabel,
23390 syntax_theme: &'a theme::SyntaxTheme,
23391) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23392 let fade_out = HighlightStyle {
23393 fade_out: Some(0.35),
23394 ..Default::default()
23395 };
23396
23397 let mut prev_end = label.filter_range.end;
23398 label
23399 .runs
23400 .iter()
23401 .enumerate()
23402 .flat_map(move |(ix, (range, highlight_id))| {
23403 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23404 style
23405 } else {
23406 return Default::default();
23407 };
23408 let mut muted_style = style;
23409 muted_style.highlight(fade_out);
23410
23411 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23412 if range.start >= label.filter_range.end {
23413 if range.start > prev_end {
23414 runs.push((prev_end..range.start, fade_out));
23415 }
23416 runs.push((range.clone(), muted_style));
23417 } else if range.end <= label.filter_range.end {
23418 runs.push((range.clone(), style));
23419 } else {
23420 runs.push((range.start..label.filter_range.end, style));
23421 runs.push((label.filter_range.end..range.end, muted_style));
23422 }
23423 prev_end = cmp::max(prev_end, range.end);
23424
23425 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23426 runs.push((prev_end..label.text.len(), fade_out));
23427 }
23428
23429 runs
23430 })
23431}
23432
23433pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23434 let mut prev_index = 0;
23435 let mut prev_codepoint: Option<char> = None;
23436 text.char_indices()
23437 .chain([(text.len(), '\0')])
23438 .filter_map(move |(index, codepoint)| {
23439 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23440 let is_boundary = index == text.len()
23441 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23442 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23443 if is_boundary {
23444 let chunk = &text[prev_index..index];
23445 prev_index = index;
23446 Some(chunk)
23447 } else {
23448 None
23449 }
23450 })
23451}
23452
23453pub trait RangeToAnchorExt: Sized {
23454 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23455
23456 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23457 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23458 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23459 }
23460}
23461
23462impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23463 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23464 let start_offset = self.start.to_offset(snapshot);
23465 let end_offset = self.end.to_offset(snapshot);
23466 if start_offset == end_offset {
23467 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23468 } else {
23469 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23470 }
23471 }
23472}
23473
23474pub trait RowExt {
23475 fn as_f32(&self) -> f32;
23476
23477 fn next_row(&self) -> Self;
23478
23479 fn previous_row(&self) -> Self;
23480
23481 fn minus(&self, other: Self) -> u32;
23482}
23483
23484impl RowExt for DisplayRow {
23485 fn as_f32(&self) -> f32 {
23486 self.0 as f32
23487 }
23488
23489 fn next_row(&self) -> Self {
23490 Self(self.0 + 1)
23491 }
23492
23493 fn previous_row(&self) -> Self {
23494 Self(self.0.saturating_sub(1))
23495 }
23496
23497 fn minus(&self, other: Self) -> u32 {
23498 self.0 - other.0
23499 }
23500}
23501
23502impl RowExt for MultiBufferRow {
23503 fn as_f32(&self) -> f32 {
23504 self.0 as f32
23505 }
23506
23507 fn next_row(&self) -> Self {
23508 Self(self.0 + 1)
23509 }
23510
23511 fn previous_row(&self) -> Self {
23512 Self(self.0.saturating_sub(1))
23513 }
23514
23515 fn minus(&self, other: Self) -> u32 {
23516 self.0 - other.0
23517 }
23518}
23519
23520trait RowRangeExt {
23521 type Row;
23522
23523 fn len(&self) -> usize;
23524
23525 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23526}
23527
23528impl RowRangeExt for Range<MultiBufferRow> {
23529 type Row = MultiBufferRow;
23530
23531 fn len(&self) -> usize {
23532 (self.end.0 - self.start.0) as usize
23533 }
23534
23535 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23536 (self.start.0..self.end.0).map(MultiBufferRow)
23537 }
23538}
23539
23540impl RowRangeExt for Range<DisplayRow> {
23541 type Row = DisplayRow;
23542
23543 fn len(&self) -> usize {
23544 (self.end.0 - self.start.0) as usize
23545 }
23546
23547 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23548 (self.start.0..self.end.0).map(DisplayRow)
23549 }
23550}
23551
23552/// If select range has more than one line, we
23553/// just point the cursor to range.start.
23554fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23555 if range.start.row == range.end.row {
23556 range
23557 } else {
23558 range.start..range.start
23559 }
23560}
23561pub struct KillRing(ClipboardItem);
23562impl Global for KillRing {}
23563
23564const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23565
23566enum BreakpointPromptEditAction {
23567 Log,
23568 Condition,
23569 HitCondition,
23570}
23571
23572struct BreakpointPromptEditor {
23573 pub(crate) prompt: Entity<Editor>,
23574 editor: WeakEntity<Editor>,
23575 breakpoint_anchor: Anchor,
23576 breakpoint: Breakpoint,
23577 edit_action: BreakpointPromptEditAction,
23578 block_ids: HashSet<CustomBlockId>,
23579 editor_margins: Arc<Mutex<EditorMargins>>,
23580 _subscriptions: Vec<Subscription>,
23581}
23582
23583impl BreakpointPromptEditor {
23584 const MAX_LINES: u8 = 4;
23585
23586 fn new(
23587 editor: WeakEntity<Editor>,
23588 breakpoint_anchor: Anchor,
23589 breakpoint: Breakpoint,
23590 edit_action: BreakpointPromptEditAction,
23591 window: &mut Window,
23592 cx: &mut Context<Self>,
23593 ) -> Self {
23594 let base_text = match edit_action {
23595 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23596 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23597 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23598 }
23599 .map(|msg| msg.to_string())
23600 .unwrap_or_default();
23601
23602 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23603 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23604
23605 let prompt = cx.new(|cx| {
23606 let mut prompt = Editor::new(
23607 EditorMode::AutoHeight {
23608 min_lines: 1,
23609 max_lines: Some(Self::MAX_LINES as usize),
23610 },
23611 buffer,
23612 None,
23613 window,
23614 cx,
23615 );
23616 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23617 prompt.set_show_cursor_when_unfocused(false, cx);
23618 prompt.set_placeholder_text(
23619 match edit_action {
23620 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23621 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23622 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23623 },
23624 cx,
23625 );
23626
23627 prompt
23628 });
23629
23630 Self {
23631 prompt,
23632 editor,
23633 breakpoint_anchor,
23634 breakpoint,
23635 edit_action,
23636 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23637 block_ids: Default::default(),
23638 _subscriptions: vec![],
23639 }
23640 }
23641
23642 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23643 self.block_ids.extend(block_ids)
23644 }
23645
23646 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23647 if let Some(editor) = self.editor.upgrade() {
23648 let message = self
23649 .prompt
23650 .read(cx)
23651 .buffer
23652 .read(cx)
23653 .as_singleton()
23654 .expect("A multi buffer in breakpoint prompt isn't possible")
23655 .read(cx)
23656 .as_rope()
23657 .to_string();
23658
23659 editor.update(cx, |editor, cx| {
23660 editor.edit_breakpoint_at_anchor(
23661 self.breakpoint_anchor,
23662 self.breakpoint.clone(),
23663 match self.edit_action {
23664 BreakpointPromptEditAction::Log => {
23665 BreakpointEditAction::EditLogMessage(message.into())
23666 }
23667 BreakpointPromptEditAction::Condition => {
23668 BreakpointEditAction::EditCondition(message.into())
23669 }
23670 BreakpointPromptEditAction::HitCondition => {
23671 BreakpointEditAction::EditHitCondition(message.into())
23672 }
23673 },
23674 cx,
23675 );
23676
23677 editor.remove_blocks(self.block_ids.clone(), None, cx);
23678 cx.focus_self(window);
23679 });
23680 }
23681 }
23682
23683 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23684 self.editor
23685 .update(cx, |editor, cx| {
23686 editor.remove_blocks(self.block_ids.clone(), None, cx);
23687 window.focus(&editor.focus_handle);
23688 })
23689 .log_err();
23690 }
23691
23692 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23693 let settings = ThemeSettings::get_global(cx);
23694 let text_style = TextStyle {
23695 color: if self.prompt.read(cx).read_only(cx) {
23696 cx.theme().colors().text_disabled
23697 } else {
23698 cx.theme().colors().text
23699 },
23700 font_family: settings.buffer_font.family.clone(),
23701 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23702 font_size: settings.buffer_font_size(cx).into(),
23703 font_weight: settings.buffer_font.weight,
23704 line_height: relative(settings.buffer_line_height.value()),
23705 ..Default::default()
23706 };
23707 EditorElement::new(
23708 &self.prompt,
23709 EditorStyle {
23710 background: cx.theme().colors().editor_background,
23711 local_player: cx.theme().players().local(),
23712 text: text_style,
23713 ..Default::default()
23714 },
23715 )
23716 }
23717}
23718
23719impl Render for BreakpointPromptEditor {
23720 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23721 let editor_margins = *self.editor_margins.lock();
23722 let gutter_dimensions = editor_margins.gutter;
23723 h_flex()
23724 .key_context("Editor")
23725 .bg(cx.theme().colors().editor_background)
23726 .border_y_1()
23727 .border_color(cx.theme().status().info_border)
23728 .size_full()
23729 .py(window.line_height() / 2.5)
23730 .on_action(cx.listener(Self::confirm))
23731 .on_action(cx.listener(Self::cancel))
23732 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23733 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23734 }
23735}
23736
23737impl Focusable for BreakpointPromptEditor {
23738 fn focus_handle(&self, cx: &App) -> FocusHandle {
23739 self.prompt.focus_handle(cx)
23740 }
23741}
23742
23743fn all_edits_insertions_or_deletions(
23744 edits: &Vec<(Range<Anchor>, String)>,
23745 snapshot: &MultiBufferSnapshot,
23746) -> bool {
23747 let mut all_insertions = true;
23748 let mut all_deletions = true;
23749
23750 for (range, new_text) in edits.iter() {
23751 let range_is_empty = range.to_offset(&snapshot).is_empty();
23752 let text_is_empty = new_text.is_empty();
23753
23754 if range_is_empty != text_is_empty {
23755 if range_is_empty {
23756 all_deletions = false;
23757 } else {
23758 all_insertions = false;
23759 }
23760 } else {
23761 return false;
23762 }
23763
23764 if !all_insertions && !all_deletions {
23765 return false;
23766 }
23767 }
23768 all_insertions || all_deletions
23769}
23770
23771struct MissingEditPredictionKeybindingTooltip;
23772
23773impl Render for MissingEditPredictionKeybindingTooltip {
23774 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23775 ui::tooltip_container(window, cx, |container, _, cx| {
23776 container
23777 .flex_shrink_0()
23778 .max_w_80()
23779 .min_h(rems_from_px(124.))
23780 .justify_between()
23781 .child(
23782 v_flex()
23783 .flex_1()
23784 .text_ui_sm(cx)
23785 .child(Label::new("Conflict with Accept Keybinding"))
23786 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23787 )
23788 .child(
23789 h_flex()
23790 .pb_1()
23791 .gap_1()
23792 .items_end()
23793 .w_full()
23794 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23795 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23796 }))
23797 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23798 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23799 })),
23800 )
23801 })
23802 }
23803}
23804
23805#[derive(Debug, Clone, Copy, PartialEq)]
23806pub struct LineHighlight {
23807 pub background: Background,
23808 pub border: Option<gpui::Hsla>,
23809 pub include_gutter: bool,
23810 pub type_id: Option<TypeId>,
23811}
23812
23813struct LineManipulationResult {
23814 pub new_text: String,
23815 pub line_count_before: usize,
23816 pub line_count_after: usize,
23817}
23818
23819fn render_diff_hunk_controls(
23820 row: u32,
23821 status: &DiffHunkStatus,
23822 hunk_range: Range<Anchor>,
23823 is_created_file: bool,
23824 line_height: Pixels,
23825 editor: &Entity<Editor>,
23826 _window: &mut Window,
23827 cx: &mut App,
23828) -> AnyElement {
23829 h_flex()
23830 .h(line_height)
23831 .mr_1()
23832 .gap_1()
23833 .px_0p5()
23834 .pb_1()
23835 .border_x_1()
23836 .border_b_1()
23837 .border_color(cx.theme().colors().border_variant)
23838 .rounded_b_lg()
23839 .bg(cx.theme().colors().editor_background)
23840 .gap_1()
23841 .block_mouse_except_scroll()
23842 .shadow_md()
23843 .child(if status.has_secondary_hunk() {
23844 Button::new(("stage", row as u64), "Stage")
23845 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23846 .tooltip({
23847 let focus_handle = editor.focus_handle(cx);
23848 move |window, cx| {
23849 Tooltip::for_action_in(
23850 "Stage Hunk",
23851 &::git::ToggleStaged,
23852 &focus_handle,
23853 window,
23854 cx,
23855 )
23856 }
23857 })
23858 .on_click({
23859 let editor = editor.clone();
23860 move |_event, _window, cx| {
23861 editor.update(cx, |editor, cx| {
23862 editor.stage_or_unstage_diff_hunks(
23863 true,
23864 vec![hunk_range.start..hunk_range.start],
23865 cx,
23866 );
23867 });
23868 }
23869 })
23870 } else {
23871 Button::new(("unstage", row as u64), "Unstage")
23872 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23873 .tooltip({
23874 let focus_handle = editor.focus_handle(cx);
23875 move |window, cx| {
23876 Tooltip::for_action_in(
23877 "Unstage Hunk",
23878 &::git::ToggleStaged,
23879 &focus_handle,
23880 window,
23881 cx,
23882 )
23883 }
23884 })
23885 .on_click({
23886 let editor = editor.clone();
23887 move |_event, _window, cx| {
23888 editor.update(cx, |editor, cx| {
23889 editor.stage_or_unstage_diff_hunks(
23890 false,
23891 vec![hunk_range.start..hunk_range.start],
23892 cx,
23893 );
23894 });
23895 }
23896 })
23897 })
23898 .child(
23899 Button::new(("restore", row as u64), "Restore")
23900 .tooltip({
23901 let focus_handle = editor.focus_handle(cx);
23902 move |window, cx| {
23903 Tooltip::for_action_in(
23904 "Restore Hunk",
23905 &::git::Restore,
23906 &focus_handle,
23907 window,
23908 cx,
23909 )
23910 }
23911 })
23912 .on_click({
23913 let editor = editor.clone();
23914 move |_event, window, cx| {
23915 editor.update(cx, |editor, cx| {
23916 let snapshot = editor.snapshot(window, cx);
23917 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23918 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23919 });
23920 }
23921 })
23922 .disabled(is_created_file),
23923 )
23924 .when(
23925 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23926 |el| {
23927 el.child(
23928 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23929 .shape(IconButtonShape::Square)
23930 .icon_size(IconSize::Small)
23931 // .disabled(!has_multiple_hunks)
23932 .tooltip({
23933 let focus_handle = editor.focus_handle(cx);
23934 move |window, cx| {
23935 Tooltip::for_action_in(
23936 "Next Hunk",
23937 &GoToHunk,
23938 &focus_handle,
23939 window,
23940 cx,
23941 )
23942 }
23943 })
23944 .on_click({
23945 let editor = editor.clone();
23946 move |_event, window, cx| {
23947 editor.update(cx, |editor, cx| {
23948 let snapshot = editor.snapshot(window, cx);
23949 let position =
23950 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23951 editor.go_to_hunk_before_or_after_position(
23952 &snapshot,
23953 position,
23954 Direction::Next,
23955 window,
23956 cx,
23957 );
23958 editor.expand_selected_diff_hunks(cx);
23959 });
23960 }
23961 }),
23962 )
23963 .child(
23964 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23965 .shape(IconButtonShape::Square)
23966 .icon_size(IconSize::Small)
23967 // .disabled(!has_multiple_hunks)
23968 .tooltip({
23969 let focus_handle = editor.focus_handle(cx);
23970 move |window, cx| {
23971 Tooltip::for_action_in(
23972 "Previous Hunk",
23973 &GoToPreviousHunk,
23974 &focus_handle,
23975 window,
23976 cx,
23977 )
23978 }
23979 })
23980 .on_click({
23981 let editor = editor.clone();
23982 move |_event, window, cx| {
23983 editor.update(cx, |editor, cx| {
23984 let snapshot = editor.snapshot(window, cx);
23985 let point =
23986 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23987 editor.go_to_hunk_before_or_after_position(
23988 &snapshot,
23989 point,
23990 Direction::Prev,
23991 window,
23992 cx,
23993 );
23994 editor.expand_selected_diff_hunks(cx);
23995 });
23996 }
23997 }),
23998 )
23999 },
24000 )
24001 .into_any_element()
24002}