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 .unique()
15883 .take(3)
15884 .join(", ");
15885 format!("{tab_kind} for {target}")
15886 })
15887 .context("buffer title")?;
15888
15889 let opened = workspace
15890 .update_in(acx, |workspace, window, cx| {
15891 Self::open_locations_in_multibuffer(
15892 workspace,
15893 locations,
15894 title,
15895 split,
15896 MultibufferSelectionMode::First,
15897 window,
15898 cx,
15899 )
15900 })
15901 .is_ok();
15902
15903 anyhow::Ok(Navigated::from_bool(opened))
15904 } else if locations.is_empty() {
15905 // If there is one definition, just open it directly
15906 match first_url_or_file {
15907 Some(Either::Left(url)) => {
15908 acx.update(|_, cx| cx.open_url(&url))?;
15909 Ok(Navigated::Yes)
15910 }
15911 Some(Either::Right(path)) => {
15912 let Some(workspace) = workspace else {
15913 return Ok(Navigated::No);
15914 };
15915
15916 workspace
15917 .update_in(acx, |workspace, window, cx| {
15918 workspace.open_resolved_path(path, window, cx)
15919 })?
15920 .await?;
15921 Ok(Navigated::Yes)
15922 }
15923 None => Ok(Navigated::No),
15924 }
15925 } else {
15926 let Some(workspace) = workspace else {
15927 return Ok(Navigated::No);
15928 };
15929
15930 let target = locations.pop().unwrap();
15931 editor.update_in(acx, |editor, window, cx| {
15932 let pane = workspace.read(cx).active_pane().clone();
15933
15934 let range = target.range.to_point(target.buffer.read(cx));
15935 let range = editor.range_for_match(&range);
15936 let range = collapse_multiline_range(range);
15937
15938 if !split
15939 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15940 {
15941 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15942 } else {
15943 window.defer(cx, move |window, cx| {
15944 let target_editor: Entity<Self> =
15945 workspace.update(cx, |workspace, cx| {
15946 let pane = if split {
15947 workspace.adjacent_pane(window, cx)
15948 } else {
15949 workspace.active_pane().clone()
15950 };
15951
15952 workspace.open_project_item(
15953 pane,
15954 target.buffer.clone(),
15955 true,
15956 true,
15957 window,
15958 cx,
15959 )
15960 });
15961 target_editor.update(cx, |target_editor, cx| {
15962 // When selecting a definition in a different buffer, disable the nav history
15963 // to avoid creating a history entry at the previous cursor location.
15964 pane.update(cx, |pane, _| pane.disable_history());
15965 target_editor.go_to_singleton_buffer_range(range, window, cx);
15966 pane.update(cx, |pane, _| pane.enable_history());
15967 });
15968 });
15969 }
15970 Navigated::Yes
15971 })
15972 }
15973 })
15974 }
15975
15976 fn compute_target_location(
15977 &self,
15978 lsp_location: lsp::Location,
15979 server_id: LanguageServerId,
15980 window: &mut Window,
15981 cx: &mut Context<Self>,
15982 ) -> Task<anyhow::Result<Option<Location>>> {
15983 let Some(project) = self.project.clone() else {
15984 return Task::ready(Ok(None));
15985 };
15986
15987 cx.spawn_in(window, async move |editor, cx| {
15988 let location_task = editor.update(cx, |_, cx| {
15989 project.update(cx, |project, cx| {
15990 let language_server_name = project
15991 .language_server_statuses(cx)
15992 .find(|(id, _)| server_id == *id)
15993 .map(|(_, status)| status.name.clone());
15994 language_server_name.map(|language_server_name| {
15995 project.open_local_buffer_via_lsp(
15996 lsp_location.uri.clone(),
15997 server_id,
15998 language_server_name,
15999 cx,
16000 )
16001 })
16002 })
16003 })?;
16004 let location = match location_task {
16005 Some(task) => Some({
16006 let target_buffer_handle = task.await.context("open local buffer")?;
16007 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16008 let target_start = target_buffer
16009 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16010 let target_end = target_buffer
16011 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16012 target_buffer.anchor_after(target_start)
16013 ..target_buffer.anchor_before(target_end)
16014 })?;
16015 Location {
16016 buffer: target_buffer_handle,
16017 range,
16018 }
16019 }),
16020 None => None,
16021 };
16022 Ok(location)
16023 })
16024 }
16025
16026 pub fn find_all_references(
16027 &mut self,
16028 _: &FindAllReferences,
16029 window: &mut Window,
16030 cx: &mut Context<Self>,
16031 ) -> Option<Task<Result<Navigated>>> {
16032 let selection = self.selections.newest::<usize>(cx);
16033 let multi_buffer = self.buffer.read(cx);
16034 let head = selection.head();
16035
16036 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16037 let head_anchor = multi_buffer_snapshot.anchor_at(
16038 head,
16039 if head < selection.tail() {
16040 Bias::Right
16041 } else {
16042 Bias::Left
16043 },
16044 );
16045
16046 match self
16047 .find_all_references_task_sources
16048 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16049 {
16050 Ok(_) => {
16051 log::info!(
16052 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16053 );
16054 return None;
16055 }
16056 Err(i) => {
16057 self.find_all_references_task_sources.insert(i, head_anchor);
16058 }
16059 }
16060
16061 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16062 let workspace = self.workspace()?;
16063 let project = workspace.read(cx).project().clone();
16064 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16065 Some(cx.spawn_in(window, async move |editor, cx| {
16066 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16067 if let Ok(i) = editor
16068 .find_all_references_task_sources
16069 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16070 {
16071 editor.find_all_references_task_sources.remove(i);
16072 }
16073 });
16074
16075 let locations = references.await?;
16076 if locations.is_empty() {
16077 return anyhow::Ok(Navigated::No);
16078 }
16079
16080 workspace.update_in(cx, |workspace, window, cx| {
16081 let target = locations
16082 .iter()
16083 .map(|location| {
16084 location
16085 .buffer
16086 .read(cx)
16087 .text_for_range(location.range.clone())
16088 .collect::<String>()
16089 })
16090 .unique()
16091 .take(3)
16092 .join(", ");
16093 let title = format!("References to {target}");
16094 Self::open_locations_in_multibuffer(
16095 workspace,
16096 locations,
16097 title,
16098 false,
16099 MultibufferSelectionMode::First,
16100 window,
16101 cx,
16102 );
16103 Navigated::Yes
16104 })
16105 }))
16106 }
16107
16108 /// Opens a multibuffer with the given project locations in it
16109 pub fn open_locations_in_multibuffer(
16110 workspace: &mut Workspace,
16111 mut locations: Vec<Location>,
16112 title: String,
16113 split: bool,
16114 multibuffer_selection_mode: MultibufferSelectionMode,
16115 window: &mut Window,
16116 cx: &mut Context<Workspace>,
16117 ) {
16118 if locations.is_empty() {
16119 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16120 return;
16121 }
16122
16123 // If there are multiple definitions, open them in a multibuffer
16124 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16125 let mut locations = locations.into_iter().peekable();
16126 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16127 let capability = workspace.project().read(cx).capability();
16128
16129 let excerpt_buffer = cx.new(|cx| {
16130 let mut multibuffer = MultiBuffer::new(capability);
16131 while let Some(location) = locations.next() {
16132 let buffer = location.buffer.read(cx);
16133 let mut ranges_for_buffer = Vec::new();
16134 let range = location.range.to_point(buffer);
16135 ranges_for_buffer.push(range.clone());
16136
16137 while let Some(next_location) = locations.peek() {
16138 if next_location.buffer == location.buffer {
16139 ranges_for_buffer.push(next_location.range.to_point(buffer));
16140 locations.next();
16141 } else {
16142 break;
16143 }
16144 }
16145
16146 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16147 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16148 PathKey::for_buffer(&location.buffer, cx),
16149 location.buffer.clone(),
16150 ranges_for_buffer,
16151 DEFAULT_MULTIBUFFER_CONTEXT,
16152 cx,
16153 );
16154 ranges.extend(new_ranges)
16155 }
16156
16157 multibuffer.with_title(title)
16158 });
16159
16160 let editor = cx.new(|cx| {
16161 Editor::for_multibuffer(
16162 excerpt_buffer,
16163 Some(workspace.project().clone()),
16164 window,
16165 cx,
16166 )
16167 });
16168 editor.update(cx, |editor, cx| {
16169 match multibuffer_selection_mode {
16170 MultibufferSelectionMode::First => {
16171 if let Some(first_range) = ranges.first() {
16172 editor.change_selections(
16173 SelectionEffects::no_scroll(),
16174 window,
16175 cx,
16176 |selections| {
16177 selections.clear_disjoint();
16178 selections
16179 .select_anchor_ranges(std::iter::once(first_range.clone()));
16180 },
16181 );
16182 }
16183 editor.highlight_background::<Self>(
16184 &ranges,
16185 |theme| theme.colors().editor_highlighted_line_background,
16186 cx,
16187 );
16188 }
16189 MultibufferSelectionMode::All => {
16190 editor.change_selections(
16191 SelectionEffects::no_scroll(),
16192 window,
16193 cx,
16194 |selections| {
16195 selections.clear_disjoint();
16196 selections.select_anchor_ranges(ranges);
16197 },
16198 );
16199 }
16200 }
16201 editor.register_buffers_with_language_servers(cx);
16202 });
16203
16204 let item = Box::new(editor);
16205 let item_id = item.item_id();
16206
16207 if split {
16208 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16209 } else {
16210 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16211 let (preview_item_id, preview_item_idx) =
16212 workspace.active_pane().read_with(cx, |pane, _| {
16213 (pane.preview_item_id(), pane.preview_item_idx())
16214 });
16215
16216 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16217
16218 if let Some(preview_item_id) = preview_item_id {
16219 workspace.active_pane().update(cx, |pane, cx| {
16220 pane.remove_item(preview_item_id, false, false, window, cx);
16221 });
16222 }
16223 } else {
16224 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16225 }
16226 }
16227 workspace.active_pane().update(cx, |pane, cx| {
16228 pane.set_preview_item_id(Some(item_id), cx);
16229 });
16230 }
16231
16232 pub fn rename(
16233 &mut self,
16234 _: &Rename,
16235 window: &mut Window,
16236 cx: &mut Context<Self>,
16237 ) -> Option<Task<Result<()>>> {
16238 use language::ToOffset as _;
16239
16240 let provider = self.semantics_provider.clone()?;
16241 let selection = self.selections.newest_anchor().clone();
16242 let (cursor_buffer, cursor_buffer_position) = self
16243 .buffer
16244 .read(cx)
16245 .text_anchor_for_position(selection.head(), cx)?;
16246 let (tail_buffer, cursor_buffer_position_end) = self
16247 .buffer
16248 .read(cx)
16249 .text_anchor_for_position(selection.tail(), cx)?;
16250 if tail_buffer != cursor_buffer {
16251 return None;
16252 }
16253
16254 let snapshot = cursor_buffer.read(cx).snapshot();
16255 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16256 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16257 let prepare_rename = provider
16258 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16259 .unwrap_or_else(|| Task::ready(Ok(None)));
16260 drop(snapshot);
16261
16262 Some(cx.spawn_in(window, async move |this, cx| {
16263 let rename_range = if let Some(range) = prepare_rename.await? {
16264 Some(range)
16265 } else {
16266 this.update(cx, |this, cx| {
16267 let buffer = this.buffer.read(cx).snapshot(cx);
16268 let mut buffer_highlights = this
16269 .document_highlights_for_position(selection.head(), &buffer)
16270 .filter(|highlight| {
16271 highlight.start.excerpt_id == selection.head().excerpt_id
16272 && highlight.end.excerpt_id == selection.head().excerpt_id
16273 });
16274 buffer_highlights
16275 .next()
16276 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16277 })?
16278 };
16279 if let Some(rename_range) = rename_range {
16280 this.update_in(cx, |this, window, cx| {
16281 let snapshot = cursor_buffer.read(cx).snapshot();
16282 let rename_buffer_range = rename_range.to_offset(&snapshot);
16283 let cursor_offset_in_rename_range =
16284 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16285 let cursor_offset_in_rename_range_end =
16286 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16287
16288 this.take_rename(false, window, cx);
16289 let buffer = this.buffer.read(cx).read(cx);
16290 let cursor_offset = selection.head().to_offset(&buffer);
16291 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16292 let rename_end = rename_start + rename_buffer_range.len();
16293 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16294 let mut old_highlight_id = None;
16295 let old_name: Arc<str> = buffer
16296 .chunks(rename_start..rename_end, true)
16297 .map(|chunk| {
16298 if old_highlight_id.is_none() {
16299 old_highlight_id = chunk.syntax_highlight_id;
16300 }
16301 chunk.text
16302 })
16303 .collect::<String>()
16304 .into();
16305
16306 drop(buffer);
16307
16308 // Position the selection in the rename editor so that it matches the current selection.
16309 this.show_local_selections = false;
16310 let rename_editor = cx.new(|cx| {
16311 let mut editor = Editor::single_line(window, cx);
16312 editor.buffer.update(cx, |buffer, cx| {
16313 buffer.edit([(0..0, old_name.clone())], None, cx)
16314 });
16315 let rename_selection_range = match cursor_offset_in_rename_range
16316 .cmp(&cursor_offset_in_rename_range_end)
16317 {
16318 Ordering::Equal => {
16319 editor.select_all(&SelectAll, window, cx);
16320 return editor;
16321 }
16322 Ordering::Less => {
16323 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16324 }
16325 Ordering::Greater => {
16326 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16327 }
16328 };
16329 if rename_selection_range.end > old_name.len() {
16330 editor.select_all(&SelectAll, window, cx);
16331 } else {
16332 editor.change_selections(Default::default(), window, cx, |s| {
16333 s.select_ranges([rename_selection_range]);
16334 });
16335 }
16336 editor
16337 });
16338 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16339 if e == &EditorEvent::Focused {
16340 cx.emit(EditorEvent::FocusedIn)
16341 }
16342 })
16343 .detach();
16344
16345 let write_highlights =
16346 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16347 let read_highlights =
16348 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16349 let ranges = write_highlights
16350 .iter()
16351 .flat_map(|(_, ranges)| ranges.iter())
16352 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16353 .cloned()
16354 .collect();
16355
16356 this.highlight_text::<Rename>(
16357 ranges,
16358 HighlightStyle {
16359 fade_out: Some(0.6),
16360 ..Default::default()
16361 },
16362 cx,
16363 );
16364 let rename_focus_handle = rename_editor.focus_handle(cx);
16365 window.focus(&rename_focus_handle);
16366 let block_id = this.insert_blocks(
16367 [BlockProperties {
16368 style: BlockStyle::Flex,
16369 placement: BlockPlacement::Below(range.start),
16370 height: Some(1),
16371 render: Arc::new({
16372 let rename_editor = rename_editor.clone();
16373 move |cx: &mut BlockContext| {
16374 let mut text_style = cx.editor_style.text.clone();
16375 if let Some(highlight_style) = old_highlight_id
16376 .and_then(|h| h.style(&cx.editor_style.syntax))
16377 {
16378 text_style = text_style.highlight(highlight_style);
16379 }
16380 div()
16381 .block_mouse_except_scroll()
16382 .pl(cx.anchor_x)
16383 .child(EditorElement::new(
16384 &rename_editor,
16385 EditorStyle {
16386 background: cx.theme().system().transparent,
16387 local_player: cx.editor_style.local_player,
16388 text: text_style,
16389 scrollbar_width: cx.editor_style.scrollbar_width,
16390 syntax: cx.editor_style.syntax.clone(),
16391 status: cx.editor_style.status.clone(),
16392 inlay_hints_style: HighlightStyle {
16393 font_weight: Some(FontWeight::BOLD),
16394 ..make_inlay_hints_style(cx.app)
16395 },
16396 edit_prediction_styles: make_suggestion_styles(
16397 cx.app,
16398 ),
16399 ..EditorStyle::default()
16400 },
16401 ))
16402 .into_any_element()
16403 }
16404 }),
16405 priority: 0,
16406 }],
16407 Some(Autoscroll::fit()),
16408 cx,
16409 )[0];
16410 this.pending_rename = Some(RenameState {
16411 range,
16412 old_name,
16413 editor: rename_editor,
16414 block_id,
16415 });
16416 })?;
16417 }
16418
16419 Ok(())
16420 }))
16421 }
16422
16423 pub fn confirm_rename(
16424 &mut self,
16425 _: &ConfirmRename,
16426 window: &mut Window,
16427 cx: &mut Context<Self>,
16428 ) -> Option<Task<Result<()>>> {
16429 let rename = self.take_rename(false, window, cx)?;
16430 let workspace = self.workspace()?.downgrade();
16431 let (buffer, start) = self
16432 .buffer
16433 .read(cx)
16434 .text_anchor_for_position(rename.range.start, cx)?;
16435 let (end_buffer, _) = self
16436 .buffer
16437 .read(cx)
16438 .text_anchor_for_position(rename.range.end, cx)?;
16439 if buffer != end_buffer {
16440 return None;
16441 }
16442
16443 let old_name = rename.old_name;
16444 let new_name = rename.editor.read(cx).text(cx);
16445
16446 let rename = self.semantics_provider.as_ref()?.perform_rename(
16447 &buffer,
16448 start,
16449 new_name.clone(),
16450 cx,
16451 )?;
16452
16453 Some(cx.spawn_in(window, async move |editor, cx| {
16454 let project_transaction = rename.await?;
16455 Self::open_project_transaction(
16456 &editor,
16457 workspace,
16458 project_transaction,
16459 format!("Rename: {} → {}", old_name, new_name),
16460 cx,
16461 )
16462 .await?;
16463
16464 editor.update(cx, |editor, cx| {
16465 editor.refresh_document_highlights(cx);
16466 })?;
16467 Ok(())
16468 }))
16469 }
16470
16471 fn take_rename(
16472 &mut self,
16473 moving_cursor: bool,
16474 window: &mut Window,
16475 cx: &mut Context<Self>,
16476 ) -> Option<RenameState> {
16477 let rename = self.pending_rename.take()?;
16478 if rename.editor.focus_handle(cx).is_focused(window) {
16479 window.focus(&self.focus_handle);
16480 }
16481
16482 self.remove_blocks(
16483 [rename.block_id].into_iter().collect(),
16484 Some(Autoscroll::fit()),
16485 cx,
16486 );
16487 self.clear_highlights::<Rename>(cx);
16488 self.show_local_selections = true;
16489
16490 if moving_cursor {
16491 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16492 editor.selections.newest::<usize>(cx).head()
16493 });
16494
16495 // Update the selection to match the position of the selection inside
16496 // the rename editor.
16497 let snapshot = self.buffer.read(cx).read(cx);
16498 let rename_range = rename.range.to_offset(&snapshot);
16499 let cursor_in_editor = snapshot
16500 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16501 .min(rename_range.end);
16502 drop(snapshot);
16503
16504 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16505 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16506 });
16507 } else {
16508 self.refresh_document_highlights(cx);
16509 }
16510
16511 Some(rename)
16512 }
16513
16514 pub fn pending_rename(&self) -> Option<&RenameState> {
16515 self.pending_rename.as_ref()
16516 }
16517
16518 fn format(
16519 &mut self,
16520 _: &Format,
16521 window: &mut Window,
16522 cx: &mut Context<Self>,
16523 ) -> Option<Task<Result<()>>> {
16524 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16525
16526 let project = match &self.project {
16527 Some(project) => project.clone(),
16528 None => return None,
16529 };
16530
16531 Some(self.perform_format(
16532 project,
16533 FormatTrigger::Manual,
16534 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16535 window,
16536 cx,
16537 ))
16538 }
16539
16540 fn format_selections(
16541 &mut self,
16542 _: &FormatSelections,
16543 window: &mut Window,
16544 cx: &mut Context<Self>,
16545 ) -> Option<Task<Result<()>>> {
16546 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16547
16548 let project = match &self.project {
16549 Some(project) => project.clone(),
16550 None => return None,
16551 };
16552
16553 let ranges = self
16554 .selections
16555 .all_adjusted(cx)
16556 .into_iter()
16557 .map(|selection| selection.range())
16558 .collect_vec();
16559
16560 Some(self.perform_format(
16561 project,
16562 FormatTrigger::Manual,
16563 FormatTarget::Ranges(ranges),
16564 window,
16565 cx,
16566 ))
16567 }
16568
16569 fn perform_format(
16570 &mut self,
16571 project: Entity<Project>,
16572 trigger: FormatTrigger,
16573 target: FormatTarget,
16574 window: &mut Window,
16575 cx: &mut Context<Self>,
16576 ) -> Task<Result<()>> {
16577 let buffer = self.buffer.clone();
16578 let (buffers, target) = match target {
16579 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16580 FormatTarget::Ranges(selection_ranges) => {
16581 let multi_buffer = buffer.read(cx);
16582 let snapshot = multi_buffer.read(cx);
16583 let mut buffers = HashSet::default();
16584 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16585 BTreeMap::new();
16586 for selection_range in selection_ranges {
16587 for (buffer, buffer_range, _) in
16588 snapshot.range_to_buffer_ranges(selection_range)
16589 {
16590 let buffer_id = buffer.remote_id();
16591 let start = buffer.anchor_before(buffer_range.start);
16592 let end = buffer.anchor_after(buffer_range.end);
16593 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16594 buffer_id_to_ranges
16595 .entry(buffer_id)
16596 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16597 .or_insert_with(|| vec![start..end]);
16598 }
16599 }
16600 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16601 }
16602 };
16603
16604 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16605 let selections_prev = transaction_id_prev
16606 .and_then(|transaction_id_prev| {
16607 // default to selections as they were after the last edit, if we have them,
16608 // instead of how they are now.
16609 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16610 // will take you back to where you made the last edit, instead of staying where you scrolled
16611 self.selection_history
16612 .transaction(transaction_id_prev)
16613 .map(|t| t.0.clone())
16614 })
16615 .unwrap_or_else(|| {
16616 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16617 self.selections.disjoint_anchors()
16618 });
16619
16620 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16621 let format = project.update(cx, |project, cx| {
16622 project.format(buffers, target, true, trigger, cx)
16623 });
16624
16625 cx.spawn_in(window, async move |editor, cx| {
16626 let transaction = futures::select_biased! {
16627 transaction = format.log_err().fuse() => transaction,
16628 () = timeout => {
16629 log::warn!("timed out waiting for formatting");
16630 None
16631 }
16632 };
16633
16634 buffer
16635 .update(cx, |buffer, cx| {
16636 if let Some(transaction) = transaction {
16637 if !buffer.is_singleton() {
16638 buffer.push_transaction(&transaction.0, cx);
16639 }
16640 }
16641 cx.notify();
16642 })
16643 .ok();
16644
16645 if let Some(transaction_id_now) =
16646 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16647 {
16648 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16649 if has_new_transaction {
16650 _ = editor.update(cx, |editor, _| {
16651 editor
16652 .selection_history
16653 .insert_transaction(transaction_id_now, selections_prev);
16654 });
16655 }
16656 }
16657
16658 Ok(())
16659 })
16660 }
16661
16662 fn organize_imports(
16663 &mut self,
16664 _: &OrganizeImports,
16665 window: &mut Window,
16666 cx: &mut Context<Self>,
16667 ) -> Option<Task<Result<()>>> {
16668 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16669 let project = match &self.project {
16670 Some(project) => project.clone(),
16671 None => return None,
16672 };
16673 Some(self.perform_code_action_kind(
16674 project,
16675 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16676 window,
16677 cx,
16678 ))
16679 }
16680
16681 fn perform_code_action_kind(
16682 &mut self,
16683 project: Entity<Project>,
16684 kind: CodeActionKind,
16685 window: &mut Window,
16686 cx: &mut Context<Self>,
16687 ) -> Task<Result<()>> {
16688 let buffer = self.buffer.clone();
16689 let buffers = buffer.read(cx).all_buffers();
16690 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16691 let apply_action = project.update(cx, |project, cx| {
16692 project.apply_code_action_kind(buffers, kind, true, cx)
16693 });
16694 cx.spawn_in(window, async move |_, cx| {
16695 let transaction = futures::select_biased! {
16696 () = timeout => {
16697 log::warn!("timed out waiting for executing code action");
16698 None
16699 }
16700 transaction = apply_action.log_err().fuse() => transaction,
16701 };
16702 buffer
16703 .update(cx, |buffer, cx| {
16704 // check if we need this
16705 if let Some(transaction) = transaction {
16706 if !buffer.is_singleton() {
16707 buffer.push_transaction(&transaction.0, cx);
16708 }
16709 }
16710 cx.notify();
16711 })
16712 .ok();
16713 Ok(())
16714 })
16715 }
16716
16717 pub fn restart_language_server(
16718 &mut self,
16719 _: &RestartLanguageServer,
16720 _: &mut Window,
16721 cx: &mut Context<Self>,
16722 ) {
16723 if let Some(project) = self.project.clone() {
16724 self.buffer.update(cx, |multi_buffer, cx| {
16725 project.update(cx, |project, cx| {
16726 project.restart_language_servers_for_buffers(
16727 multi_buffer.all_buffers().into_iter().collect(),
16728 HashSet::default(),
16729 cx,
16730 );
16731 });
16732 })
16733 }
16734 }
16735
16736 pub fn stop_language_server(
16737 &mut self,
16738 _: &StopLanguageServer,
16739 _: &mut Window,
16740 cx: &mut Context<Self>,
16741 ) {
16742 if let Some(project) = self.project.clone() {
16743 self.buffer.update(cx, |multi_buffer, cx| {
16744 project.update(cx, |project, cx| {
16745 project.stop_language_servers_for_buffers(
16746 multi_buffer.all_buffers().into_iter().collect(),
16747 HashSet::default(),
16748 cx,
16749 );
16750 cx.emit(project::Event::RefreshInlayHints);
16751 });
16752 });
16753 }
16754 }
16755
16756 fn cancel_language_server_work(
16757 workspace: &mut Workspace,
16758 _: &actions::CancelLanguageServerWork,
16759 _: &mut Window,
16760 cx: &mut Context<Workspace>,
16761 ) {
16762 let project = workspace.project();
16763 let buffers = workspace
16764 .active_item(cx)
16765 .and_then(|item| item.act_as::<Editor>(cx))
16766 .map_or(HashSet::default(), |editor| {
16767 editor.read(cx).buffer.read(cx).all_buffers()
16768 });
16769 project.update(cx, |project, cx| {
16770 project.cancel_language_server_work_for_buffers(buffers, cx);
16771 });
16772 }
16773
16774 fn show_character_palette(
16775 &mut self,
16776 _: &ShowCharacterPalette,
16777 window: &mut Window,
16778 _: &mut Context<Self>,
16779 ) {
16780 window.show_character_palette();
16781 }
16782
16783 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16784 if !self.diagnostics_enabled() {
16785 return;
16786 }
16787
16788 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16789 let buffer = self.buffer.read(cx).snapshot(cx);
16790 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16791 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16792 let is_valid = buffer
16793 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16794 .any(|entry| {
16795 entry.diagnostic.is_primary
16796 && !entry.range.is_empty()
16797 && entry.range.start == primary_range_start
16798 && entry.diagnostic.message == active_diagnostics.active_message
16799 });
16800
16801 if !is_valid {
16802 self.dismiss_diagnostics(cx);
16803 }
16804 }
16805 }
16806
16807 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16808 match &self.active_diagnostics {
16809 ActiveDiagnostic::Group(group) => Some(group),
16810 _ => None,
16811 }
16812 }
16813
16814 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16815 if !self.diagnostics_enabled() {
16816 return;
16817 }
16818 self.dismiss_diagnostics(cx);
16819 self.active_diagnostics = ActiveDiagnostic::All;
16820 }
16821
16822 fn activate_diagnostics(
16823 &mut self,
16824 buffer_id: BufferId,
16825 diagnostic: DiagnosticEntry<usize>,
16826 window: &mut Window,
16827 cx: &mut Context<Self>,
16828 ) {
16829 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16830 return;
16831 }
16832 self.dismiss_diagnostics(cx);
16833 let snapshot = self.snapshot(window, cx);
16834 let buffer = self.buffer.read(cx).snapshot(cx);
16835 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16836 return;
16837 };
16838
16839 let diagnostic_group = buffer
16840 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16841 .collect::<Vec<_>>();
16842
16843 let blocks =
16844 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16845
16846 let blocks = self.display_map.update(cx, |display_map, cx| {
16847 display_map.insert_blocks(blocks, cx).into_iter().collect()
16848 });
16849 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16850 active_range: buffer.anchor_before(diagnostic.range.start)
16851 ..buffer.anchor_after(diagnostic.range.end),
16852 active_message: diagnostic.diagnostic.message.clone(),
16853 group_id: diagnostic.diagnostic.group_id,
16854 blocks,
16855 });
16856 cx.notify();
16857 }
16858
16859 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16860 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16861 return;
16862 };
16863
16864 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16865 if let ActiveDiagnostic::Group(group) = prev {
16866 self.display_map.update(cx, |display_map, cx| {
16867 display_map.remove_blocks(group.blocks, cx);
16868 });
16869 cx.notify();
16870 }
16871 }
16872
16873 /// Disable inline diagnostics rendering for this editor.
16874 pub fn disable_inline_diagnostics(&mut self) {
16875 self.inline_diagnostics_enabled = false;
16876 self.inline_diagnostics_update = Task::ready(());
16877 self.inline_diagnostics.clear();
16878 }
16879
16880 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16881 self.diagnostics_enabled = false;
16882 self.dismiss_diagnostics(cx);
16883 self.inline_diagnostics_update = Task::ready(());
16884 self.inline_diagnostics.clear();
16885 }
16886
16887 pub fn diagnostics_enabled(&self) -> bool {
16888 self.diagnostics_enabled && self.mode.is_full()
16889 }
16890
16891 pub fn inline_diagnostics_enabled(&self) -> bool {
16892 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16893 }
16894
16895 pub fn show_inline_diagnostics(&self) -> bool {
16896 self.show_inline_diagnostics
16897 }
16898
16899 pub fn toggle_inline_diagnostics(
16900 &mut self,
16901 _: &ToggleInlineDiagnostics,
16902 window: &mut Window,
16903 cx: &mut Context<Editor>,
16904 ) {
16905 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16906 self.refresh_inline_diagnostics(false, window, cx);
16907 }
16908
16909 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16910 self.diagnostics_max_severity = severity;
16911 self.display_map.update(cx, |display_map, _| {
16912 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16913 });
16914 }
16915
16916 pub fn toggle_diagnostics(
16917 &mut self,
16918 _: &ToggleDiagnostics,
16919 window: &mut Window,
16920 cx: &mut Context<Editor>,
16921 ) {
16922 if !self.diagnostics_enabled() {
16923 return;
16924 }
16925
16926 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16927 EditorSettings::get_global(cx)
16928 .diagnostics_max_severity
16929 .filter(|severity| severity != &DiagnosticSeverity::Off)
16930 .unwrap_or(DiagnosticSeverity::Hint)
16931 } else {
16932 DiagnosticSeverity::Off
16933 };
16934 self.set_max_diagnostics_severity(new_severity, cx);
16935 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16936 self.active_diagnostics = ActiveDiagnostic::None;
16937 self.inline_diagnostics_update = Task::ready(());
16938 self.inline_diagnostics.clear();
16939 } else {
16940 self.refresh_inline_diagnostics(false, window, cx);
16941 }
16942
16943 cx.notify();
16944 }
16945
16946 pub fn toggle_minimap(
16947 &mut self,
16948 _: &ToggleMinimap,
16949 window: &mut Window,
16950 cx: &mut Context<Editor>,
16951 ) {
16952 if self.supports_minimap(cx) {
16953 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16954 }
16955 }
16956
16957 fn refresh_inline_diagnostics(
16958 &mut self,
16959 debounce: bool,
16960 window: &mut Window,
16961 cx: &mut Context<Self>,
16962 ) {
16963 let max_severity = ProjectSettings::get_global(cx)
16964 .diagnostics
16965 .inline
16966 .max_severity
16967 .unwrap_or(self.diagnostics_max_severity);
16968
16969 if !self.inline_diagnostics_enabled()
16970 || !self.show_inline_diagnostics
16971 || max_severity == DiagnosticSeverity::Off
16972 {
16973 self.inline_diagnostics_update = Task::ready(());
16974 self.inline_diagnostics.clear();
16975 return;
16976 }
16977
16978 let debounce_ms = ProjectSettings::get_global(cx)
16979 .diagnostics
16980 .inline
16981 .update_debounce_ms;
16982 let debounce = if debounce && debounce_ms > 0 {
16983 Some(Duration::from_millis(debounce_ms))
16984 } else {
16985 None
16986 };
16987 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16988 if let Some(debounce) = debounce {
16989 cx.background_executor().timer(debounce).await;
16990 }
16991 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16992 editor
16993 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16994 .ok()
16995 }) else {
16996 return;
16997 };
16998
16999 let new_inline_diagnostics = cx
17000 .background_spawn(async move {
17001 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17002 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17003 let message = diagnostic_entry
17004 .diagnostic
17005 .message
17006 .split_once('\n')
17007 .map(|(line, _)| line)
17008 .map(SharedString::new)
17009 .unwrap_or_else(|| {
17010 SharedString::from(diagnostic_entry.diagnostic.message)
17011 });
17012 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17013 let (Ok(i) | Err(i)) = inline_diagnostics
17014 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17015 inline_diagnostics.insert(
17016 i,
17017 (
17018 start_anchor,
17019 InlineDiagnostic {
17020 message,
17021 group_id: diagnostic_entry.diagnostic.group_id,
17022 start: diagnostic_entry.range.start.to_point(&snapshot),
17023 is_primary: diagnostic_entry.diagnostic.is_primary,
17024 severity: diagnostic_entry.diagnostic.severity,
17025 },
17026 ),
17027 );
17028 }
17029 inline_diagnostics
17030 })
17031 .await;
17032
17033 editor
17034 .update(cx, |editor, cx| {
17035 editor.inline_diagnostics = new_inline_diagnostics;
17036 cx.notify();
17037 })
17038 .ok();
17039 });
17040 }
17041
17042 fn pull_diagnostics(
17043 &mut self,
17044 buffer_id: Option<BufferId>,
17045 window: &Window,
17046 cx: &mut Context<Self>,
17047 ) -> Option<()> {
17048 if !self.mode().is_full() {
17049 return None;
17050 }
17051 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17052 .diagnostics
17053 .lsp_pull_diagnostics;
17054 if !pull_diagnostics_settings.enabled {
17055 return None;
17056 }
17057 let project = self.project.as_ref()?.downgrade();
17058 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17059 let mut buffers = self.buffer.read(cx).all_buffers();
17060 if let Some(buffer_id) = buffer_id {
17061 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17062 }
17063
17064 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17065 cx.background_executor().timer(debounce).await;
17066
17067 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17068 buffers
17069 .into_iter()
17070 .filter_map(|buffer| {
17071 project
17072 .update(cx, |project, cx| {
17073 project.lsp_store().update(cx, |lsp_store, cx| {
17074 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17075 })
17076 })
17077 .ok()
17078 })
17079 .collect::<FuturesUnordered<_>>()
17080 }) else {
17081 return;
17082 };
17083
17084 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17085 match pull_task {
17086 Ok(()) => {
17087 if editor
17088 .update_in(cx, |editor, window, cx| {
17089 editor.update_diagnostics_state(window, cx);
17090 })
17091 .is_err()
17092 {
17093 return;
17094 }
17095 }
17096 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17097 }
17098 }
17099 });
17100
17101 Some(())
17102 }
17103
17104 pub fn set_selections_from_remote(
17105 &mut self,
17106 selections: Vec<Selection<Anchor>>,
17107 pending_selection: Option<Selection<Anchor>>,
17108 window: &mut Window,
17109 cx: &mut Context<Self>,
17110 ) {
17111 let old_cursor_position = self.selections.newest_anchor().head();
17112 self.selections.change_with(cx, |s| {
17113 s.select_anchors(selections);
17114 if let Some(pending_selection) = pending_selection {
17115 s.set_pending(pending_selection, SelectMode::Character);
17116 } else {
17117 s.clear_pending();
17118 }
17119 });
17120 self.selections_did_change(
17121 false,
17122 &old_cursor_position,
17123 SelectionEffects::default(),
17124 window,
17125 cx,
17126 );
17127 }
17128
17129 pub fn transact(
17130 &mut self,
17131 window: &mut Window,
17132 cx: &mut Context<Self>,
17133 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17134 ) -> Option<TransactionId> {
17135 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17136 this.start_transaction_at(Instant::now(), window, cx);
17137 update(this, window, cx);
17138 this.end_transaction_at(Instant::now(), cx)
17139 })
17140 }
17141
17142 pub fn start_transaction_at(
17143 &mut self,
17144 now: Instant,
17145 window: &mut Window,
17146 cx: &mut Context<Self>,
17147 ) -> Option<TransactionId> {
17148 self.end_selection(window, cx);
17149 if let Some(tx_id) = self
17150 .buffer
17151 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17152 {
17153 self.selection_history
17154 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17155 cx.emit(EditorEvent::TransactionBegun {
17156 transaction_id: tx_id,
17157 });
17158 Some(tx_id)
17159 } else {
17160 None
17161 }
17162 }
17163
17164 pub fn end_transaction_at(
17165 &mut self,
17166 now: Instant,
17167 cx: &mut Context<Self>,
17168 ) -> Option<TransactionId> {
17169 if let Some(transaction_id) = self
17170 .buffer
17171 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17172 {
17173 if let Some((_, end_selections)) =
17174 self.selection_history.transaction_mut(transaction_id)
17175 {
17176 *end_selections = Some(self.selections.disjoint_anchors());
17177 } else {
17178 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17179 }
17180
17181 cx.emit(EditorEvent::Edited { transaction_id });
17182 Some(transaction_id)
17183 } else {
17184 None
17185 }
17186 }
17187
17188 pub fn modify_transaction_selection_history(
17189 &mut self,
17190 transaction_id: TransactionId,
17191 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17192 ) -> bool {
17193 self.selection_history
17194 .transaction_mut(transaction_id)
17195 .map(modify)
17196 .is_some()
17197 }
17198
17199 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17200 if self.selection_mark_mode {
17201 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17202 s.move_with(|_, sel| {
17203 sel.collapse_to(sel.head(), SelectionGoal::None);
17204 });
17205 })
17206 }
17207 self.selection_mark_mode = true;
17208 cx.notify();
17209 }
17210
17211 pub fn swap_selection_ends(
17212 &mut self,
17213 _: &actions::SwapSelectionEnds,
17214 window: &mut Window,
17215 cx: &mut Context<Self>,
17216 ) {
17217 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17218 s.move_with(|_, sel| {
17219 if sel.start != sel.end {
17220 sel.reversed = !sel.reversed
17221 }
17222 });
17223 });
17224 self.request_autoscroll(Autoscroll::newest(), cx);
17225 cx.notify();
17226 }
17227
17228 pub fn toggle_focus(
17229 workspace: &mut Workspace,
17230 _: &actions::ToggleFocus,
17231 window: &mut Window,
17232 cx: &mut Context<Workspace>,
17233 ) {
17234 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17235 return;
17236 };
17237 workspace.activate_item(&item, true, true, window, cx);
17238 }
17239
17240 pub fn toggle_fold(
17241 &mut self,
17242 _: &actions::ToggleFold,
17243 window: &mut Window,
17244 cx: &mut Context<Self>,
17245 ) {
17246 if self.is_singleton(cx) {
17247 let selection = self.selections.newest::<Point>(cx);
17248
17249 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17250 let range = if selection.is_empty() {
17251 let point = selection.head().to_display_point(&display_map);
17252 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17253 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17254 .to_point(&display_map);
17255 start..end
17256 } else {
17257 selection.range()
17258 };
17259 if display_map.folds_in_range(range).next().is_some() {
17260 self.unfold_lines(&Default::default(), window, cx)
17261 } else {
17262 self.fold(&Default::default(), window, cx)
17263 }
17264 } else {
17265 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17266 let buffer_ids: HashSet<_> = self
17267 .selections
17268 .disjoint_anchor_ranges()
17269 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17270 .collect();
17271
17272 let should_unfold = buffer_ids
17273 .iter()
17274 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17275
17276 for buffer_id in buffer_ids {
17277 if should_unfold {
17278 self.unfold_buffer(buffer_id, cx);
17279 } else {
17280 self.fold_buffer(buffer_id, cx);
17281 }
17282 }
17283 }
17284 }
17285
17286 pub fn toggle_fold_recursive(
17287 &mut self,
17288 _: &actions::ToggleFoldRecursive,
17289 window: &mut Window,
17290 cx: &mut Context<Self>,
17291 ) {
17292 let selection = self.selections.newest::<Point>(cx);
17293
17294 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17295 let range = if selection.is_empty() {
17296 let point = selection.head().to_display_point(&display_map);
17297 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17298 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17299 .to_point(&display_map);
17300 start..end
17301 } else {
17302 selection.range()
17303 };
17304 if display_map.folds_in_range(range).next().is_some() {
17305 self.unfold_recursive(&Default::default(), window, cx)
17306 } else {
17307 self.fold_recursive(&Default::default(), window, cx)
17308 }
17309 }
17310
17311 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17312 if self.is_singleton(cx) {
17313 let mut to_fold = Vec::new();
17314 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17315 let selections = self.selections.all_adjusted(cx);
17316
17317 for selection in selections {
17318 let range = selection.range().sorted();
17319 let buffer_start_row = range.start.row;
17320
17321 if range.start.row != range.end.row {
17322 let mut found = false;
17323 let mut row = range.start.row;
17324 while row <= range.end.row {
17325 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17326 {
17327 found = true;
17328 row = crease.range().end.row + 1;
17329 to_fold.push(crease);
17330 } else {
17331 row += 1
17332 }
17333 }
17334 if found {
17335 continue;
17336 }
17337 }
17338
17339 for row in (0..=range.start.row).rev() {
17340 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17341 if crease.range().end.row >= buffer_start_row {
17342 to_fold.push(crease);
17343 if row <= range.start.row {
17344 break;
17345 }
17346 }
17347 }
17348 }
17349 }
17350
17351 self.fold_creases(to_fold, true, window, cx);
17352 } else {
17353 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17354 let buffer_ids = self
17355 .selections
17356 .disjoint_anchor_ranges()
17357 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17358 .collect::<HashSet<_>>();
17359 for buffer_id in buffer_ids {
17360 self.fold_buffer(buffer_id, cx);
17361 }
17362 }
17363 }
17364
17365 pub fn toggle_fold_all(
17366 &mut self,
17367 _: &actions::ToggleFoldAll,
17368 window: &mut Window,
17369 cx: &mut Context<Self>,
17370 ) {
17371 if self.buffer.read(cx).is_singleton() {
17372 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17373 let has_folds = display_map
17374 .folds_in_range(0..display_map.buffer_snapshot.len())
17375 .next()
17376 .is_some();
17377
17378 if has_folds {
17379 self.unfold_all(&actions::UnfoldAll, window, cx);
17380 } else {
17381 self.fold_all(&actions::FoldAll, window, cx);
17382 }
17383 } else {
17384 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17385 let should_unfold = buffer_ids
17386 .iter()
17387 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17388
17389 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17390 editor
17391 .update_in(cx, |editor, _, cx| {
17392 for buffer_id in buffer_ids {
17393 if should_unfold {
17394 editor.unfold_buffer(buffer_id, cx);
17395 } else {
17396 editor.fold_buffer(buffer_id, cx);
17397 }
17398 }
17399 })
17400 .ok();
17401 });
17402 }
17403 }
17404
17405 fn fold_at_level(
17406 &mut self,
17407 fold_at: &FoldAtLevel,
17408 window: &mut Window,
17409 cx: &mut Context<Self>,
17410 ) {
17411 if !self.buffer.read(cx).is_singleton() {
17412 return;
17413 }
17414
17415 let fold_at_level = fold_at.0;
17416 let snapshot = self.buffer.read(cx).snapshot(cx);
17417 let mut to_fold = Vec::new();
17418 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17419
17420 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17421 while start_row < end_row {
17422 match self
17423 .snapshot(window, cx)
17424 .crease_for_buffer_row(MultiBufferRow(start_row))
17425 {
17426 Some(crease) => {
17427 let nested_start_row = crease.range().start.row + 1;
17428 let nested_end_row = crease.range().end.row;
17429
17430 if current_level < fold_at_level {
17431 stack.push((nested_start_row, nested_end_row, current_level + 1));
17432 } else if current_level == fold_at_level {
17433 to_fold.push(crease);
17434 }
17435
17436 start_row = nested_end_row + 1;
17437 }
17438 None => start_row += 1,
17439 }
17440 }
17441 }
17442
17443 self.fold_creases(to_fold, true, window, cx);
17444 }
17445
17446 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17447 if self.buffer.read(cx).is_singleton() {
17448 let mut fold_ranges = Vec::new();
17449 let snapshot = self.buffer.read(cx).snapshot(cx);
17450
17451 for row in 0..snapshot.max_row().0 {
17452 if let Some(foldable_range) = self
17453 .snapshot(window, cx)
17454 .crease_for_buffer_row(MultiBufferRow(row))
17455 {
17456 fold_ranges.push(foldable_range);
17457 }
17458 }
17459
17460 self.fold_creases(fold_ranges, true, window, cx);
17461 } else {
17462 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17463 editor
17464 .update_in(cx, |editor, _, cx| {
17465 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17466 editor.fold_buffer(buffer_id, cx);
17467 }
17468 })
17469 .ok();
17470 });
17471 }
17472 }
17473
17474 pub fn fold_function_bodies(
17475 &mut self,
17476 _: &actions::FoldFunctionBodies,
17477 window: &mut Window,
17478 cx: &mut Context<Self>,
17479 ) {
17480 let snapshot = self.buffer.read(cx).snapshot(cx);
17481
17482 let ranges = snapshot
17483 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17484 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17485 .collect::<Vec<_>>();
17486
17487 let creases = ranges
17488 .into_iter()
17489 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17490 .collect();
17491
17492 self.fold_creases(creases, true, window, cx);
17493 }
17494
17495 pub fn fold_recursive(
17496 &mut self,
17497 _: &actions::FoldRecursive,
17498 window: &mut Window,
17499 cx: &mut Context<Self>,
17500 ) {
17501 let mut to_fold = Vec::new();
17502 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17503 let selections = self.selections.all_adjusted(cx);
17504
17505 for selection in selections {
17506 let range = selection.range().sorted();
17507 let buffer_start_row = range.start.row;
17508
17509 if range.start.row != range.end.row {
17510 let mut found = false;
17511 for row in range.start.row..=range.end.row {
17512 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17513 found = true;
17514 to_fold.push(crease);
17515 }
17516 }
17517 if found {
17518 continue;
17519 }
17520 }
17521
17522 for row in (0..=range.start.row).rev() {
17523 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17524 if crease.range().end.row >= buffer_start_row {
17525 to_fold.push(crease);
17526 } else {
17527 break;
17528 }
17529 }
17530 }
17531 }
17532
17533 self.fold_creases(to_fold, true, window, cx);
17534 }
17535
17536 pub fn fold_at(
17537 &mut self,
17538 buffer_row: MultiBufferRow,
17539 window: &mut Window,
17540 cx: &mut Context<Self>,
17541 ) {
17542 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17543
17544 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17545 let autoscroll = self
17546 .selections
17547 .all::<Point>(cx)
17548 .iter()
17549 .any(|selection| crease.range().overlaps(&selection.range()));
17550
17551 self.fold_creases(vec![crease], autoscroll, window, cx);
17552 }
17553 }
17554
17555 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17556 if self.is_singleton(cx) {
17557 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17558 let buffer = &display_map.buffer_snapshot;
17559 let selections = self.selections.all::<Point>(cx);
17560 let ranges = selections
17561 .iter()
17562 .map(|s| {
17563 let range = s.display_range(&display_map).sorted();
17564 let mut start = range.start.to_point(&display_map);
17565 let mut end = range.end.to_point(&display_map);
17566 start.column = 0;
17567 end.column = buffer.line_len(MultiBufferRow(end.row));
17568 start..end
17569 })
17570 .collect::<Vec<_>>();
17571
17572 self.unfold_ranges(&ranges, true, true, cx);
17573 } else {
17574 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17575 let buffer_ids = self
17576 .selections
17577 .disjoint_anchor_ranges()
17578 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17579 .collect::<HashSet<_>>();
17580 for buffer_id in buffer_ids {
17581 self.unfold_buffer(buffer_id, cx);
17582 }
17583 }
17584 }
17585
17586 pub fn unfold_recursive(
17587 &mut self,
17588 _: &UnfoldRecursive,
17589 _window: &mut Window,
17590 cx: &mut Context<Self>,
17591 ) {
17592 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17593 let selections = self.selections.all::<Point>(cx);
17594 let ranges = selections
17595 .iter()
17596 .map(|s| {
17597 let mut range = s.display_range(&display_map).sorted();
17598 *range.start.column_mut() = 0;
17599 *range.end.column_mut() = display_map.line_len(range.end.row());
17600 let start = range.start.to_point(&display_map);
17601 let end = range.end.to_point(&display_map);
17602 start..end
17603 })
17604 .collect::<Vec<_>>();
17605
17606 self.unfold_ranges(&ranges, true, true, cx);
17607 }
17608
17609 pub fn unfold_at(
17610 &mut self,
17611 buffer_row: MultiBufferRow,
17612 _window: &mut Window,
17613 cx: &mut Context<Self>,
17614 ) {
17615 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17616
17617 let intersection_range = Point::new(buffer_row.0, 0)
17618 ..Point::new(
17619 buffer_row.0,
17620 display_map.buffer_snapshot.line_len(buffer_row),
17621 );
17622
17623 let autoscroll = self
17624 .selections
17625 .all::<Point>(cx)
17626 .iter()
17627 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17628
17629 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17630 }
17631
17632 pub fn unfold_all(
17633 &mut self,
17634 _: &actions::UnfoldAll,
17635 _window: &mut Window,
17636 cx: &mut Context<Self>,
17637 ) {
17638 if self.buffer.read(cx).is_singleton() {
17639 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17640 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17641 } else {
17642 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17643 editor
17644 .update(cx, |editor, cx| {
17645 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17646 editor.unfold_buffer(buffer_id, cx);
17647 }
17648 })
17649 .ok();
17650 });
17651 }
17652 }
17653
17654 pub fn fold_selected_ranges(
17655 &mut self,
17656 _: &FoldSelectedRanges,
17657 window: &mut Window,
17658 cx: &mut Context<Self>,
17659 ) {
17660 let selections = self.selections.all_adjusted(cx);
17661 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17662 let ranges = selections
17663 .into_iter()
17664 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17665 .collect::<Vec<_>>();
17666 self.fold_creases(ranges, true, window, cx);
17667 }
17668
17669 pub fn fold_ranges<T: ToOffset + Clone>(
17670 &mut self,
17671 ranges: Vec<Range<T>>,
17672 auto_scroll: bool,
17673 window: &mut Window,
17674 cx: &mut Context<Self>,
17675 ) {
17676 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17677 let ranges = ranges
17678 .into_iter()
17679 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17680 .collect::<Vec<_>>();
17681 self.fold_creases(ranges, auto_scroll, window, cx);
17682 }
17683
17684 pub fn fold_creases<T: ToOffset + Clone>(
17685 &mut self,
17686 creases: Vec<Crease<T>>,
17687 auto_scroll: bool,
17688 _window: &mut Window,
17689 cx: &mut Context<Self>,
17690 ) {
17691 if creases.is_empty() {
17692 return;
17693 }
17694
17695 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17696
17697 if auto_scroll {
17698 self.request_autoscroll(Autoscroll::fit(), cx);
17699 }
17700
17701 cx.notify();
17702
17703 self.scrollbar_marker_state.dirty = true;
17704 self.folds_did_change(cx);
17705 }
17706
17707 /// Removes any folds whose ranges intersect any of the given ranges.
17708 pub fn unfold_ranges<T: ToOffset + Clone>(
17709 &mut self,
17710 ranges: &[Range<T>],
17711 inclusive: bool,
17712 auto_scroll: bool,
17713 cx: &mut Context<Self>,
17714 ) {
17715 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17716 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17717 });
17718 self.folds_did_change(cx);
17719 }
17720
17721 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17722 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17723 return;
17724 }
17725 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17726 self.display_map.update(cx, |display_map, cx| {
17727 display_map.fold_buffers([buffer_id], cx)
17728 });
17729 cx.emit(EditorEvent::BufferFoldToggled {
17730 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17731 folded: true,
17732 });
17733 cx.notify();
17734 }
17735
17736 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17737 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17738 return;
17739 }
17740 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17741 self.display_map.update(cx, |display_map, cx| {
17742 display_map.unfold_buffers([buffer_id], cx);
17743 });
17744 cx.emit(EditorEvent::BufferFoldToggled {
17745 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17746 folded: false,
17747 });
17748 cx.notify();
17749 }
17750
17751 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17752 self.display_map.read(cx).is_buffer_folded(buffer)
17753 }
17754
17755 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17756 self.display_map.read(cx).folded_buffers()
17757 }
17758
17759 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17760 self.display_map.update(cx, |display_map, cx| {
17761 display_map.disable_header_for_buffer(buffer_id, cx);
17762 });
17763 cx.notify();
17764 }
17765
17766 /// Removes any folds with the given ranges.
17767 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17768 &mut self,
17769 ranges: &[Range<T>],
17770 type_id: TypeId,
17771 auto_scroll: bool,
17772 cx: &mut Context<Self>,
17773 ) {
17774 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17775 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17776 });
17777 self.folds_did_change(cx);
17778 }
17779
17780 fn remove_folds_with<T: ToOffset + Clone>(
17781 &mut self,
17782 ranges: &[Range<T>],
17783 auto_scroll: bool,
17784 cx: &mut Context<Self>,
17785 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17786 ) {
17787 if ranges.is_empty() {
17788 return;
17789 }
17790
17791 let mut buffers_affected = HashSet::default();
17792 let multi_buffer = self.buffer().read(cx);
17793 for range in ranges {
17794 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17795 buffers_affected.insert(buffer.read(cx).remote_id());
17796 };
17797 }
17798
17799 self.display_map.update(cx, update);
17800
17801 if auto_scroll {
17802 self.request_autoscroll(Autoscroll::fit(), cx);
17803 }
17804
17805 cx.notify();
17806 self.scrollbar_marker_state.dirty = true;
17807 self.active_indent_guides_state.dirty = true;
17808 }
17809
17810 pub fn update_renderer_widths(
17811 &mut self,
17812 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17813 cx: &mut Context<Self>,
17814 ) -> bool {
17815 self.display_map
17816 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17817 }
17818
17819 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17820 self.display_map.read(cx).fold_placeholder.clone()
17821 }
17822
17823 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17824 self.buffer.update(cx, |buffer, cx| {
17825 buffer.set_all_diff_hunks_expanded(cx);
17826 });
17827 }
17828
17829 pub fn expand_all_diff_hunks(
17830 &mut self,
17831 _: &ExpandAllDiffHunks,
17832 _window: &mut Window,
17833 cx: &mut Context<Self>,
17834 ) {
17835 self.buffer.update(cx, |buffer, cx| {
17836 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17837 });
17838 }
17839
17840 pub fn toggle_selected_diff_hunks(
17841 &mut self,
17842 _: &ToggleSelectedDiffHunks,
17843 _window: &mut Window,
17844 cx: &mut Context<Self>,
17845 ) {
17846 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17847 self.toggle_diff_hunks_in_ranges(ranges, cx);
17848 }
17849
17850 pub fn diff_hunks_in_ranges<'a>(
17851 &'a self,
17852 ranges: &'a [Range<Anchor>],
17853 buffer: &'a MultiBufferSnapshot,
17854 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17855 ranges.iter().flat_map(move |range| {
17856 let end_excerpt_id = range.end.excerpt_id;
17857 let range = range.to_point(buffer);
17858 let mut peek_end = range.end;
17859 if range.end.row < buffer.max_row().0 {
17860 peek_end = Point::new(range.end.row + 1, 0);
17861 }
17862 buffer
17863 .diff_hunks_in_range(range.start..peek_end)
17864 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17865 })
17866 }
17867
17868 pub fn has_stageable_diff_hunks_in_ranges(
17869 &self,
17870 ranges: &[Range<Anchor>],
17871 snapshot: &MultiBufferSnapshot,
17872 ) -> bool {
17873 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17874 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17875 }
17876
17877 pub fn toggle_staged_selected_diff_hunks(
17878 &mut self,
17879 _: &::git::ToggleStaged,
17880 _: &mut Window,
17881 cx: &mut Context<Self>,
17882 ) {
17883 let snapshot = self.buffer.read(cx).snapshot(cx);
17884 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17885 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17886 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17887 }
17888
17889 pub fn set_render_diff_hunk_controls(
17890 &mut self,
17891 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17892 cx: &mut Context<Self>,
17893 ) {
17894 self.render_diff_hunk_controls = render_diff_hunk_controls;
17895 cx.notify();
17896 }
17897
17898 pub fn stage_and_next(
17899 &mut self,
17900 _: &::git::StageAndNext,
17901 window: &mut Window,
17902 cx: &mut Context<Self>,
17903 ) {
17904 self.do_stage_or_unstage_and_next(true, window, cx);
17905 }
17906
17907 pub fn unstage_and_next(
17908 &mut self,
17909 _: &::git::UnstageAndNext,
17910 window: &mut Window,
17911 cx: &mut Context<Self>,
17912 ) {
17913 self.do_stage_or_unstage_and_next(false, window, cx);
17914 }
17915
17916 pub fn stage_or_unstage_diff_hunks(
17917 &mut self,
17918 stage: bool,
17919 ranges: Vec<Range<Anchor>>,
17920 cx: &mut Context<Self>,
17921 ) {
17922 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17923 cx.spawn(async move |this, cx| {
17924 task.await?;
17925 this.update(cx, |this, cx| {
17926 let snapshot = this.buffer.read(cx).snapshot(cx);
17927 let chunk_by = this
17928 .diff_hunks_in_ranges(&ranges, &snapshot)
17929 .chunk_by(|hunk| hunk.buffer_id);
17930 for (buffer_id, hunks) in &chunk_by {
17931 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17932 }
17933 })
17934 })
17935 .detach_and_log_err(cx);
17936 }
17937
17938 fn save_buffers_for_ranges_if_needed(
17939 &mut self,
17940 ranges: &[Range<Anchor>],
17941 cx: &mut Context<Editor>,
17942 ) -> Task<Result<()>> {
17943 let multibuffer = self.buffer.read(cx);
17944 let snapshot = multibuffer.read(cx);
17945 let buffer_ids: HashSet<_> = ranges
17946 .iter()
17947 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17948 .collect();
17949 drop(snapshot);
17950
17951 let mut buffers = HashSet::default();
17952 for buffer_id in buffer_ids {
17953 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17954 let buffer = buffer_entity.read(cx);
17955 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17956 {
17957 buffers.insert(buffer_entity);
17958 }
17959 }
17960 }
17961
17962 if let Some(project) = &self.project {
17963 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17964 } else {
17965 Task::ready(Ok(()))
17966 }
17967 }
17968
17969 fn do_stage_or_unstage_and_next(
17970 &mut self,
17971 stage: bool,
17972 window: &mut Window,
17973 cx: &mut Context<Self>,
17974 ) {
17975 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17976
17977 if ranges.iter().any(|range| range.start != range.end) {
17978 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17979 return;
17980 }
17981
17982 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17983 let snapshot = self.snapshot(window, cx);
17984 let position = self.selections.newest::<Point>(cx).head();
17985 let mut row = snapshot
17986 .buffer_snapshot
17987 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17988 .find(|hunk| hunk.row_range.start.0 > position.row)
17989 .map(|hunk| hunk.row_range.start);
17990
17991 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17992 // Outside of the project diff editor, wrap around to the beginning.
17993 if !all_diff_hunks_expanded {
17994 row = row.or_else(|| {
17995 snapshot
17996 .buffer_snapshot
17997 .diff_hunks_in_range(Point::zero()..position)
17998 .find(|hunk| hunk.row_range.end.0 < position.row)
17999 .map(|hunk| hunk.row_range.start)
18000 });
18001 }
18002
18003 if let Some(row) = row {
18004 let destination = Point::new(row.0, 0);
18005 let autoscroll = Autoscroll::center();
18006
18007 self.unfold_ranges(&[destination..destination], false, false, cx);
18008 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18009 s.select_ranges([destination..destination]);
18010 });
18011 }
18012 }
18013
18014 fn do_stage_or_unstage(
18015 &self,
18016 stage: bool,
18017 buffer_id: BufferId,
18018 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18019 cx: &mut App,
18020 ) -> Option<()> {
18021 let project = self.project.as_ref()?;
18022 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18023 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18024 let buffer_snapshot = buffer.read(cx).snapshot();
18025 let file_exists = buffer_snapshot
18026 .file()
18027 .is_some_and(|file| file.disk_state().exists());
18028 diff.update(cx, |diff, cx| {
18029 diff.stage_or_unstage_hunks(
18030 stage,
18031 &hunks
18032 .map(|hunk| buffer_diff::DiffHunk {
18033 buffer_range: hunk.buffer_range,
18034 diff_base_byte_range: hunk.diff_base_byte_range,
18035 secondary_status: hunk.secondary_status,
18036 range: Point::zero()..Point::zero(), // unused
18037 })
18038 .collect::<Vec<_>>(),
18039 &buffer_snapshot,
18040 file_exists,
18041 cx,
18042 )
18043 });
18044 None
18045 }
18046
18047 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18048 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18049 self.buffer
18050 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18051 }
18052
18053 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18054 self.buffer.update(cx, |buffer, cx| {
18055 let ranges = vec![Anchor::min()..Anchor::max()];
18056 if !buffer.all_diff_hunks_expanded()
18057 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18058 {
18059 buffer.collapse_diff_hunks(ranges, cx);
18060 true
18061 } else {
18062 false
18063 }
18064 })
18065 }
18066
18067 fn toggle_diff_hunks_in_ranges(
18068 &mut self,
18069 ranges: Vec<Range<Anchor>>,
18070 cx: &mut Context<Editor>,
18071 ) {
18072 self.buffer.update(cx, |buffer, cx| {
18073 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18074 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18075 })
18076 }
18077
18078 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18079 self.buffer.update(cx, |buffer, cx| {
18080 let snapshot = buffer.snapshot(cx);
18081 let excerpt_id = range.end.excerpt_id;
18082 let point_range = range.to_point(&snapshot);
18083 let expand = !buffer.single_hunk_is_expanded(range, cx);
18084 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18085 })
18086 }
18087
18088 pub(crate) fn apply_all_diff_hunks(
18089 &mut self,
18090 _: &ApplyAllDiffHunks,
18091 window: &mut Window,
18092 cx: &mut Context<Self>,
18093 ) {
18094 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18095
18096 let buffers = self.buffer.read(cx).all_buffers();
18097 for branch_buffer in buffers {
18098 branch_buffer.update(cx, |branch_buffer, cx| {
18099 branch_buffer.merge_into_base(Vec::new(), cx);
18100 });
18101 }
18102
18103 if let Some(project) = self.project.clone() {
18104 self.save(
18105 SaveOptions {
18106 format: true,
18107 autosave: false,
18108 },
18109 project,
18110 window,
18111 cx,
18112 )
18113 .detach_and_log_err(cx);
18114 }
18115 }
18116
18117 pub(crate) fn apply_selected_diff_hunks(
18118 &mut self,
18119 _: &ApplyDiffHunk,
18120 window: &mut Window,
18121 cx: &mut Context<Self>,
18122 ) {
18123 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18124 let snapshot = self.snapshot(window, cx);
18125 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18126 let mut ranges_by_buffer = HashMap::default();
18127 self.transact(window, cx, |editor, _window, cx| {
18128 for hunk in hunks {
18129 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18130 ranges_by_buffer
18131 .entry(buffer.clone())
18132 .or_insert_with(Vec::new)
18133 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18134 }
18135 }
18136
18137 for (buffer, ranges) in ranges_by_buffer {
18138 buffer.update(cx, |buffer, cx| {
18139 buffer.merge_into_base(ranges, cx);
18140 });
18141 }
18142 });
18143
18144 if let Some(project) = self.project.clone() {
18145 self.save(
18146 SaveOptions {
18147 format: true,
18148 autosave: false,
18149 },
18150 project,
18151 window,
18152 cx,
18153 )
18154 .detach_and_log_err(cx);
18155 }
18156 }
18157
18158 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18159 if hovered != self.gutter_hovered {
18160 self.gutter_hovered = hovered;
18161 cx.notify();
18162 }
18163 }
18164
18165 pub fn insert_blocks(
18166 &mut self,
18167 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18168 autoscroll: Option<Autoscroll>,
18169 cx: &mut Context<Self>,
18170 ) -> Vec<CustomBlockId> {
18171 let blocks = self
18172 .display_map
18173 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18174 if let Some(autoscroll) = autoscroll {
18175 self.request_autoscroll(autoscroll, cx);
18176 }
18177 cx.notify();
18178 blocks
18179 }
18180
18181 pub fn resize_blocks(
18182 &mut self,
18183 heights: HashMap<CustomBlockId, u32>,
18184 autoscroll: Option<Autoscroll>,
18185 cx: &mut Context<Self>,
18186 ) {
18187 self.display_map
18188 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18189 if let Some(autoscroll) = autoscroll {
18190 self.request_autoscroll(autoscroll, cx);
18191 }
18192 cx.notify();
18193 }
18194
18195 pub fn replace_blocks(
18196 &mut self,
18197 renderers: HashMap<CustomBlockId, RenderBlock>,
18198 autoscroll: Option<Autoscroll>,
18199 cx: &mut Context<Self>,
18200 ) {
18201 self.display_map
18202 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18203 if let Some(autoscroll) = autoscroll {
18204 self.request_autoscroll(autoscroll, cx);
18205 }
18206 cx.notify();
18207 }
18208
18209 pub fn remove_blocks(
18210 &mut self,
18211 block_ids: HashSet<CustomBlockId>,
18212 autoscroll: Option<Autoscroll>,
18213 cx: &mut Context<Self>,
18214 ) {
18215 self.display_map.update(cx, |display_map, cx| {
18216 display_map.remove_blocks(block_ids, cx)
18217 });
18218 if let Some(autoscroll) = autoscroll {
18219 self.request_autoscroll(autoscroll, cx);
18220 }
18221 cx.notify();
18222 }
18223
18224 pub fn row_for_block(
18225 &self,
18226 block_id: CustomBlockId,
18227 cx: &mut Context<Self>,
18228 ) -> Option<DisplayRow> {
18229 self.display_map
18230 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18231 }
18232
18233 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18234 self.focused_block = Some(focused_block);
18235 }
18236
18237 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18238 self.focused_block.take()
18239 }
18240
18241 pub fn insert_creases(
18242 &mut self,
18243 creases: impl IntoIterator<Item = Crease<Anchor>>,
18244 cx: &mut Context<Self>,
18245 ) -> Vec<CreaseId> {
18246 self.display_map
18247 .update(cx, |map, cx| map.insert_creases(creases, cx))
18248 }
18249
18250 pub fn remove_creases(
18251 &mut self,
18252 ids: impl IntoIterator<Item = CreaseId>,
18253 cx: &mut Context<Self>,
18254 ) -> Vec<(CreaseId, Range<Anchor>)> {
18255 self.display_map
18256 .update(cx, |map, cx| map.remove_creases(ids, cx))
18257 }
18258
18259 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18260 self.display_map
18261 .update(cx, |map, cx| map.snapshot(cx))
18262 .longest_row()
18263 }
18264
18265 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18266 self.display_map
18267 .update(cx, |map, cx| map.snapshot(cx))
18268 .max_point()
18269 }
18270
18271 pub fn text(&self, cx: &App) -> String {
18272 self.buffer.read(cx).read(cx).text()
18273 }
18274
18275 pub fn is_empty(&self, cx: &App) -> bool {
18276 self.buffer.read(cx).read(cx).is_empty()
18277 }
18278
18279 pub fn text_option(&self, cx: &App) -> Option<String> {
18280 let text = self.text(cx);
18281 let text = text.trim();
18282
18283 if text.is_empty() {
18284 return None;
18285 }
18286
18287 Some(text.to_string())
18288 }
18289
18290 pub fn set_text(
18291 &mut self,
18292 text: impl Into<Arc<str>>,
18293 window: &mut Window,
18294 cx: &mut Context<Self>,
18295 ) {
18296 self.transact(window, cx, |this, _, cx| {
18297 this.buffer
18298 .read(cx)
18299 .as_singleton()
18300 .expect("you can only call set_text on editors for singleton buffers")
18301 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18302 });
18303 }
18304
18305 pub fn display_text(&self, cx: &mut App) -> String {
18306 self.display_map
18307 .update(cx, |map, cx| map.snapshot(cx))
18308 .text()
18309 }
18310
18311 fn create_minimap(
18312 &self,
18313 minimap_settings: MinimapSettings,
18314 window: &mut Window,
18315 cx: &mut Context<Self>,
18316 ) -> Option<Entity<Self>> {
18317 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18318 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18319 }
18320
18321 fn initialize_new_minimap(
18322 &self,
18323 minimap_settings: MinimapSettings,
18324 window: &mut Window,
18325 cx: &mut Context<Self>,
18326 ) -> Entity<Self> {
18327 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18328
18329 let mut minimap = Editor::new_internal(
18330 EditorMode::Minimap {
18331 parent: cx.weak_entity(),
18332 },
18333 self.buffer.clone(),
18334 None,
18335 Some(self.display_map.clone()),
18336 window,
18337 cx,
18338 );
18339 minimap.scroll_manager.clone_state(&self.scroll_manager);
18340 minimap.set_text_style_refinement(TextStyleRefinement {
18341 font_size: Some(MINIMAP_FONT_SIZE),
18342 font_weight: Some(MINIMAP_FONT_WEIGHT),
18343 ..Default::default()
18344 });
18345 minimap.update_minimap_configuration(minimap_settings, cx);
18346 cx.new(|_| minimap)
18347 }
18348
18349 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18350 let current_line_highlight = minimap_settings
18351 .current_line_highlight
18352 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18353 self.set_current_line_highlight(Some(current_line_highlight));
18354 }
18355
18356 pub fn minimap(&self) -> Option<&Entity<Self>> {
18357 self.minimap
18358 .as_ref()
18359 .filter(|_| self.minimap_visibility.visible())
18360 }
18361
18362 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18363 let mut wrap_guides = smallvec![];
18364
18365 if self.show_wrap_guides == Some(false) {
18366 return wrap_guides;
18367 }
18368
18369 let settings = self.buffer.read(cx).language_settings(cx);
18370 if settings.show_wrap_guides {
18371 match self.soft_wrap_mode(cx) {
18372 SoftWrap::Column(soft_wrap) => {
18373 wrap_guides.push((soft_wrap as usize, true));
18374 }
18375 SoftWrap::Bounded(soft_wrap) => {
18376 wrap_guides.push((soft_wrap as usize, true));
18377 }
18378 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18379 }
18380 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18381 }
18382
18383 wrap_guides
18384 }
18385
18386 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18387 let settings = self.buffer.read(cx).language_settings(cx);
18388 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18389 match mode {
18390 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18391 SoftWrap::None
18392 }
18393 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18394 language_settings::SoftWrap::PreferredLineLength => {
18395 SoftWrap::Column(settings.preferred_line_length)
18396 }
18397 language_settings::SoftWrap::Bounded => {
18398 SoftWrap::Bounded(settings.preferred_line_length)
18399 }
18400 }
18401 }
18402
18403 pub fn set_soft_wrap_mode(
18404 &mut self,
18405 mode: language_settings::SoftWrap,
18406
18407 cx: &mut Context<Self>,
18408 ) {
18409 self.soft_wrap_mode_override = Some(mode);
18410 cx.notify();
18411 }
18412
18413 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18414 self.hard_wrap = hard_wrap;
18415 cx.notify();
18416 }
18417
18418 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18419 self.text_style_refinement = Some(style);
18420 }
18421
18422 /// called by the Element so we know what style we were most recently rendered with.
18423 pub(crate) fn set_style(
18424 &mut self,
18425 style: EditorStyle,
18426 window: &mut Window,
18427 cx: &mut Context<Self>,
18428 ) {
18429 // We intentionally do not inform the display map about the minimap style
18430 // so that wrapping is not recalculated and stays consistent for the editor
18431 // and its linked minimap.
18432 if !self.mode.is_minimap() {
18433 let rem_size = window.rem_size();
18434 self.display_map.update(cx, |map, cx| {
18435 map.set_font(
18436 style.text.font(),
18437 style.text.font_size.to_pixels(rem_size),
18438 cx,
18439 )
18440 });
18441 }
18442 self.style = Some(style);
18443 }
18444
18445 pub fn style(&self) -> Option<&EditorStyle> {
18446 self.style.as_ref()
18447 }
18448
18449 // Called by the element. This method is not designed to be called outside of the editor
18450 // element's layout code because it does not notify when rewrapping is computed synchronously.
18451 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18452 self.display_map
18453 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18454 }
18455
18456 pub fn set_soft_wrap(&mut self) {
18457 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18458 }
18459
18460 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18461 if self.soft_wrap_mode_override.is_some() {
18462 self.soft_wrap_mode_override.take();
18463 } else {
18464 let soft_wrap = match self.soft_wrap_mode(cx) {
18465 SoftWrap::GitDiff => return,
18466 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18467 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18468 language_settings::SoftWrap::None
18469 }
18470 };
18471 self.soft_wrap_mode_override = Some(soft_wrap);
18472 }
18473 cx.notify();
18474 }
18475
18476 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18477 let Some(workspace) = self.workspace() else {
18478 return;
18479 };
18480 let fs = workspace.read(cx).app_state().fs.clone();
18481 let current_show = TabBarSettings::get_global(cx).show;
18482 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18483 setting.show = Some(!current_show);
18484 });
18485 }
18486
18487 pub fn toggle_indent_guides(
18488 &mut self,
18489 _: &ToggleIndentGuides,
18490 _: &mut Window,
18491 cx: &mut Context<Self>,
18492 ) {
18493 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18494 self.buffer
18495 .read(cx)
18496 .language_settings(cx)
18497 .indent_guides
18498 .enabled
18499 });
18500 self.show_indent_guides = Some(!currently_enabled);
18501 cx.notify();
18502 }
18503
18504 fn should_show_indent_guides(&self) -> Option<bool> {
18505 self.show_indent_guides
18506 }
18507
18508 pub fn toggle_line_numbers(
18509 &mut self,
18510 _: &ToggleLineNumbers,
18511 _: &mut Window,
18512 cx: &mut Context<Self>,
18513 ) {
18514 let mut editor_settings = EditorSettings::get_global(cx).clone();
18515 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18516 EditorSettings::override_global(editor_settings, cx);
18517 }
18518
18519 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18520 if let Some(show_line_numbers) = self.show_line_numbers {
18521 return show_line_numbers;
18522 }
18523 EditorSettings::get_global(cx).gutter.line_numbers
18524 }
18525
18526 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18527 self.use_relative_line_numbers
18528 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18529 }
18530
18531 pub fn toggle_relative_line_numbers(
18532 &mut self,
18533 _: &ToggleRelativeLineNumbers,
18534 _: &mut Window,
18535 cx: &mut Context<Self>,
18536 ) {
18537 let is_relative = self.should_use_relative_line_numbers(cx);
18538 self.set_relative_line_number(Some(!is_relative), cx)
18539 }
18540
18541 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18542 self.use_relative_line_numbers = is_relative;
18543 cx.notify();
18544 }
18545
18546 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18547 self.show_gutter = show_gutter;
18548 cx.notify();
18549 }
18550
18551 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18552 self.show_scrollbars = ScrollbarAxes {
18553 horizontal: show,
18554 vertical: show,
18555 };
18556 cx.notify();
18557 }
18558
18559 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18560 self.show_scrollbars.vertical = show;
18561 cx.notify();
18562 }
18563
18564 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18565 self.show_scrollbars.horizontal = show;
18566 cx.notify();
18567 }
18568
18569 pub fn set_minimap_visibility(
18570 &mut self,
18571 minimap_visibility: MinimapVisibility,
18572 window: &mut Window,
18573 cx: &mut Context<Self>,
18574 ) {
18575 if self.minimap_visibility != minimap_visibility {
18576 if minimap_visibility.visible() && self.minimap.is_none() {
18577 let minimap_settings = EditorSettings::get_global(cx).minimap;
18578 self.minimap =
18579 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18580 }
18581 self.minimap_visibility = minimap_visibility;
18582 cx.notify();
18583 }
18584 }
18585
18586 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18587 self.set_show_scrollbars(false, cx);
18588 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18589 }
18590
18591 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18592 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18593 }
18594
18595 /// Normally the text in full mode and auto height editors is padded on the
18596 /// left side by roughly half a character width for improved hit testing.
18597 ///
18598 /// Use this method to disable this for cases where this is not wanted (e.g.
18599 /// if you want to align the editor text with some other text above or below)
18600 /// or if you want to add this padding to single-line editors.
18601 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18602 self.offset_content = offset_content;
18603 cx.notify();
18604 }
18605
18606 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18607 self.show_line_numbers = Some(show_line_numbers);
18608 cx.notify();
18609 }
18610
18611 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18612 self.disable_expand_excerpt_buttons = true;
18613 cx.notify();
18614 }
18615
18616 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18617 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18618 cx.notify();
18619 }
18620
18621 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18622 self.show_code_actions = Some(show_code_actions);
18623 cx.notify();
18624 }
18625
18626 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18627 self.show_runnables = Some(show_runnables);
18628 cx.notify();
18629 }
18630
18631 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18632 self.show_breakpoints = Some(show_breakpoints);
18633 cx.notify();
18634 }
18635
18636 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18637 if self.display_map.read(cx).masked != masked {
18638 self.display_map.update(cx, |map, _| map.masked = masked);
18639 }
18640 cx.notify()
18641 }
18642
18643 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18644 self.show_wrap_guides = Some(show_wrap_guides);
18645 cx.notify();
18646 }
18647
18648 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18649 self.show_indent_guides = Some(show_indent_guides);
18650 cx.notify();
18651 }
18652
18653 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18654 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18655 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18656 if let Some(dir) = file.abs_path(cx).parent() {
18657 return Some(dir.to_owned());
18658 }
18659 }
18660
18661 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18662 return Some(project_path.path.to_path_buf());
18663 }
18664 }
18665
18666 None
18667 }
18668
18669 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18670 self.active_excerpt(cx)?
18671 .1
18672 .read(cx)
18673 .file()
18674 .and_then(|f| f.as_local())
18675 }
18676
18677 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18678 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18679 let buffer = buffer.read(cx);
18680 if let Some(project_path) = buffer.project_path(cx) {
18681 let project = self.project.as_ref()?.read(cx);
18682 project.absolute_path(&project_path, cx)
18683 } else {
18684 buffer
18685 .file()
18686 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18687 }
18688 })
18689 }
18690
18691 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18692 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18693 let project_path = buffer.read(cx).project_path(cx)?;
18694 let project = self.project.as_ref()?.read(cx);
18695 let entry = project.entry_for_path(&project_path, cx)?;
18696 let path = entry.path.to_path_buf();
18697 Some(path)
18698 })
18699 }
18700
18701 pub fn reveal_in_finder(
18702 &mut self,
18703 _: &RevealInFileManager,
18704 _window: &mut Window,
18705 cx: &mut Context<Self>,
18706 ) {
18707 if let Some(target) = self.target_file(cx) {
18708 cx.reveal_path(&target.abs_path(cx));
18709 }
18710 }
18711
18712 pub fn copy_path(
18713 &mut self,
18714 _: &zed_actions::workspace::CopyPath,
18715 _window: &mut Window,
18716 cx: &mut Context<Self>,
18717 ) {
18718 if let Some(path) = self.target_file_abs_path(cx) {
18719 if let Some(path) = path.to_str() {
18720 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18721 }
18722 }
18723 }
18724
18725 pub fn copy_relative_path(
18726 &mut self,
18727 _: &zed_actions::workspace::CopyRelativePath,
18728 _window: &mut Window,
18729 cx: &mut Context<Self>,
18730 ) {
18731 if let Some(path) = self.target_file_path(cx) {
18732 if let Some(path) = path.to_str() {
18733 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18734 }
18735 }
18736 }
18737
18738 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18739 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18740 buffer.read(cx).project_path(cx)
18741 } else {
18742 None
18743 }
18744 }
18745
18746 // Returns true if the editor handled a go-to-line request
18747 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18748 maybe!({
18749 let breakpoint_store = self.breakpoint_store.as_ref()?;
18750
18751 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18752 else {
18753 self.clear_row_highlights::<ActiveDebugLine>();
18754 return None;
18755 };
18756
18757 let position = active_stack_frame.position;
18758 let buffer_id = position.buffer_id?;
18759 let snapshot = self
18760 .project
18761 .as_ref()?
18762 .read(cx)
18763 .buffer_for_id(buffer_id, cx)?
18764 .read(cx)
18765 .snapshot();
18766
18767 let mut handled = false;
18768 for (id, ExcerptRange { context, .. }) in
18769 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18770 {
18771 if context.start.cmp(&position, &snapshot).is_ge()
18772 || context.end.cmp(&position, &snapshot).is_lt()
18773 {
18774 continue;
18775 }
18776 let snapshot = self.buffer.read(cx).snapshot(cx);
18777 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18778
18779 handled = true;
18780 self.clear_row_highlights::<ActiveDebugLine>();
18781
18782 self.go_to_line::<ActiveDebugLine>(
18783 multibuffer_anchor,
18784 Some(cx.theme().colors().editor_debugger_active_line_background),
18785 window,
18786 cx,
18787 );
18788
18789 cx.notify();
18790 }
18791
18792 handled.then_some(())
18793 })
18794 .is_some()
18795 }
18796
18797 pub fn copy_file_name_without_extension(
18798 &mut self,
18799 _: &CopyFileNameWithoutExtension,
18800 _: &mut Window,
18801 cx: &mut Context<Self>,
18802 ) {
18803 if let Some(file) = self.target_file(cx) {
18804 if let Some(file_stem) = file.path().file_stem() {
18805 if let Some(name) = file_stem.to_str() {
18806 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18807 }
18808 }
18809 }
18810 }
18811
18812 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18813 if let Some(file) = self.target_file(cx) {
18814 if let Some(file_name) = file.path().file_name() {
18815 if let Some(name) = file_name.to_str() {
18816 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18817 }
18818 }
18819 }
18820 }
18821
18822 pub fn toggle_git_blame(
18823 &mut self,
18824 _: &::git::Blame,
18825 window: &mut Window,
18826 cx: &mut Context<Self>,
18827 ) {
18828 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18829
18830 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18831 self.start_git_blame(true, window, cx);
18832 }
18833
18834 cx.notify();
18835 }
18836
18837 pub fn toggle_git_blame_inline(
18838 &mut self,
18839 _: &ToggleGitBlameInline,
18840 window: &mut Window,
18841 cx: &mut Context<Self>,
18842 ) {
18843 self.toggle_git_blame_inline_internal(true, window, cx);
18844 cx.notify();
18845 }
18846
18847 pub fn open_git_blame_commit(
18848 &mut self,
18849 _: &OpenGitBlameCommit,
18850 window: &mut Window,
18851 cx: &mut Context<Self>,
18852 ) {
18853 self.open_git_blame_commit_internal(window, cx);
18854 }
18855
18856 fn open_git_blame_commit_internal(
18857 &mut self,
18858 window: &mut Window,
18859 cx: &mut Context<Self>,
18860 ) -> Option<()> {
18861 let blame = self.blame.as_ref()?;
18862 let snapshot = self.snapshot(window, cx);
18863 let cursor = self.selections.newest::<Point>(cx).head();
18864 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18865 let blame_entry = blame
18866 .update(cx, |blame, cx| {
18867 blame
18868 .blame_for_rows(
18869 &[RowInfo {
18870 buffer_id: Some(buffer.remote_id()),
18871 buffer_row: Some(point.row),
18872 ..Default::default()
18873 }],
18874 cx,
18875 )
18876 .next()
18877 })
18878 .flatten()?;
18879 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18880 let repo = blame.read(cx).repository(cx)?;
18881 let workspace = self.workspace()?.downgrade();
18882 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18883 None
18884 }
18885
18886 pub fn git_blame_inline_enabled(&self) -> bool {
18887 self.git_blame_inline_enabled
18888 }
18889
18890 pub fn toggle_selection_menu(
18891 &mut self,
18892 _: &ToggleSelectionMenu,
18893 _: &mut Window,
18894 cx: &mut Context<Self>,
18895 ) {
18896 self.show_selection_menu = self
18897 .show_selection_menu
18898 .map(|show_selections_menu| !show_selections_menu)
18899 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18900
18901 cx.notify();
18902 }
18903
18904 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18905 self.show_selection_menu
18906 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18907 }
18908
18909 fn start_git_blame(
18910 &mut self,
18911 user_triggered: bool,
18912 window: &mut Window,
18913 cx: &mut Context<Self>,
18914 ) {
18915 if let Some(project) = self.project.as_ref() {
18916 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18917 return;
18918 };
18919
18920 if buffer.read(cx).file().is_none() {
18921 return;
18922 }
18923
18924 let focused = self.focus_handle(cx).contains_focused(window, cx);
18925
18926 let project = project.clone();
18927 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18928 self.blame_subscription =
18929 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18930 self.blame = Some(blame);
18931 }
18932 }
18933
18934 fn toggle_git_blame_inline_internal(
18935 &mut self,
18936 user_triggered: bool,
18937 window: &mut Window,
18938 cx: &mut Context<Self>,
18939 ) {
18940 if self.git_blame_inline_enabled {
18941 self.git_blame_inline_enabled = false;
18942 self.show_git_blame_inline = false;
18943 self.show_git_blame_inline_delay_task.take();
18944 } else {
18945 self.git_blame_inline_enabled = true;
18946 self.start_git_blame_inline(user_triggered, window, cx);
18947 }
18948
18949 cx.notify();
18950 }
18951
18952 fn start_git_blame_inline(
18953 &mut self,
18954 user_triggered: bool,
18955 window: &mut Window,
18956 cx: &mut Context<Self>,
18957 ) {
18958 self.start_git_blame(user_triggered, window, cx);
18959
18960 if ProjectSettings::get_global(cx)
18961 .git
18962 .inline_blame_delay()
18963 .is_some()
18964 {
18965 self.start_inline_blame_timer(window, cx);
18966 } else {
18967 self.show_git_blame_inline = true
18968 }
18969 }
18970
18971 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18972 self.blame.as_ref()
18973 }
18974
18975 pub fn show_git_blame_gutter(&self) -> bool {
18976 self.show_git_blame_gutter
18977 }
18978
18979 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18980 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18981 }
18982
18983 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18984 self.show_git_blame_inline
18985 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18986 && !self.newest_selection_head_on_empty_line(cx)
18987 && self.has_blame_entries(cx)
18988 }
18989
18990 fn has_blame_entries(&self, cx: &App) -> bool {
18991 self.blame()
18992 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18993 }
18994
18995 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18996 let cursor_anchor = self.selections.newest_anchor().head();
18997
18998 let snapshot = self.buffer.read(cx).snapshot(cx);
18999 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19000
19001 snapshot.line_len(buffer_row) == 0
19002 }
19003
19004 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19005 let buffer_and_selection = maybe!({
19006 let selection = self.selections.newest::<Point>(cx);
19007 let selection_range = selection.range();
19008
19009 let multi_buffer = self.buffer().read(cx);
19010 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19011 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19012
19013 let (buffer, range, _) = if selection.reversed {
19014 buffer_ranges.first()
19015 } else {
19016 buffer_ranges.last()
19017 }?;
19018
19019 let selection = text::ToPoint::to_point(&range.start, &buffer).row
19020 ..text::ToPoint::to_point(&range.end, &buffer).row;
19021 Some((
19022 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
19023 selection,
19024 ))
19025 });
19026
19027 let Some((buffer, selection)) = buffer_and_selection else {
19028 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19029 };
19030
19031 let Some(project) = self.project.as_ref() else {
19032 return Task::ready(Err(anyhow!("editor does not have project")));
19033 };
19034
19035 project.update(cx, |project, cx| {
19036 project.get_permalink_to_line(&buffer, selection, cx)
19037 })
19038 }
19039
19040 pub fn copy_permalink_to_line(
19041 &mut self,
19042 _: &CopyPermalinkToLine,
19043 window: &mut Window,
19044 cx: &mut Context<Self>,
19045 ) {
19046 let permalink_task = self.get_permalink_to_line(cx);
19047 let workspace = self.workspace();
19048
19049 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19050 Ok(permalink) => {
19051 cx.update(|_, cx| {
19052 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19053 })
19054 .ok();
19055 }
19056 Err(err) => {
19057 let message = format!("Failed to copy permalink: {err}");
19058
19059 anyhow::Result::<()>::Err(err).log_err();
19060
19061 if let Some(workspace) = workspace {
19062 workspace
19063 .update_in(cx, |workspace, _, cx| {
19064 struct CopyPermalinkToLine;
19065
19066 workspace.show_toast(
19067 Toast::new(
19068 NotificationId::unique::<CopyPermalinkToLine>(),
19069 message,
19070 ),
19071 cx,
19072 )
19073 })
19074 .ok();
19075 }
19076 }
19077 })
19078 .detach();
19079 }
19080
19081 pub fn copy_file_location(
19082 &mut self,
19083 _: &CopyFileLocation,
19084 _: &mut Window,
19085 cx: &mut Context<Self>,
19086 ) {
19087 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19088 if let Some(file) = self.target_file(cx) {
19089 if let Some(path) = file.path().to_str() {
19090 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19091 }
19092 }
19093 }
19094
19095 pub fn open_permalink_to_line(
19096 &mut self,
19097 _: &OpenPermalinkToLine,
19098 window: &mut Window,
19099 cx: &mut Context<Self>,
19100 ) {
19101 let permalink_task = self.get_permalink_to_line(cx);
19102 let workspace = self.workspace();
19103
19104 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19105 Ok(permalink) => {
19106 cx.update(|_, cx| {
19107 cx.open_url(permalink.as_ref());
19108 })
19109 .ok();
19110 }
19111 Err(err) => {
19112 let message = format!("Failed to open permalink: {err}");
19113
19114 anyhow::Result::<()>::Err(err).log_err();
19115
19116 if let Some(workspace) = workspace {
19117 workspace
19118 .update(cx, |workspace, cx| {
19119 struct OpenPermalinkToLine;
19120
19121 workspace.show_toast(
19122 Toast::new(
19123 NotificationId::unique::<OpenPermalinkToLine>(),
19124 message,
19125 ),
19126 cx,
19127 )
19128 })
19129 .ok();
19130 }
19131 }
19132 })
19133 .detach();
19134 }
19135
19136 pub fn insert_uuid_v4(
19137 &mut self,
19138 _: &InsertUuidV4,
19139 window: &mut Window,
19140 cx: &mut Context<Self>,
19141 ) {
19142 self.insert_uuid(UuidVersion::V4, window, cx);
19143 }
19144
19145 pub fn insert_uuid_v7(
19146 &mut self,
19147 _: &InsertUuidV7,
19148 window: &mut Window,
19149 cx: &mut Context<Self>,
19150 ) {
19151 self.insert_uuid(UuidVersion::V7, window, cx);
19152 }
19153
19154 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19155 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19156 self.transact(window, cx, |this, window, cx| {
19157 let edits = this
19158 .selections
19159 .all::<Point>(cx)
19160 .into_iter()
19161 .map(|selection| {
19162 let uuid = match version {
19163 UuidVersion::V4 => uuid::Uuid::new_v4(),
19164 UuidVersion::V7 => uuid::Uuid::now_v7(),
19165 };
19166
19167 (selection.range(), uuid.to_string())
19168 });
19169 this.edit(edits, cx);
19170 this.refresh_edit_prediction(true, false, window, cx);
19171 });
19172 }
19173
19174 pub fn open_selections_in_multibuffer(
19175 &mut self,
19176 _: &OpenSelectionsInMultibuffer,
19177 window: &mut Window,
19178 cx: &mut Context<Self>,
19179 ) {
19180 let multibuffer = self.buffer.read(cx);
19181
19182 let Some(buffer) = multibuffer.as_singleton() else {
19183 return;
19184 };
19185
19186 let Some(workspace) = self.workspace() else {
19187 return;
19188 };
19189
19190 let title = multibuffer.title(cx).to_string();
19191
19192 let locations = self
19193 .selections
19194 .all_anchors(cx)
19195 .into_iter()
19196 .map(|selection| Location {
19197 buffer: buffer.clone(),
19198 range: selection.start.text_anchor..selection.end.text_anchor,
19199 })
19200 .collect::<Vec<_>>();
19201
19202 cx.spawn_in(window, async move |_, cx| {
19203 workspace.update_in(cx, |workspace, window, cx| {
19204 Self::open_locations_in_multibuffer(
19205 workspace,
19206 locations,
19207 format!("Selections for '{title}'"),
19208 false,
19209 MultibufferSelectionMode::All,
19210 window,
19211 cx,
19212 );
19213 })
19214 })
19215 .detach();
19216 }
19217
19218 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19219 /// last highlight added will be used.
19220 ///
19221 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19222 pub fn highlight_rows<T: 'static>(
19223 &mut self,
19224 range: Range<Anchor>,
19225 color: Hsla,
19226 options: RowHighlightOptions,
19227 cx: &mut Context<Self>,
19228 ) {
19229 let snapshot = self.buffer().read(cx).snapshot(cx);
19230 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19231 let ix = row_highlights.binary_search_by(|highlight| {
19232 Ordering::Equal
19233 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19234 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19235 });
19236
19237 if let Err(mut ix) = ix {
19238 let index = post_inc(&mut self.highlight_order);
19239
19240 // If this range intersects with the preceding highlight, then merge it with
19241 // the preceding highlight. Otherwise insert a new highlight.
19242 let mut merged = false;
19243 if ix > 0 {
19244 let prev_highlight = &mut row_highlights[ix - 1];
19245 if prev_highlight
19246 .range
19247 .end
19248 .cmp(&range.start, &snapshot)
19249 .is_ge()
19250 {
19251 ix -= 1;
19252 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19253 prev_highlight.range.end = range.end;
19254 }
19255 merged = true;
19256 prev_highlight.index = index;
19257 prev_highlight.color = color;
19258 prev_highlight.options = options;
19259 }
19260 }
19261
19262 if !merged {
19263 row_highlights.insert(
19264 ix,
19265 RowHighlight {
19266 range: range.clone(),
19267 index,
19268 color,
19269 options,
19270 type_id: TypeId::of::<T>(),
19271 },
19272 );
19273 }
19274
19275 // If any of the following highlights intersect with this one, merge them.
19276 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19277 let highlight = &row_highlights[ix];
19278 if next_highlight
19279 .range
19280 .start
19281 .cmp(&highlight.range.end, &snapshot)
19282 .is_le()
19283 {
19284 if next_highlight
19285 .range
19286 .end
19287 .cmp(&highlight.range.end, &snapshot)
19288 .is_gt()
19289 {
19290 row_highlights[ix].range.end = next_highlight.range.end;
19291 }
19292 row_highlights.remove(ix + 1);
19293 } else {
19294 break;
19295 }
19296 }
19297 }
19298 }
19299
19300 /// Remove any highlighted row ranges of the given type that intersect the
19301 /// given ranges.
19302 pub fn remove_highlighted_rows<T: 'static>(
19303 &mut self,
19304 ranges_to_remove: Vec<Range<Anchor>>,
19305 cx: &mut Context<Self>,
19306 ) {
19307 let snapshot = self.buffer().read(cx).snapshot(cx);
19308 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19309 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19310 row_highlights.retain(|highlight| {
19311 while let Some(range_to_remove) = ranges_to_remove.peek() {
19312 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19313 Ordering::Less | Ordering::Equal => {
19314 ranges_to_remove.next();
19315 }
19316 Ordering::Greater => {
19317 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19318 Ordering::Less | Ordering::Equal => {
19319 return false;
19320 }
19321 Ordering::Greater => break,
19322 }
19323 }
19324 }
19325 }
19326
19327 true
19328 })
19329 }
19330
19331 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19332 pub fn clear_row_highlights<T: 'static>(&mut self) {
19333 self.highlighted_rows.remove(&TypeId::of::<T>());
19334 }
19335
19336 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19337 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19338 self.highlighted_rows
19339 .get(&TypeId::of::<T>())
19340 .map_or(&[] as &[_], |vec| vec.as_slice())
19341 .iter()
19342 .map(|highlight| (highlight.range.clone(), highlight.color))
19343 }
19344
19345 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19346 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19347 /// Allows to ignore certain kinds of highlights.
19348 pub fn highlighted_display_rows(
19349 &self,
19350 window: &mut Window,
19351 cx: &mut App,
19352 ) -> BTreeMap<DisplayRow, LineHighlight> {
19353 let snapshot = self.snapshot(window, cx);
19354 let mut used_highlight_orders = HashMap::default();
19355 self.highlighted_rows
19356 .iter()
19357 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19358 .fold(
19359 BTreeMap::<DisplayRow, LineHighlight>::new(),
19360 |mut unique_rows, highlight| {
19361 let start = highlight.range.start.to_display_point(&snapshot);
19362 let end = highlight.range.end.to_display_point(&snapshot);
19363 let start_row = start.row().0;
19364 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19365 && end.column() == 0
19366 {
19367 end.row().0.saturating_sub(1)
19368 } else {
19369 end.row().0
19370 };
19371 for row in start_row..=end_row {
19372 let used_index =
19373 used_highlight_orders.entry(row).or_insert(highlight.index);
19374 if highlight.index >= *used_index {
19375 *used_index = highlight.index;
19376 unique_rows.insert(
19377 DisplayRow(row),
19378 LineHighlight {
19379 include_gutter: highlight.options.include_gutter,
19380 border: None,
19381 background: highlight.color.into(),
19382 type_id: Some(highlight.type_id),
19383 },
19384 );
19385 }
19386 }
19387 unique_rows
19388 },
19389 )
19390 }
19391
19392 pub fn highlighted_display_row_for_autoscroll(
19393 &self,
19394 snapshot: &DisplaySnapshot,
19395 ) -> Option<DisplayRow> {
19396 self.highlighted_rows
19397 .values()
19398 .flat_map(|highlighted_rows| highlighted_rows.iter())
19399 .filter_map(|highlight| {
19400 if highlight.options.autoscroll {
19401 Some(highlight.range.start.to_display_point(snapshot).row())
19402 } else {
19403 None
19404 }
19405 })
19406 .min()
19407 }
19408
19409 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19410 self.highlight_background::<SearchWithinRange>(
19411 ranges,
19412 |colors| colors.colors().editor_document_highlight_read_background,
19413 cx,
19414 )
19415 }
19416
19417 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19418 self.breadcrumb_header = Some(new_header);
19419 }
19420
19421 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19422 self.clear_background_highlights::<SearchWithinRange>(cx);
19423 }
19424
19425 pub fn highlight_background<T: 'static>(
19426 &mut self,
19427 ranges: &[Range<Anchor>],
19428 color_fetcher: fn(&Theme) -> Hsla,
19429 cx: &mut Context<Self>,
19430 ) {
19431 self.background_highlights.insert(
19432 HighlightKey::Type(TypeId::of::<T>()),
19433 (color_fetcher, Arc::from(ranges)),
19434 );
19435 self.scrollbar_marker_state.dirty = true;
19436 cx.notify();
19437 }
19438
19439 pub fn highlight_background_key<T: 'static>(
19440 &mut self,
19441 key: usize,
19442 ranges: &[Range<Anchor>],
19443 color_fetcher: fn(&Theme) -> Hsla,
19444 cx: &mut Context<Self>,
19445 ) {
19446 self.background_highlights.insert(
19447 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19448 (color_fetcher, Arc::from(ranges)),
19449 );
19450 self.scrollbar_marker_state.dirty = true;
19451 cx.notify();
19452 }
19453
19454 pub fn clear_background_highlights<T: 'static>(
19455 &mut self,
19456 cx: &mut Context<Self>,
19457 ) -> Option<BackgroundHighlight> {
19458 let text_highlights = self
19459 .background_highlights
19460 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19461 if !text_highlights.1.is_empty() {
19462 self.scrollbar_marker_state.dirty = true;
19463 cx.notify();
19464 }
19465 Some(text_highlights)
19466 }
19467
19468 pub fn highlight_gutter<T: 'static>(
19469 &mut self,
19470 ranges: impl Into<Vec<Range<Anchor>>>,
19471 color_fetcher: fn(&App) -> Hsla,
19472 cx: &mut Context<Self>,
19473 ) {
19474 self.gutter_highlights
19475 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19476 cx.notify();
19477 }
19478
19479 pub fn clear_gutter_highlights<T: 'static>(
19480 &mut self,
19481 cx: &mut Context<Self>,
19482 ) -> Option<GutterHighlight> {
19483 cx.notify();
19484 self.gutter_highlights.remove(&TypeId::of::<T>())
19485 }
19486
19487 pub fn insert_gutter_highlight<T: 'static>(
19488 &mut self,
19489 range: Range<Anchor>,
19490 color_fetcher: fn(&App) -> Hsla,
19491 cx: &mut Context<Self>,
19492 ) {
19493 let snapshot = self.buffer().read(cx).snapshot(cx);
19494 let mut highlights = self
19495 .gutter_highlights
19496 .remove(&TypeId::of::<T>())
19497 .map(|(_, highlights)| highlights)
19498 .unwrap_or_default();
19499 let ix = highlights.binary_search_by(|highlight| {
19500 Ordering::Equal
19501 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19502 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19503 });
19504 if let Err(ix) = ix {
19505 highlights.insert(ix, range);
19506 }
19507 self.gutter_highlights
19508 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19509 }
19510
19511 pub fn remove_gutter_highlights<T: 'static>(
19512 &mut self,
19513 ranges_to_remove: Vec<Range<Anchor>>,
19514 cx: &mut Context<Self>,
19515 ) {
19516 let snapshot = self.buffer().read(cx).snapshot(cx);
19517 let Some((color_fetcher, mut gutter_highlights)) =
19518 self.gutter_highlights.remove(&TypeId::of::<T>())
19519 else {
19520 return;
19521 };
19522 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19523 gutter_highlights.retain(|highlight| {
19524 while let Some(range_to_remove) = ranges_to_remove.peek() {
19525 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19526 Ordering::Less | Ordering::Equal => {
19527 ranges_to_remove.next();
19528 }
19529 Ordering::Greater => {
19530 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19531 Ordering::Less | Ordering::Equal => {
19532 return false;
19533 }
19534 Ordering::Greater => break,
19535 }
19536 }
19537 }
19538 }
19539
19540 true
19541 });
19542 self.gutter_highlights
19543 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19544 }
19545
19546 #[cfg(feature = "test-support")]
19547 pub fn all_text_highlights(
19548 &self,
19549 window: &mut Window,
19550 cx: &mut Context<Self>,
19551 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19552 let snapshot = self.snapshot(window, cx);
19553 self.display_map.update(cx, |display_map, _| {
19554 display_map
19555 .all_text_highlights()
19556 .map(|highlight| {
19557 let (style, ranges) = highlight.as_ref();
19558 (
19559 *style,
19560 ranges
19561 .iter()
19562 .map(|range| range.clone().to_display_points(&snapshot))
19563 .collect(),
19564 )
19565 })
19566 .collect()
19567 })
19568 }
19569
19570 #[cfg(feature = "test-support")]
19571 pub fn all_text_background_highlights(
19572 &self,
19573 window: &mut Window,
19574 cx: &mut Context<Self>,
19575 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19576 let snapshot = self.snapshot(window, cx);
19577 let buffer = &snapshot.buffer_snapshot;
19578 let start = buffer.anchor_before(0);
19579 let end = buffer.anchor_after(buffer.len());
19580 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19581 }
19582
19583 #[cfg(feature = "test-support")]
19584 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19585 let snapshot = self.buffer().read(cx).snapshot(cx);
19586
19587 let highlights = self
19588 .background_highlights
19589 .get(&HighlightKey::Type(TypeId::of::<
19590 items::BufferSearchHighlights,
19591 >()));
19592
19593 if let Some((_color, ranges)) = highlights {
19594 ranges
19595 .iter()
19596 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19597 .collect_vec()
19598 } else {
19599 vec![]
19600 }
19601 }
19602
19603 fn document_highlights_for_position<'a>(
19604 &'a self,
19605 position: Anchor,
19606 buffer: &'a MultiBufferSnapshot,
19607 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19608 let read_highlights = self
19609 .background_highlights
19610 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19611 .map(|h| &h.1);
19612 let write_highlights = self
19613 .background_highlights
19614 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19615 .map(|h| &h.1);
19616 let left_position = position.bias_left(buffer);
19617 let right_position = position.bias_right(buffer);
19618 read_highlights
19619 .into_iter()
19620 .chain(write_highlights)
19621 .flat_map(move |ranges| {
19622 let start_ix = match ranges.binary_search_by(|probe| {
19623 let cmp = probe.end.cmp(&left_position, buffer);
19624 if cmp.is_ge() {
19625 Ordering::Greater
19626 } else {
19627 Ordering::Less
19628 }
19629 }) {
19630 Ok(i) | Err(i) => i,
19631 };
19632
19633 ranges[start_ix..]
19634 .iter()
19635 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19636 })
19637 }
19638
19639 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19640 self.background_highlights
19641 .get(&HighlightKey::Type(TypeId::of::<T>()))
19642 .map_or(false, |(_, highlights)| !highlights.is_empty())
19643 }
19644
19645 pub fn background_highlights_in_range(
19646 &self,
19647 search_range: Range<Anchor>,
19648 display_snapshot: &DisplaySnapshot,
19649 theme: &Theme,
19650 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19651 let mut results = Vec::new();
19652 for (color_fetcher, ranges) in self.background_highlights.values() {
19653 let color = color_fetcher(theme);
19654 let start_ix = match ranges.binary_search_by(|probe| {
19655 let cmp = probe
19656 .end
19657 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19658 if cmp.is_gt() {
19659 Ordering::Greater
19660 } else {
19661 Ordering::Less
19662 }
19663 }) {
19664 Ok(i) | Err(i) => i,
19665 };
19666 for range in &ranges[start_ix..] {
19667 if range
19668 .start
19669 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19670 .is_ge()
19671 {
19672 break;
19673 }
19674
19675 let start = range.start.to_display_point(display_snapshot);
19676 let end = range.end.to_display_point(display_snapshot);
19677 results.push((start..end, color))
19678 }
19679 }
19680 results
19681 }
19682
19683 pub fn background_highlight_row_ranges<T: 'static>(
19684 &self,
19685 search_range: Range<Anchor>,
19686 display_snapshot: &DisplaySnapshot,
19687 count: usize,
19688 ) -> Vec<RangeInclusive<DisplayPoint>> {
19689 let mut results = Vec::new();
19690 let Some((_, ranges)) = self
19691 .background_highlights
19692 .get(&HighlightKey::Type(TypeId::of::<T>()))
19693 else {
19694 return vec![];
19695 };
19696
19697 let start_ix = match ranges.binary_search_by(|probe| {
19698 let cmp = probe
19699 .end
19700 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19701 if cmp.is_gt() {
19702 Ordering::Greater
19703 } else {
19704 Ordering::Less
19705 }
19706 }) {
19707 Ok(i) | Err(i) => i,
19708 };
19709 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19710 if let (Some(start_display), Some(end_display)) = (start, end) {
19711 results.push(
19712 start_display.to_display_point(display_snapshot)
19713 ..=end_display.to_display_point(display_snapshot),
19714 );
19715 }
19716 };
19717 let mut start_row: Option<Point> = None;
19718 let mut end_row: Option<Point> = None;
19719 if ranges.len() > count {
19720 return Vec::new();
19721 }
19722 for range in &ranges[start_ix..] {
19723 if range
19724 .start
19725 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19726 .is_ge()
19727 {
19728 break;
19729 }
19730 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19731 if let Some(current_row) = &end_row {
19732 if end.row == current_row.row {
19733 continue;
19734 }
19735 }
19736 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19737 if start_row.is_none() {
19738 assert_eq!(end_row, None);
19739 start_row = Some(start);
19740 end_row = Some(end);
19741 continue;
19742 }
19743 if let Some(current_end) = end_row.as_mut() {
19744 if start.row > current_end.row + 1 {
19745 push_region(start_row, end_row);
19746 start_row = Some(start);
19747 end_row = Some(end);
19748 } else {
19749 // Merge two hunks.
19750 *current_end = end;
19751 }
19752 } else {
19753 unreachable!();
19754 }
19755 }
19756 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19757 push_region(start_row, end_row);
19758 results
19759 }
19760
19761 pub fn gutter_highlights_in_range(
19762 &self,
19763 search_range: Range<Anchor>,
19764 display_snapshot: &DisplaySnapshot,
19765 cx: &App,
19766 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19767 let mut results = Vec::new();
19768 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19769 let color = color_fetcher(cx);
19770 let start_ix = match ranges.binary_search_by(|probe| {
19771 let cmp = probe
19772 .end
19773 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19774 if cmp.is_gt() {
19775 Ordering::Greater
19776 } else {
19777 Ordering::Less
19778 }
19779 }) {
19780 Ok(i) | Err(i) => i,
19781 };
19782 for range in &ranges[start_ix..] {
19783 if range
19784 .start
19785 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19786 .is_ge()
19787 {
19788 break;
19789 }
19790
19791 let start = range.start.to_display_point(display_snapshot);
19792 let end = range.end.to_display_point(display_snapshot);
19793 results.push((start..end, color))
19794 }
19795 }
19796 results
19797 }
19798
19799 /// Get the text ranges corresponding to the redaction query
19800 pub fn redacted_ranges(
19801 &self,
19802 search_range: Range<Anchor>,
19803 display_snapshot: &DisplaySnapshot,
19804 cx: &App,
19805 ) -> Vec<Range<DisplayPoint>> {
19806 display_snapshot
19807 .buffer_snapshot
19808 .redacted_ranges(search_range, |file| {
19809 if let Some(file) = file {
19810 file.is_private()
19811 && EditorSettings::get(
19812 Some(SettingsLocation {
19813 worktree_id: file.worktree_id(cx),
19814 path: file.path().as_ref(),
19815 }),
19816 cx,
19817 )
19818 .redact_private_values
19819 } else {
19820 false
19821 }
19822 })
19823 .map(|range| {
19824 range.start.to_display_point(display_snapshot)
19825 ..range.end.to_display_point(display_snapshot)
19826 })
19827 .collect()
19828 }
19829
19830 pub fn highlight_text_key<T: 'static>(
19831 &mut self,
19832 key: usize,
19833 ranges: Vec<Range<Anchor>>,
19834 style: HighlightStyle,
19835 cx: &mut Context<Self>,
19836 ) {
19837 self.display_map.update(cx, |map, _| {
19838 map.highlight_text(
19839 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19840 ranges,
19841 style,
19842 );
19843 });
19844 cx.notify();
19845 }
19846
19847 pub fn highlight_text<T: 'static>(
19848 &mut self,
19849 ranges: Vec<Range<Anchor>>,
19850 style: HighlightStyle,
19851 cx: &mut Context<Self>,
19852 ) {
19853 self.display_map.update(cx, |map, _| {
19854 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19855 });
19856 cx.notify();
19857 }
19858
19859 pub(crate) fn highlight_inlays<T: 'static>(
19860 &mut self,
19861 highlights: Vec<InlayHighlight>,
19862 style: HighlightStyle,
19863 cx: &mut Context<Self>,
19864 ) {
19865 self.display_map.update(cx, |map, _| {
19866 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19867 });
19868 cx.notify();
19869 }
19870
19871 pub fn text_highlights<'a, T: 'static>(
19872 &'a self,
19873 cx: &'a App,
19874 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19875 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19876 }
19877
19878 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19879 let cleared = self
19880 .display_map
19881 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19882 if cleared {
19883 cx.notify();
19884 }
19885 }
19886
19887 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19888 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19889 && self.focus_handle.is_focused(window)
19890 }
19891
19892 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19893 self.show_cursor_when_unfocused = is_enabled;
19894 cx.notify();
19895 }
19896
19897 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19898 cx.notify();
19899 }
19900
19901 fn on_debug_session_event(
19902 &mut self,
19903 _session: Entity<Session>,
19904 event: &SessionEvent,
19905 cx: &mut Context<Self>,
19906 ) {
19907 match event {
19908 SessionEvent::InvalidateInlineValue => {
19909 self.refresh_inline_values(cx);
19910 }
19911 _ => {}
19912 }
19913 }
19914
19915 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19916 let Some(project) = self.project.clone() else {
19917 return;
19918 };
19919
19920 if !self.inline_value_cache.enabled {
19921 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19922 self.splice_inlays(&inlays, Vec::new(), cx);
19923 return;
19924 }
19925
19926 let current_execution_position = self
19927 .highlighted_rows
19928 .get(&TypeId::of::<ActiveDebugLine>())
19929 .and_then(|lines| lines.last().map(|line| line.range.end));
19930
19931 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19932 let inline_values = editor
19933 .update(cx, |editor, cx| {
19934 let Some(current_execution_position) = current_execution_position else {
19935 return Some(Task::ready(Ok(Vec::new())));
19936 };
19937
19938 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19939 let snapshot = buffer.snapshot(cx);
19940
19941 let excerpt = snapshot.excerpt_containing(
19942 current_execution_position..current_execution_position,
19943 )?;
19944
19945 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19946 })?;
19947
19948 let range =
19949 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19950
19951 project.inline_values(buffer, range, cx)
19952 })
19953 .ok()
19954 .flatten()?
19955 .await
19956 .context("refreshing debugger inlays")
19957 .log_err()?;
19958
19959 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19960
19961 for (buffer_id, inline_value) in inline_values
19962 .into_iter()
19963 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19964 {
19965 buffer_inline_values
19966 .entry(buffer_id)
19967 .or_default()
19968 .push(inline_value);
19969 }
19970
19971 editor
19972 .update(cx, |editor, cx| {
19973 let snapshot = editor.buffer.read(cx).snapshot(cx);
19974 let mut new_inlays = Vec::default();
19975
19976 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19977 let buffer_id = buffer_snapshot.remote_id();
19978 buffer_inline_values
19979 .get(&buffer_id)
19980 .into_iter()
19981 .flatten()
19982 .for_each(|hint| {
19983 let inlay = Inlay::debugger(
19984 post_inc(&mut editor.next_inlay_id),
19985 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19986 hint.text(),
19987 );
19988 if !inlay.text.chars().contains(&'\n') {
19989 new_inlays.push(inlay);
19990 }
19991 });
19992 }
19993
19994 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19995 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19996
19997 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19998 })
19999 .ok()?;
20000 Some(())
20001 });
20002 }
20003
20004 fn on_buffer_event(
20005 &mut self,
20006 multibuffer: &Entity<MultiBuffer>,
20007 event: &multi_buffer::Event,
20008 window: &mut Window,
20009 cx: &mut Context<Self>,
20010 ) {
20011 match event {
20012 multi_buffer::Event::Edited {
20013 singleton_buffer_edited,
20014 edited_buffer,
20015 } => {
20016 self.scrollbar_marker_state.dirty = true;
20017 self.active_indent_guides_state.dirty = true;
20018 self.refresh_active_diagnostics(cx);
20019 self.refresh_code_actions(window, cx);
20020 self.refresh_selected_text_highlights(true, window, cx);
20021 self.refresh_single_line_folds(window, cx);
20022 refresh_matching_bracket_highlights(self, window, cx);
20023 if self.has_active_edit_prediction() {
20024 self.update_visible_edit_prediction(window, cx);
20025 }
20026 if let Some(project) = self.project.as_ref() {
20027 if let Some(edited_buffer) = edited_buffer {
20028 project.update(cx, |project, cx| {
20029 self.registered_buffers
20030 .entry(edited_buffer.read(cx).remote_id())
20031 .or_insert_with(|| {
20032 project
20033 .register_buffer_with_language_servers(&edited_buffer, cx)
20034 });
20035 });
20036 }
20037 }
20038 cx.emit(EditorEvent::BufferEdited);
20039 cx.emit(SearchEvent::MatchesInvalidated);
20040
20041 if let Some(buffer) = edited_buffer {
20042 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20043 }
20044
20045 if *singleton_buffer_edited {
20046 if let Some(buffer) = edited_buffer {
20047 if buffer.read(cx).file().is_none() {
20048 cx.emit(EditorEvent::TitleChanged);
20049 }
20050 }
20051 if let Some(project) = &self.project {
20052 #[allow(clippy::mutable_key_type)]
20053 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20054 multibuffer
20055 .all_buffers()
20056 .into_iter()
20057 .filter_map(|buffer| {
20058 buffer.update(cx, |buffer, cx| {
20059 let language = buffer.language()?;
20060 let should_discard = project.update(cx, |project, cx| {
20061 project.is_local()
20062 && !project.has_language_servers_for(buffer, cx)
20063 });
20064 should_discard.not().then_some(language.clone())
20065 })
20066 })
20067 .collect::<HashSet<_>>()
20068 });
20069 if !languages_affected.is_empty() {
20070 self.refresh_inlay_hints(
20071 InlayHintRefreshReason::BufferEdited(languages_affected),
20072 cx,
20073 );
20074 }
20075 }
20076 }
20077
20078 let Some(project) = &self.project else { return };
20079 let (telemetry, is_via_ssh) = {
20080 let project = project.read(cx);
20081 let telemetry = project.client().telemetry().clone();
20082 let is_via_ssh = project.is_via_ssh();
20083 (telemetry, is_via_ssh)
20084 };
20085 refresh_linked_ranges(self, window, cx);
20086 telemetry.log_edit_event("editor", is_via_ssh);
20087 }
20088 multi_buffer::Event::ExcerptsAdded {
20089 buffer,
20090 predecessor,
20091 excerpts,
20092 } => {
20093 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20094 let buffer_id = buffer.read(cx).remote_id();
20095 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
20096 if let Some(project) = &self.project {
20097 update_uncommitted_diff_for_buffer(
20098 cx.entity(),
20099 project,
20100 [buffer.clone()],
20101 self.buffer.clone(),
20102 cx,
20103 )
20104 .detach();
20105 }
20106 }
20107 self.update_lsp_data(false, Some(buffer_id), window, cx);
20108 cx.emit(EditorEvent::ExcerptsAdded {
20109 buffer: buffer.clone(),
20110 predecessor: *predecessor,
20111 excerpts: excerpts.clone(),
20112 });
20113 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20114 }
20115 multi_buffer::Event::ExcerptsRemoved {
20116 ids,
20117 removed_buffer_ids,
20118 } => {
20119 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20120 let buffer = self.buffer.read(cx);
20121 self.registered_buffers
20122 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20123 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20124 cx.emit(EditorEvent::ExcerptsRemoved {
20125 ids: ids.clone(),
20126 removed_buffer_ids: removed_buffer_ids.clone(),
20127 });
20128 }
20129 multi_buffer::Event::ExcerptsEdited {
20130 excerpt_ids,
20131 buffer_ids,
20132 } => {
20133 self.display_map.update(cx, |map, cx| {
20134 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20135 });
20136 cx.emit(EditorEvent::ExcerptsEdited {
20137 ids: excerpt_ids.clone(),
20138 });
20139 }
20140 multi_buffer::Event::ExcerptsExpanded { ids } => {
20141 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20142 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20143 }
20144 multi_buffer::Event::Reparsed(buffer_id) => {
20145 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20146 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20147
20148 cx.emit(EditorEvent::Reparsed(*buffer_id));
20149 }
20150 multi_buffer::Event::DiffHunksToggled => {
20151 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20152 }
20153 multi_buffer::Event::LanguageChanged(buffer_id) => {
20154 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20155 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20156 cx.emit(EditorEvent::Reparsed(*buffer_id));
20157 cx.notify();
20158 }
20159 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20160 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20161 multi_buffer::Event::FileHandleChanged
20162 | multi_buffer::Event::Reloaded
20163 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20164 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20165 multi_buffer::Event::DiagnosticsUpdated => {
20166 self.update_diagnostics_state(window, cx);
20167 }
20168 _ => {}
20169 };
20170 }
20171
20172 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20173 if !self.diagnostics_enabled() {
20174 return;
20175 }
20176 self.refresh_active_diagnostics(cx);
20177 self.refresh_inline_diagnostics(true, window, cx);
20178 self.scrollbar_marker_state.dirty = true;
20179 cx.notify();
20180 }
20181
20182 pub fn start_temporary_diff_override(&mut self) {
20183 self.load_diff_task.take();
20184 self.temporary_diff_override = true;
20185 }
20186
20187 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20188 self.temporary_diff_override = false;
20189 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20190 self.buffer.update(cx, |buffer, cx| {
20191 buffer.set_all_diff_hunks_collapsed(cx);
20192 });
20193
20194 if let Some(project) = self.project.clone() {
20195 self.load_diff_task = Some(
20196 update_uncommitted_diff_for_buffer(
20197 cx.entity(),
20198 &project,
20199 self.buffer.read(cx).all_buffers(),
20200 self.buffer.clone(),
20201 cx,
20202 )
20203 .shared(),
20204 );
20205 }
20206 }
20207
20208 fn on_display_map_changed(
20209 &mut self,
20210 _: Entity<DisplayMap>,
20211 _: &mut Window,
20212 cx: &mut Context<Self>,
20213 ) {
20214 cx.notify();
20215 }
20216
20217 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20218 if self.diagnostics_enabled() {
20219 let new_severity = EditorSettings::get_global(cx)
20220 .diagnostics_max_severity
20221 .unwrap_or(DiagnosticSeverity::Hint);
20222 self.set_max_diagnostics_severity(new_severity, cx);
20223 }
20224 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20225 self.update_edit_prediction_settings(cx);
20226 self.refresh_edit_prediction(true, false, window, cx);
20227 self.refresh_inline_values(cx);
20228 self.refresh_inlay_hints(
20229 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20230 self.selections.newest_anchor().head(),
20231 &self.buffer.read(cx).snapshot(cx),
20232 cx,
20233 )),
20234 cx,
20235 );
20236
20237 let old_cursor_shape = self.cursor_shape;
20238 let old_show_breadcrumbs = self.show_breadcrumbs;
20239
20240 {
20241 let editor_settings = EditorSettings::get_global(cx);
20242 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20243 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20244 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20245 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20246 }
20247
20248 if old_cursor_shape != self.cursor_shape {
20249 cx.emit(EditorEvent::CursorShapeChanged);
20250 }
20251
20252 if old_show_breadcrumbs != self.show_breadcrumbs {
20253 cx.emit(EditorEvent::BreadcrumbsChanged);
20254 }
20255
20256 let project_settings = ProjectSettings::get_global(cx);
20257 self.serialize_dirty_buffers =
20258 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20259
20260 if self.mode.is_full() {
20261 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20262 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20263 if self.show_inline_diagnostics != show_inline_diagnostics {
20264 self.show_inline_diagnostics = show_inline_diagnostics;
20265 self.refresh_inline_diagnostics(false, window, cx);
20266 }
20267
20268 if self.git_blame_inline_enabled != inline_blame_enabled {
20269 self.toggle_git_blame_inline_internal(false, window, cx);
20270 }
20271
20272 let minimap_settings = EditorSettings::get_global(cx).minimap;
20273 if self.minimap_visibility != MinimapVisibility::Disabled {
20274 if self.minimap_visibility.settings_visibility()
20275 != minimap_settings.minimap_enabled()
20276 {
20277 self.set_minimap_visibility(
20278 MinimapVisibility::for_mode(self.mode(), cx),
20279 window,
20280 cx,
20281 );
20282 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20283 minimap_entity.update(cx, |minimap_editor, cx| {
20284 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20285 })
20286 }
20287 }
20288 }
20289
20290 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20291 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20292 }) {
20293 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20294 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20295 }
20296 self.refresh_colors(false, None, window, cx);
20297 }
20298
20299 cx.notify();
20300 }
20301
20302 pub fn set_searchable(&mut self, searchable: bool) {
20303 self.searchable = searchable;
20304 }
20305
20306 pub fn searchable(&self) -> bool {
20307 self.searchable
20308 }
20309
20310 fn open_proposed_changes_editor(
20311 &mut self,
20312 _: &OpenProposedChangesEditor,
20313 window: &mut Window,
20314 cx: &mut Context<Self>,
20315 ) {
20316 let Some(workspace) = self.workspace() else {
20317 cx.propagate();
20318 return;
20319 };
20320
20321 let selections = self.selections.all::<usize>(cx);
20322 let multi_buffer = self.buffer.read(cx);
20323 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20324 let mut new_selections_by_buffer = HashMap::default();
20325 for selection in selections {
20326 for (buffer, range, _) in
20327 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20328 {
20329 let mut range = range.to_point(buffer);
20330 range.start.column = 0;
20331 range.end.column = buffer.line_len(range.end.row);
20332 new_selections_by_buffer
20333 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20334 .or_insert(Vec::new())
20335 .push(range)
20336 }
20337 }
20338
20339 let proposed_changes_buffers = new_selections_by_buffer
20340 .into_iter()
20341 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20342 .collect::<Vec<_>>();
20343 let proposed_changes_editor = cx.new(|cx| {
20344 ProposedChangesEditor::new(
20345 "Proposed changes",
20346 proposed_changes_buffers,
20347 self.project.clone(),
20348 window,
20349 cx,
20350 )
20351 });
20352
20353 window.defer(cx, move |window, cx| {
20354 workspace.update(cx, |workspace, cx| {
20355 workspace.active_pane().update(cx, |pane, cx| {
20356 pane.add_item(
20357 Box::new(proposed_changes_editor),
20358 true,
20359 true,
20360 None,
20361 window,
20362 cx,
20363 );
20364 });
20365 });
20366 });
20367 }
20368
20369 pub fn open_excerpts_in_split(
20370 &mut self,
20371 _: &OpenExcerptsSplit,
20372 window: &mut Window,
20373 cx: &mut Context<Self>,
20374 ) {
20375 self.open_excerpts_common(None, true, window, cx)
20376 }
20377
20378 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20379 self.open_excerpts_common(None, false, window, cx)
20380 }
20381
20382 fn open_excerpts_common(
20383 &mut self,
20384 jump_data: Option<JumpData>,
20385 split: bool,
20386 window: &mut Window,
20387 cx: &mut Context<Self>,
20388 ) {
20389 let Some(workspace) = self.workspace() else {
20390 cx.propagate();
20391 return;
20392 };
20393
20394 if self.buffer.read(cx).is_singleton() {
20395 cx.propagate();
20396 return;
20397 }
20398
20399 let mut new_selections_by_buffer = HashMap::default();
20400 match &jump_data {
20401 Some(JumpData::MultiBufferPoint {
20402 excerpt_id,
20403 position,
20404 anchor,
20405 line_offset_from_top,
20406 }) => {
20407 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20408 if let Some(buffer) = multi_buffer_snapshot
20409 .buffer_id_for_excerpt(*excerpt_id)
20410 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20411 {
20412 let buffer_snapshot = buffer.read(cx).snapshot();
20413 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20414 language::ToPoint::to_point(anchor, &buffer_snapshot)
20415 } else {
20416 buffer_snapshot.clip_point(*position, Bias::Left)
20417 };
20418 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20419 new_selections_by_buffer.insert(
20420 buffer,
20421 (
20422 vec![jump_to_offset..jump_to_offset],
20423 Some(*line_offset_from_top),
20424 ),
20425 );
20426 }
20427 }
20428 Some(JumpData::MultiBufferRow {
20429 row,
20430 line_offset_from_top,
20431 }) => {
20432 let point = MultiBufferPoint::new(row.0, 0);
20433 if let Some((buffer, buffer_point, _)) =
20434 self.buffer.read(cx).point_to_buffer_point(point, cx)
20435 {
20436 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20437 new_selections_by_buffer
20438 .entry(buffer)
20439 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20440 .0
20441 .push(buffer_offset..buffer_offset)
20442 }
20443 }
20444 None => {
20445 let selections = self.selections.all::<usize>(cx);
20446 let multi_buffer = self.buffer.read(cx);
20447 for selection in selections {
20448 for (snapshot, range, _, anchor) in multi_buffer
20449 .snapshot(cx)
20450 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20451 {
20452 if let Some(anchor) = anchor {
20453 // selection is in a deleted hunk
20454 let Some(buffer_id) = anchor.buffer_id else {
20455 continue;
20456 };
20457 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20458 continue;
20459 };
20460 let offset = text::ToOffset::to_offset(
20461 &anchor.text_anchor,
20462 &buffer_handle.read(cx).snapshot(),
20463 );
20464 let range = offset..offset;
20465 new_selections_by_buffer
20466 .entry(buffer_handle)
20467 .or_insert((Vec::new(), None))
20468 .0
20469 .push(range)
20470 } else {
20471 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20472 else {
20473 continue;
20474 };
20475 new_selections_by_buffer
20476 .entry(buffer_handle)
20477 .or_insert((Vec::new(), None))
20478 .0
20479 .push(range)
20480 }
20481 }
20482 }
20483 }
20484 }
20485
20486 new_selections_by_buffer
20487 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20488
20489 if new_selections_by_buffer.is_empty() {
20490 return;
20491 }
20492
20493 // We defer the pane interaction because we ourselves are a workspace item
20494 // and activating a new item causes the pane to call a method on us reentrantly,
20495 // which panics if we're on the stack.
20496 window.defer(cx, move |window, cx| {
20497 workspace.update(cx, |workspace, cx| {
20498 let pane = if split {
20499 workspace.adjacent_pane(window, cx)
20500 } else {
20501 workspace.active_pane().clone()
20502 };
20503
20504 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20505 let editor = buffer
20506 .read(cx)
20507 .file()
20508 .is_none()
20509 .then(|| {
20510 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20511 // so `workspace.open_project_item` will never find them, always opening a new editor.
20512 // Instead, we try to activate the existing editor in the pane first.
20513 let (editor, pane_item_index) =
20514 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20515 let editor = item.downcast::<Editor>()?;
20516 let singleton_buffer =
20517 editor.read(cx).buffer().read(cx).as_singleton()?;
20518 if singleton_buffer == buffer {
20519 Some((editor, i))
20520 } else {
20521 None
20522 }
20523 })?;
20524 pane.update(cx, |pane, cx| {
20525 pane.activate_item(pane_item_index, true, true, window, cx)
20526 });
20527 Some(editor)
20528 })
20529 .flatten()
20530 .unwrap_or_else(|| {
20531 workspace.open_project_item::<Self>(
20532 pane.clone(),
20533 buffer,
20534 true,
20535 true,
20536 window,
20537 cx,
20538 )
20539 });
20540
20541 editor.update(cx, |editor, cx| {
20542 let autoscroll = match scroll_offset {
20543 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20544 None => Autoscroll::newest(),
20545 };
20546 let nav_history = editor.nav_history.take();
20547 editor.change_selections(
20548 SelectionEffects::scroll(autoscroll),
20549 window,
20550 cx,
20551 |s| {
20552 s.select_ranges(ranges);
20553 },
20554 );
20555 editor.nav_history = nav_history;
20556 });
20557 }
20558 })
20559 });
20560 }
20561
20562 // For now, don't allow opening excerpts in buffers that aren't backed by
20563 // regular project files.
20564 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20565 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20566 }
20567
20568 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20569 let snapshot = self.buffer.read(cx).read(cx);
20570 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20571 Some(
20572 ranges
20573 .iter()
20574 .map(move |range| {
20575 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20576 })
20577 .collect(),
20578 )
20579 }
20580
20581 fn selection_replacement_ranges(
20582 &self,
20583 range: Range<OffsetUtf16>,
20584 cx: &mut App,
20585 ) -> Vec<Range<OffsetUtf16>> {
20586 let selections = self.selections.all::<OffsetUtf16>(cx);
20587 let newest_selection = selections
20588 .iter()
20589 .max_by_key(|selection| selection.id)
20590 .unwrap();
20591 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20592 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20593 let snapshot = self.buffer.read(cx).read(cx);
20594 selections
20595 .into_iter()
20596 .map(|mut selection| {
20597 selection.start.0 =
20598 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20599 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20600 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20601 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20602 })
20603 .collect()
20604 }
20605
20606 fn report_editor_event(
20607 &self,
20608 reported_event: ReportEditorEvent,
20609 file_extension: Option<String>,
20610 cx: &App,
20611 ) {
20612 if cfg!(any(test, feature = "test-support")) {
20613 return;
20614 }
20615
20616 let Some(project) = &self.project else { return };
20617
20618 // If None, we are in a file without an extension
20619 let file = self
20620 .buffer
20621 .read(cx)
20622 .as_singleton()
20623 .and_then(|b| b.read(cx).file());
20624 let file_extension = file_extension.or(file
20625 .as_ref()
20626 .and_then(|file| Path::new(file.file_name(cx)).extension())
20627 .and_then(|e| e.to_str())
20628 .map(|a| a.to_string()));
20629
20630 let vim_mode = vim_enabled(cx);
20631
20632 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20633 let copilot_enabled = edit_predictions_provider
20634 == language::language_settings::EditPredictionProvider::Copilot;
20635 let copilot_enabled_for_language = self
20636 .buffer
20637 .read(cx)
20638 .language_settings(cx)
20639 .show_edit_predictions;
20640
20641 let project = project.read(cx);
20642 let event_type = reported_event.event_type();
20643
20644 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20645 telemetry::event!(
20646 event_type,
20647 type = if auto_saved {"autosave"} else {"manual"},
20648 file_extension,
20649 vim_mode,
20650 copilot_enabled,
20651 copilot_enabled_for_language,
20652 edit_predictions_provider,
20653 is_via_ssh = project.is_via_ssh(),
20654 );
20655 } else {
20656 telemetry::event!(
20657 event_type,
20658 file_extension,
20659 vim_mode,
20660 copilot_enabled,
20661 copilot_enabled_for_language,
20662 edit_predictions_provider,
20663 is_via_ssh = project.is_via_ssh(),
20664 );
20665 };
20666 }
20667
20668 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20669 /// with each line being an array of {text, highlight} objects.
20670 fn copy_highlight_json(
20671 &mut self,
20672 _: &CopyHighlightJson,
20673 window: &mut Window,
20674 cx: &mut Context<Self>,
20675 ) {
20676 #[derive(Serialize)]
20677 struct Chunk<'a> {
20678 text: String,
20679 highlight: Option<&'a str>,
20680 }
20681
20682 let snapshot = self.buffer.read(cx).snapshot(cx);
20683 let range = self
20684 .selected_text_range(false, window, cx)
20685 .and_then(|selection| {
20686 if selection.range.is_empty() {
20687 None
20688 } else {
20689 Some(selection.range)
20690 }
20691 })
20692 .unwrap_or_else(|| 0..snapshot.len());
20693
20694 let chunks = snapshot.chunks(range, true);
20695 let mut lines = Vec::new();
20696 let mut line: VecDeque<Chunk> = VecDeque::new();
20697
20698 let Some(style) = self.style.as_ref() else {
20699 return;
20700 };
20701
20702 for chunk in chunks {
20703 let highlight = chunk
20704 .syntax_highlight_id
20705 .and_then(|id| id.name(&style.syntax));
20706 let mut chunk_lines = chunk.text.split('\n').peekable();
20707 while let Some(text) = chunk_lines.next() {
20708 let mut merged_with_last_token = false;
20709 if let Some(last_token) = line.back_mut() {
20710 if last_token.highlight == highlight {
20711 last_token.text.push_str(text);
20712 merged_with_last_token = true;
20713 }
20714 }
20715
20716 if !merged_with_last_token {
20717 line.push_back(Chunk {
20718 text: text.into(),
20719 highlight,
20720 });
20721 }
20722
20723 if chunk_lines.peek().is_some() {
20724 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20725 line.pop_front();
20726 }
20727 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20728 line.pop_back();
20729 }
20730
20731 lines.push(mem::take(&mut line));
20732 }
20733 }
20734 }
20735
20736 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20737 return;
20738 };
20739 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20740 }
20741
20742 pub fn open_context_menu(
20743 &mut self,
20744 _: &OpenContextMenu,
20745 window: &mut Window,
20746 cx: &mut Context<Self>,
20747 ) {
20748 self.request_autoscroll(Autoscroll::newest(), cx);
20749 let position = self.selections.newest_display(cx).start;
20750 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20751 }
20752
20753 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20754 &self.inlay_hint_cache
20755 }
20756
20757 pub fn replay_insert_event(
20758 &mut self,
20759 text: &str,
20760 relative_utf16_range: Option<Range<isize>>,
20761 window: &mut Window,
20762 cx: &mut Context<Self>,
20763 ) {
20764 if !self.input_enabled {
20765 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20766 return;
20767 }
20768 if let Some(relative_utf16_range) = relative_utf16_range {
20769 let selections = self.selections.all::<OffsetUtf16>(cx);
20770 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20771 let new_ranges = selections.into_iter().map(|range| {
20772 let start = OffsetUtf16(
20773 range
20774 .head()
20775 .0
20776 .saturating_add_signed(relative_utf16_range.start),
20777 );
20778 let end = OffsetUtf16(
20779 range
20780 .head()
20781 .0
20782 .saturating_add_signed(relative_utf16_range.end),
20783 );
20784 start..end
20785 });
20786 s.select_ranges(new_ranges);
20787 });
20788 }
20789
20790 self.handle_input(text, window, cx);
20791 }
20792
20793 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20794 let Some(provider) = self.semantics_provider.as_ref() else {
20795 return false;
20796 };
20797
20798 let mut supports = false;
20799 self.buffer().update(cx, |this, cx| {
20800 this.for_each_buffer(|buffer| {
20801 supports |= provider.supports_inlay_hints(buffer, cx);
20802 });
20803 });
20804
20805 supports
20806 }
20807
20808 pub fn is_focused(&self, window: &Window) -> bool {
20809 self.focus_handle.is_focused(window)
20810 }
20811
20812 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20813 cx.emit(EditorEvent::Focused);
20814
20815 if let Some(descendant) = self
20816 .last_focused_descendant
20817 .take()
20818 .and_then(|descendant| descendant.upgrade())
20819 {
20820 window.focus(&descendant);
20821 } else {
20822 if let Some(blame) = self.blame.as_ref() {
20823 blame.update(cx, GitBlame::focus)
20824 }
20825
20826 self.blink_manager.update(cx, BlinkManager::enable);
20827 self.show_cursor_names(window, cx);
20828 self.buffer.update(cx, |buffer, cx| {
20829 buffer.finalize_last_transaction(cx);
20830 if self.leader_id.is_none() {
20831 buffer.set_active_selections(
20832 &self.selections.disjoint_anchors(),
20833 self.selections.line_mode,
20834 self.cursor_shape,
20835 cx,
20836 );
20837 }
20838 });
20839 }
20840 }
20841
20842 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20843 cx.emit(EditorEvent::FocusedIn)
20844 }
20845
20846 fn handle_focus_out(
20847 &mut self,
20848 event: FocusOutEvent,
20849 _window: &mut Window,
20850 cx: &mut Context<Self>,
20851 ) {
20852 if event.blurred != self.focus_handle {
20853 self.last_focused_descendant = Some(event.blurred);
20854 }
20855 self.selection_drag_state = SelectionDragState::None;
20856 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20857 }
20858
20859 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20860 self.blink_manager.update(cx, BlinkManager::disable);
20861 self.buffer
20862 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20863
20864 if let Some(blame) = self.blame.as_ref() {
20865 blame.update(cx, GitBlame::blur)
20866 }
20867 if !self.hover_state.focused(window, cx) {
20868 hide_hover(self, cx);
20869 }
20870 if !self
20871 .context_menu
20872 .borrow()
20873 .as_ref()
20874 .is_some_and(|context_menu| context_menu.focused(window, cx))
20875 {
20876 self.hide_context_menu(window, cx);
20877 }
20878 self.discard_edit_prediction(false, cx);
20879 cx.emit(EditorEvent::Blurred);
20880 cx.notify();
20881 }
20882
20883 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20884 let mut pending: String = window
20885 .pending_input_keystrokes()
20886 .into_iter()
20887 .flatten()
20888 .filter_map(|keystroke| {
20889 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20890 keystroke.key_char.clone()
20891 } else {
20892 None
20893 }
20894 })
20895 .collect();
20896
20897 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20898 pending = "".to_string();
20899 }
20900
20901 let existing_pending = self
20902 .text_highlights::<PendingInput>(cx)
20903 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20904 if existing_pending.is_none() && pending.is_empty() {
20905 return;
20906 }
20907 let transaction =
20908 self.transact(window, cx, |this, window, cx| {
20909 let selections = this.selections.all::<usize>(cx);
20910 let edits = selections
20911 .iter()
20912 .map(|selection| (selection.end..selection.end, pending.clone()));
20913 this.edit(edits, cx);
20914 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20915 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20916 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20917 }));
20918 });
20919 if let Some(existing_ranges) = existing_pending {
20920 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20921 this.edit(edits, cx);
20922 }
20923 });
20924
20925 let snapshot = self.snapshot(window, cx);
20926 let ranges = self
20927 .selections
20928 .all::<usize>(cx)
20929 .into_iter()
20930 .map(|selection| {
20931 snapshot.buffer_snapshot.anchor_after(selection.end)
20932 ..snapshot
20933 .buffer_snapshot
20934 .anchor_before(selection.end + pending.len())
20935 })
20936 .collect();
20937
20938 if pending.is_empty() {
20939 self.clear_highlights::<PendingInput>(cx);
20940 } else {
20941 self.highlight_text::<PendingInput>(
20942 ranges,
20943 HighlightStyle {
20944 underline: Some(UnderlineStyle {
20945 thickness: px(1.),
20946 color: None,
20947 wavy: false,
20948 }),
20949 ..Default::default()
20950 },
20951 cx,
20952 );
20953 }
20954
20955 self.ime_transaction = self.ime_transaction.or(transaction);
20956 if let Some(transaction) = self.ime_transaction {
20957 self.buffer.update(cx, |buffer, cx| {
20958 buffer.group_until_transaction(transaction, cx);
20959 });
20960 }
20961
20962 if self.text_highlights::<PendingInput>(cx).is_none() {
20963 self.ime_transaction.take();
20964 }
20965 }
20966
20967 pub fn register_action_renderer(
20968 &mut self,
20969 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20970 ) -> Subscription {
20971 let id = self.next_editor_action_id.post_inc();
20972 self.editor_actions
20973 .borrow_mut()
20974 .insert(id, Box::new(listener));
20975
20976 let editor_actions = self.editor_actions.clone();
20977 Subscription::new(move || {
20978 editor_actions.borrow_mut().remove(&id);
20979 })
20980 }
20981
20982 pub fn register_action<A: Action>(
20983 &mut self,
20984 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20985 ) -> Subscription {
20986 let id = self.next_editor_action_id.post_inc();
20987 let listener = Arc::new(listener);
20988 self.editor_actions.borrow_mut().insert(
20989 id,
20990 Box::new(move |_, window, _| {
20991 let listener = listener.clone();
20992 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20993 let action = action.downcast_ref().unwrap();
20994 if phase == DispatchPhase::Bubble {
20995 listener(action, window, cx)
20996 }
20997 })
20998 }),
20999 );
21000
21001 let editor_actions = self.editor_actions.clone();
21002 Subscription::new(move || {
21003 editor_actions.borrow_mut().remove(&id);
21004 })
21005 }
21006
21007 pub fn file_header_size(&self) -> u32 {
21008 FILE_HEADER_HEIGHT
21009 }
21010
21011 pub fn restore(
21012 &mut self,
21013 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21014 window: &mut Window,
21015 cx: &mut Context<Self>,
21016 ) {
21017 let workspace = self.workspace();
21018 let project = self.project.as_ref();
21019 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21020 let mut tasks = Vec::new();
21021 for (buffer_id, changes) in revert_changes {
21022 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21023 buffer.update(cx, |buffer, cx| {
21024 buffer.edit(
21025 changes
21026 .into_iter()
21027 .map(|(range, text)| (range, text.to_string())),
21028 None,
21029 cx,
21030 );
21031 });
21032
21033 if let Some(project) =
21034 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21035 {
21036 project.update(cx, |project, cx| {
21037 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21038 })
21039 }
21040 }
21041 }
21042 tasks
21043 });
21044 cx.spawn_in(window, async move |_, cx| {
21045 for (buffer, task) in save_tasks {
21046 let result = task.await;
21047 if result.is_err() {
21048 let Some(path) = buffer
21049 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21050 .ok()
21051 else {
21052 continue;
21053 };
21054 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21055 let Some(task) = cx
21056 .update_window_entity(&workspace, |workspace, window, cx| {
21057 workspace
21058 .open_path_preview(path, None, false, false, false, window, cx)
21059 })
21060 .ok()
21061 else {
21062 continue;
21063 };
21064 task.await.log_err();
21065 }
21066 }
21067 }
21068 })
21069 .detach();
21070 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21071 selections.refresh()
21072 });
21073 }
21074
21075 pub fn to_pixel_point(
21076 &self,
21077 source: multi_buffer::Anchor,
21078 editor_snapshot: &EditorSnapshot,
21079 window: &mut Window,
21080 ) -> Option<gpui::Point<Pixels>> {
21081 let source_point = source.to_display_point(editor_snapshot);
21082 self.display_to_pixel_point(source_point, editor_snapshot, window)
21083 }
21084
21085 pub fn display_to_pixel_point(
21086 &self,
21087 source: DisplayPoint,
21088 editor_snapshot: &EditorSnapshot,
21089 window: &mut Window,
21090 ) -> Option<gpui::Point<Pixels>> {
21091 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21092 let text_layout_details = self.text_layout_details(window);
21093 let scroll_top = text_layout_details
21094 .scroll_anchor
21095 .scroll_position(editor_snapshot)
21096 .y;
21097
21098 if source.row().as_f32() < scroll_top.floor() {
21099 return None;
21100 }
21101 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21102 let source_y = line_height * (source.row().as_f32() - scroll_top);
21103 Some(gpui::Point::new(source_x, source_y))
21104 }
21105
21106 pub fn has_visible_completions_menu(&self) -> bool {
21107 !self.edit_prediction_preview_is_active()
21108 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
21109 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21110 })
21111 }
21112
21113 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21114 if self.mode.is_minimap() {
21115 return;
21116 }
21117 self.addons
21118 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21119 }
21120
21121 pub fn unregister_addon<T: Addon>(&mut self) {
21122 self.addons.remove(&std::any::TypeId::of::<T>());
21123 }
21124
21125 pub fn addon<T: Addon>(&self) -> Option<&T> {
21126 let type_id = std::any::TypeId::of::<T>();
21127 self.addons
21128 .get(&type_id)
21129 .and_then(|item| item.to_any().downcast_ref::<T>())
21130 }
21131
21132 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21133 let type_id = std::any::TypeId::of::<T>();
21134 self.addons
21135 .get_mut(&type_id)
21136 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21137 }
21138
21139 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21140 let text_layout_details = self.text_layout_details(window);
21141 let style = &text_layout_details.editor_style;
21142 let font_id = window.text_system().resolve_font(&style.text.font());
21143 let font_size = style.text.font_size.to_pixels(window.rem_size());
21144 let line_height = style.text.line_height_in_pixels(window.rem_size());
21145 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21146 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21147
21148 CharacterDimensions {
21149 em_width,
21150 em_advance,
21151 line_height,
21152 }
21153 }
21154
21155 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21156 self.load_diff_task.clone()
21157 }
21158
21159 fn read_metadata_from_db(
21160 &mut self,
21161 item_id: u64,
21162 workspace_id: WorkspaceId,
21163 window: &mut Window,
21164 cx: &mut Context<Editor>,
21165 ) {
21166 if self.is_singleton(cx)
21167 && !self.mode.is_minimap()
21168 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21169 {
21170 let buffer_snapshot = OnceCell::new();
21171
21172 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21173 if !folds.is_empty() {
21174 let snapshot =
21175 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21176 self.fold_ranges(
21177 folds
21178 .into_iter()
21179 .map(|(start, end)| {
21180 snapshot.clip_offset(start, Bias::Left)
21181 ..snapshot.clip_offset(end, Bias::Right)
21182 })
21183 .collect(),
21184 false,
21185 window,
21186 cx,
21187 );
21188 }
21189 }
21190
21191 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21192 if !selections.is_empty() {
21193 let snapshot =
21194 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21195 // skip adding the initial selection to selection history
21196 self.selection_history.mode = SelectionHistoryMode::Skipping;
21197 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21198 s.select_ranges(selections.into_iter().map(|(start, end)| {
21199 snapshot.clip_offset(start, Bias::Left)
21200 ..snapshot.clip_offset(end, Bias::Right)
21201 }));
21202 });
21203 self.selection_history.mode = SelectionHistoryMode::Normal;
21204 }
21205 };
21206 }
21207
21208 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21209 }
21210
21211 fn update_lsp_data(
21212 &mut self,
21213 ignore_cache: bool,
21214 for_buffer: Option<BufferId>,
21215 window: &mut Window,
21216 cx: &mut Context<'_, Self>,
21217 ) {
21218 self.pull_diagnostics(for_buffer, window, cx);
21219 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21220 }
21221}
21222
21223fn vim_enabled(cx: &App) -> bool {
21224 cx.global::<SettingsStore>()
21225 .raw_user_settings()
21226 .get("vim_mode")
21227 == Some(&serde_json::Value::Bool(true))
21228}
21229
21230fn process_completion_for_edit(
21231 completion: &Completion,
21232 intent: CompletionIntent,
21233 buffer: &Entity<Buffer>,
21234 cursor_position: &text::Anchor,
21235 cx: &mut Context<Editor>,
21236) -> CompletionEdit {
21237 let buffer = buffer.read(cx);
21238 let buffer_snapshot = buffer.snapshot();
21239 let (snippet, new_text) = if completion.is_snippet() {
21240 // Workaround for typescript language server issues so that methods don't expand within
21241 // strings and functions with type expressions. The previous point is used because the query
21242 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21243 let mut snippet_source = completion.new_text.clone();
21244 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21245 previous_point.column = previous_point.column.saturating_sub(1);
21246 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21247 if scope.prefers_label_for_snippet_in_completion() {
21248 if let Some(label) = completion.label() {
21249 if matches!(
21250 completion.kind(),
21251 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21252 ) {
21253 snippet_source = label;
21254 }
21255 }
21256 }
21257 }
21258 match Snippet::parse(&snippet_source).log_err() {
21259 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21260 None => (None, completion.new_text.clone()),
21261 }
21262 } else {
21263 (None, completion.new_text.clone())
21264 };
21265
21266 let mut range_to_replace = {
21267 let replace_range = &completion.replace_range;
21268 if let CompletionSource::Lsp {
21269 insert_range: Some(insert_range),
21270 ..
21271 } = &completion.source
21272 {
21273 debug_assert_eq!(
21274 insert_range.start, replace_range.start,
21275 "insert_range and replace_range should start at the same position"
21276 );
21277 debug_assert!(
21278 insert_range
21279 .start
21280 .cmp(&cursor_position, &buffer_snapshot)
21281 .is_le(),
21282 "insert_range should start before or at cursor position"
21283 );
21284 debug_assert!(
21285 replace_range
21286 .start
21287 .cmp(&cursor_position, &buffer_snapshot)
21288 .is_le(),
21289 "replace_range should start before or at cursor position"
21290 );
21291
21292 let should_replace = match intent {
21293 CompletionIntent::CompleteWithInsert => false,
21294 CompletionIntent::CompleteWithReplace => true,
21295 CompletionIntent::Complete | CompletionIntent::Compose => {
21296 let insert_mode =
21297 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21298 .completions
21299 .lsp_insert_mode;
21300 match insert_mode {
21301 LspInsertMode::Insert => false,
21302 LspInsertMode::Replace => true,
21303 LspInsertMode::ReplaceSubsequence => {
21304 let mut text_to_replace = buffer.chars_for_range(
21305 buffer.anchor_before(replace_range.start)
21306 ..buffer.anchor_after(replace_range.end),
21307 );
21308 let mut current_needle = text_to_replace.next();
21309 for haystack_ch in completion.label.text.chars() {
21310 if let Some(needle_ch) = current_needle {
21311 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21312 current_needle = text_to_replace.next();
21313 }
21314 }
21315 }
21316 current_needle.is_none()
21317 }
21318 LspInsertMode::ReplaceSuffix => {
21319 if replace_range
21320 .end
21321 .cmp(&cursor_position, &buffer_snapshot)
21322 .is_gt()
21323 {
21324 let range_after_cursor = *cursor_position..replace_range.end;
21325 let text_after_cursor = buffer
21326 .text_for_range(
21327 buffer.anchor_before(range_after_cursor.start)
21328 ..buffer.anchor_after(range_after_cursor.end),
21329 )
21330 .collect::<String>()
21331 .to_ascii_lowercase();
21332 completion
21333 .label
21334 .text
21335 .to_ascii_lowercase()
21336 .ends_with(&text_after_cursor)
21337 } else {
21338 true
21339 }
21340 }
21341 }
21342 }
21343 };
21344
21345 if should_replace {
21346 replace_range.clone()
21347 } else {
21348 insert_range.clone()
21349 }
21350 } else {
21351 replace_range.clone()
21352 }
21353 };
21354
21355 if range_to_replace
21356 .end
21357 .cmp(&cursor_position, &buffer_snapshot)
21358 .is_lt()
21359 {
21360 range_to_replace.end = *cursor_position;
21361 }
21362
21363 CompletionEdit {
21364 new_text,
21365 replace_range: range_to_replace.to_offset(&buffer),
21366 snippet,
21367 }
21368}
21369
21370struct CompletionEdit {
21371 new_text: String,
21372 replace_range: Range<usize>,
21373 snippet: Option<Snippet>,
21374}
21375
21376fn insert_extra_newline_brackets(
21377 buffer: &MultiBufferSnapshot,
21378 range: Range<usize>,
21379 language: &language::LanguageScope,
21380) -> bool {
21381 let leading_whitespace_len = buffer
21382 .reversed_chars_at(range.start)
21383 .take_while(|c| c.is_whitespace() && *c != '\n')
21384 .map(|c| c.len_utf8())
21385 .sum::<usize>();
21386 let trailing_whitespace_len = buffer
21387 .chars_at(range.end)
21388 .take_while(|c| c.is_whitespace() && *c != '\n')
21389 .map(|c| c.len_utf8())
21390 .sum::<usize>();
21391 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21392
21393 language.brackets().any(|(pair, enabled)| {
21394 let pair_start = pair.start.trim_end();
21395 let pair_end = pair.end.trim_start();
21396
21397 enabled
21398 && pair.newline
21399 && buffer.contains_str_at(range.end, pair_end)
21400 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21401 })
21402}
21403
21404fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21405 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21406 [(buffer, range, _)] => (*buffer, range.clone()),
21407 _ => return false,
21408 };
21409 let pair = {
21410 let mut result: Option<BracketMatch> = None;
21411
21412 for pair in buffer
21413 .all_bracket_ranges(range.clone())
21414 .filter(move |pair| {
21415 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21416 })
21417 {
21418 let len = pair.close_range.end - pair.open_range.start;
21419
21420 if let Some(existing) = &result {
21421 let existing_len = existing.close_range.end - existing.open_range.start;
21422 if len > existing_len {
21423 continue;
21424 }
21425 }
21426
21427 result = Some(pair);
21428 }
21429
21430 result
21431 };
21432 let Some(pair) = pair else {
21433 return false;
21434 };
21435 pair.newline_only
21436 && buffer
21437 .chars_for_range(pair.open_range.end..range.start)
21438 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21439 .all(|c| c.is_whitespace() && c != '\n')
21440}
21441
21442fn update_uncommitted_diff_for_buffer(
21443 editor: Entity<Editor>,
21444 project: &Entity<Project>,
21445 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21446 buffer: Entity<MultiBuffer>,
21447 cx: &mut App,
21448) -> Task<()> {
21449 let mut tasks = Vec::new();
21450 project.update(cx, |project, cx| {
21451 for buffer in buffers {
21452 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21453 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21454 }
21455 }
21456 });
21457 cx.spawn(async move |cx| {
21458 let diffs = future::join_all(tasks).await;
21459 if editor
21460 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21461 .unwrap_or(false)
21462 {
21463 return;
21464 }
21465
21466 buffer
21467 .update(cx, |buffer, cx| {
21468 for diff in diffs.into_iter().flatten() {
21469 buffer.add_diff(diff, cx);
21470 }
21471 })
21472 .ok();
21473 })
21474}
21475
21476fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21477 let tab_size = tab_size.get() as usize;
21478 let mut width = offset;
21479
21480 for ch in text.chars() {
21481 width += if ch == '\t' {
21482 tab_size - (width % tab_size)
21483 } else {
21484 1
21485 };
21486 }
21487
21488 width - offset
21489}
21490
21491#[cfg(test)]
21492mod tests {
21493 use super::*;
21494
21495 #[test]
21496 fn test_string_size_with_expanded_tabs() {
21497 let nz = |val| NonZeroU32::new(val).unwrap();
21498 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21499 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21500 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21501 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21502 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21503 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21504 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21505 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21506 }
21507}
21508
21509/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21510struct WordBreakingTokenizer<'a> {
21511 input: &'a str,
21512}
21513
21514impl<'a> WordBreakingTokenizer<'a> {
21515 fn new(input: &'a str) -> Self {
21516 Self { input }
21517 }
21518}
21519
21520fn is_char_ideographic(ch: char) -> bool {
21521 use unicode_script::Script::*;
21522 use unicode_script::UnicodeScript;
21523 matches!(ch.script(), Han | Tangut | Yi)
21524}
21525
21526fn is_grapheme_ideographic(text: &str) -> bool {
21527 text.chars().any(is_char_ideographic)
21528}
21529
21530fn is_grapheme_whitespace(text: &str) -> bool {
21531 text.chars().any(|x| x.is_whitespace())
21532}
21533
21534fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21535 text.chars().next().map_or(false, |ch| {
21536 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21537 })
21538}
21539
21540#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21541enum WordBreakToken<'a> {
21542 Word { token: &'a str, grapheme_len: usize },
21543 InlineWhitespace { token: &'a str, grapheme_len: usize },
21544 Newline,
21545}
21546
21547impl<'a> Iterator for WordBreakingTokenizer<'a> {
21548 /// Yields a span, the count of graphemes in the token, and whether it was
21549 /// whitespace. Note that it also breaks at word boundaries.
21550 type Item = WordBreakToken<'a>;
21551
21552 fn next(&mut self) -> Option<Self::Item> {
21553 use unicode_segmentation::UnicodeSegmentation;
21554 if self.input.is_empty() {
21555 return None;
21556 }
21557
21558 let mut iter = self.input.graphemes(true).peekable();
21559 let mut offset = 0;
21560 let mut grapheme_len = 0;
21561 if let Some(first_grapheme) = iter.next() {
21562 let is_newline = first_grapheme == "\n";
21563 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21564 offset += first_grapheme.len();
21565 grapheme_len += 1;
21566 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21567 if let Some(grapheme) = iter.peek().copied() {
21568 if should_stay_with_preceding_ideograph(grapheme) {
21569 offset += grapheme.len();
21570 grapheme_len += 1;
21571 }
21572 }
21573 } else {
21574 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21575 let mut next_word_bound = words.peek().copied();
21576 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21577 next_word_bound = words.next();
21578 }
21579 while let Some(grapheme) = iter.peek().copied() {
21580 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21581 break;
21582 };
21583 if is_grapheme_whitespace(grapheme) != is_whitespace
21584 || (grapheme == "\n") != is_newline
21585 {
21586 break;
21587 };
21588 offset += grapheme.len();
21589 grapheme_len += 1;
21590 iter.next();
21591 }
21592 }
21593 let token = &self.input[..offset];
21594 self.input = &self.input[offset..];
21595 if token == "\n" {
21596 Some(WordBreakToken::Newline)
21597 } else if is_whitespace {
21598 Some(WordBreakToken::InlineWhitespace {
21599 token,
21600 grapheme_len,
21601 })
21602 } else {
21603 Some(WordBreakToken::Word {
21604 token,
21605 grapheme_len,
21606 })
21607 }
21608 } else {
21609 None
21610 }
21611 }
21612}
21613
21614#[test]
21615fn test_word_breaking_tokenizer() {
21616 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21617 ("", &[]),
21618 (" ", &[whitespace(" ", 2)]),
21619 ("Ʒ", &[word("Ʒ", 1)]),
21620 ("Ǽ", &[word("Ǽ", 1)]),
21621 ("⋑", &[word("⋑", 1)]),
21622 ("⋑⋑", &[word("⋑⋑", 2)]),
21623 (
21624 "原理,进而",
21625 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21626 ),
21627 (
21628 "hello world",
21629 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21630 ),
21631 (
21632 "hello, world",
21633 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21634 ),
21635 (
21636 " hello world",
21637 &[
21638 whitespace(" ", 2),
21639 word("hello", 5),
21640 whitespace(" ", 1),
21641 word("world", 5),
21642 ],
21643 ),
21644 (
21645 "这是什么 \n 钢笔",
21646 &[
21647 word("这", 1),
21648 word("是", 1),
21649 word("什", 1),
21650 word("么", 1),
21651 whitespace(" ", 1),
21652 newline(),
21653 whitespace(" ", 1),
21654 word("钢", 1),
21655 word("笔", 1),
21656 ],
21657 ),
21658 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21659 ];
21660
21661 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21662 WordBreakToken::Word {
21663 token,
21664 grapheme_len,
21665 }
21666 }
21667
21668 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21669 WordBreakToken::InlineWhitespace {
21670 token,
21671 grapheme_len,
21672 }
21673 }
21674
21675 fn newline() -> WordBreakToken<'static> {
21676 WordBreakToken::Newline
21677 }
21678
21679 for (input, result) in tests {
21680 assert_eq!(
21681 WordBreakingTokenizer::new(input)
21682 .collect::<Vec<_>>()
21683 .as_slice(),
21684 *result,
21685 );
21686 }
21687}
21688
21689fn wrap_with_prefix(
21690 first_line_prefix: String,
21691 subsequent_lines_prefix: String,
21692 unwrapped_text: String,
21693 wrap_column: usize,
21694 tab_size: NonZeroU32,
21695 preserve_existing_whitespace: bool,
21696) -> String {
21697 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21698 let subsequent_lines_prefix_len =
21699 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21700 let mut wrapped_text = String::new();
21701 let mut current_line = first_line_prefix.clone();
21702 let mut is_first_line = true;
21703
21704 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21705 let mut current_line_len = first_line_prefix_len;
21706 let mut in_whitespace = false;
21707 for token in tokenizer {
21708 let have_preceding_whitespace = in_whitespace;
21709 match token {
21710 WordBreakToken::Word {
21711 token,
21712 grapheme_len,
21713 } => {
21714 in_whitespace = false;
21715 let current_prefix_len = if is_first_line {
21716 first_line_prefix_len
21717 } else {
21718 subsequent_lines_prefix_len
21719 };
21720 if current_line_len + grapheme_len > wrap_column
21721 && current_line_len != current_prefix_len
21722 {
21723 wrapped_text.push_str(current_line.trim_end());
21724 wrapped_text.push('\n');
21725 is_first_line = false;
21726 current_line = subsequent_lines_prefix.clone();
21727 current_line_len = subsequent_lines_prefix_len;
21728 }
21729 current_line.push_str(token);
21730 current_line_len += grapheme_len;
21731 }
21732 WordBreakToken::InlineWhitespace {
21733 mut token,
21734 mut grapheme_len,
21735 } => {
21736 in_whitespace = true;
21737 if have_preceding_whitespace && !preserve_existing_whitespace {
21738 continue;
21739 }
21740 if !preserve_existing_whitespace {
21741 token = " ";
21742 grapheme_len = 1;
21743 }
21744 let current_prefix_len = if is_first_line {
21745 first_line_prefix_len
21746 } else {
21747 subsequent_lines_prefix_len
21748 };
21749 if current_line_len + grapheme_len > wrap_column {
21750 wrapped_text.push_str(current_line.trim_end());
21751 wrapped_text.push('\n');
21752 is_first_line = false;
21753 current_line = subsequent_lines_prefix.clone();
21754 current_line_len = subsequent_lines_prefix_len;
21755 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21756 current_line.push_str(token);
21757 current_line_len += grapheme_len;
21758 }
21759 }
21760 WordBreakToken::Newline => {
21761 in_whitespace = true;
21762 let current_prefix_len = if is_first_line {
21763 first_line_prefix_len
21764 } else {
21765 subsequent_lines_prefix_len
21766 };
21767 if preserve_existing_whitespace {
21768 wrapped_text.push_str(current_line.trim_end());
21769 wrapped_text.push('\n');
21770 is_first_line = false;
21771 current_line = subsequent_lines_prefix.clone();
21772 current_line_len = subsequent_lines_prefix_len;
21773 } else if have_preceding_whitespace {
21774 continue;
21775 } else if current_line_len + 1 > wrap_column
21776 && current_line_len != current_prefix_len
21777 {
21778 wrapped_text.push_str(current_line.trim_end());
21779 wrapped_text.push('\n');
21780 is_first_line = false;
21781 current_line = subsequent_lines_prefix.clone();
21782 current_line_len = subsequent_lines_prefix_len;
21783 } else if current_line_len != current_prefix_len {
21784 current_line.push(' ');
21785 current_line_len += 1;
21786 }
21787 }
21788 }
21789 }
21790
21791 if !current_line.is_empty() {
21792 wrapped_text.push_str(¤t_line);
21793 }
21794 wrapped_text
21795}
21796
21797#[test]
21798fn test_wrap_with_prefix() {
21799 assert_eq!(
21800 wrap_with_prefix(
21801 "# ".to_string(),
21802 "# ".to_string(),
21803 "abcdefg".to_string(),
21804 4,
21805 NonZeroU32::new(4).unwrap(),
21806 false,
21807 ),
21808 "# abcdefg"
21809 );
21810 assert_eq!(
21811 wrap_with_prefix(
21812 "".to_string(),
21813 "".to_string(),
21814 "\thello world".to_string(),
21815 8,
21816 NonZeroU32::new(4).unwrap(),
21817 false,
21818 ),
21819 "hello\nworld"
21820 );
21821 assert_eq!(
21822 wrap_with_prefix(
21823 "// ".to_string(),
21824 "// ".to_string(),
21825 "xx \nyy zz aa bb cc".to_string(),
21826 12,
21827 NonZeroU32::new(4).unwrap(),
21828 false,
21829 ),
21830 "// xx yy zz\n// aa bb cc"
21831 );
21832 assert_eq!(
21833 wrap_with_prefix(
21834 String::new(),
21835 String::new(),
21836 "这是什么 \n 钢笔".to_string(),
21837 3,
21838 NonZeroU32::new(4).unwrap(),
21839 false,
21840 ),
21841 "这是什\n么 钢\n笔"
21842 );
21843}
21844
21845pub trait CollaborationHub {
21846 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21847 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21848 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21849}
21850
21851impl CollaborationHub for Entity<Project> {
21852 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21853 self.read(cx).collaborators()
21854 }
21855
21856 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21857 self.read(cx).user_store().read(cx).participant_indices()
21858 }
21859
21860 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21861 let this = self.read(cx);
21862 let user_ids = this.collaborators().values().map(|c| c.user_id);
21863 this.user_store().read(cx).participant_names(user_ids, cx)
21864 }
21865}
21866
21867pub trait SemanticsProvider {
21868 fn hover(
21869 &self,
21870 buffer: &Entity<Buffer>,
21871 position: text::Anchor,
21872 cx: &mut App,
21873 ) -> Option<Task<Vec<project::Hover>>>;
21874
21875 fn inline_values(
21876 &self,
21877 buffer_handle: Entity<Buffer>,
21878 range: Range<text::Anchor>,
21879 cx: &mut App,
21880 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21881
21882 fn inlay_hints(
21883 &self,
21884 buffer_handle: Entity<Buffer>,
21885 range: Range<text::Anchor>,
21886 cx: &mut App,
21887 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21888
21889 fn resolve_inlay_hint(
21890 &self,
21891 hint: InlayHint,
21892 buffer_handle: Entity<Buffer>,
21893 server_id: LanguageServerId,
21894 cx: &mut App,
21895 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21896
21897 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21898
21899 fn document_highlights(
21900 &self,
21901 buffer: &Entity<Buffer>,
21902 position: text::Anchor,
21903 cx: &mut App,
21904 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21905
21906 fn definitions(
21907 &self,
21908 buffer: &Entity<Buffer>,
21909 position: text::Anchor,
21910 kind: GotoDefinitionKind,
21911 cx: &mut App,
21912 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21913
21914 fn range_for_rename(
21915 &self,
21916 buffer: &Entity<Buffer>,
21917 position: text::Anchor,
21918 cx: &mut App,
21919 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21920
21921 fn perform_rename(
21922 &self,
21923 buffer: &Entity<Buffer>,
21924 position: text::Anchor,
21925 new_name: String,
21926 cx: &mut App,
21927 ) -> Option<Task<Result<ProjectTransaction>>>;
21928}
21929
21930pub trait CompletionProvider {
21931 fn completions(
21932 &self,
21933 excerpt_id: ExcerptId,
21934 buffer: &Entity<Buffer>,
21935 buffer_position: text::Anchor,
21936 trigger: CompletionContext,
21937 window: &mut Window,
21938 cx: &mut Context<Editor>,
21939 ) -> Task<Result<Vec<CompletionResponse>>>;
21940
21941 fn resolve_completions(
21942 &self,
21943 _buffer: Entity<Buffer>,
21944 _completion_indices: Vec<usize>,
21945 _completions: Rc<RefCell<Box<[Completion]>>>,
21946 _cx: &mut Context<Editor>,
21947 ) -> Task<Result<bool>> {
21948 Task::ready(Ok(false))
21949 }
21950
21951 fn apply_additional_edits_for_completion(
21952 &self,
21953 _buffer: Entity<Buffer>,
21954 _completions: Rc<RefCell<Box<[Completion]>>>,
21955 _completion_index: usize,
21956 _push_to_history: bool,
21957 _cx: &mut Context<Editor>,
21958 ) -> Task<Result<Option<language::Transaction>>> {
21959 Task::ready(Ok(None))
21960 }
21961
21962 fn is_completion_trigger(
21963 &self,
21964 buffer: &Entity<Buffer>,
21965 position: language::Anchor,
21966 text: &str,
21967 trigger_in_words: bool,
21968 menu_is_open: bool,
21969 cx: &mut Context<Editor>,
21970 ) -> bool;
21971
21972 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21973
21974 fn sort_completions(&self) -> bool {
21975 true
21976 }
21977
21978 fn filter_completions(&self) -> bool {
21979 true
21980 }
21981}
21982
21983pub trait CodeActionProvider {
21984 fn id(&self) -> Arc<str>;
21985
21986 fn code_actions(
21987 &self,
21988 buffer: &Entity<Buffer>,
21989 range: Range<text::Anchor>,
21990 window: &mut Window,
21991 cx: &mut App,
21992 ) -> Task<Result<Vec<CodeAction>>>;
21993
21994 fn apply_code_action(
21995 &self,
21996 buffer_handle: Entity<Buffer>,
21997 action: CodeAction,
21998 excerpt_id: ExcerptId,
21999 push_to_history: bool,
22000 window: &mut Window,
22001 cx: &mut App,
22002 ) -> Task<Result<ProjectTransaction>>;
22003}
22004
22005impl CodeActionProvider for Entity<Project> {
22006 fn id(&self) -> Arc<str> {
22007 "project".into()
22008 }
22009
22010 fn code_actions(
22011 &self,
22012 buffer: &Entity<Buffer>,
22013 range: Range<text::Anchor>,
22014 _window: &mut Window,
22015 cx: &mut App,
22016 ) -> Task<Result<Vec<CodeAction>>> {
22017 self.update(cx, |project, cx| {
22018 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22019 let code_actions = project.code_actions(buffer, range, None, cx);
22020 cx.background_spawn(async move {
22021 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22022 Ok(code_lens_actions
22023 .context("code lens fetch")?
22024 .into_iter()
22025 .chain(code_actions.context("code action fetch")?)
22026 .collect())
22027 })
22028 })
22029 }
22030
22031 fn apply_code_action(
22032 &self,
22033 buffer_handle: Entity<Buffer>,
22034 action: CodeAction,
22035 _excerpt_id: ExcerptId,
22036 push_to_history: bool,
22037 _window: &mut Window,
22038 cx: &mut App,
22039 ) -> Task<Result<ProjectTransaction>> {
22040 self.update(cx, |project, cx| {
22041 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22042 })
22043 }
22044}
22045
22046fn snippet_completions(
22047 project: &Project,
22048 buffer: &Entity<Buffer>,
22049 buffer_position: text::Anchor,
22050 cx: &mut App,
22051) -> Task<Result<CompletionResponse>> {
22052 let languages = buffer.read(cx).languages_at(buffer_position);
22053 let snippet_store = project.snippets().read(cx);
22054
22055 let scopes: Vec<_> = languages
22056 .iter()
22057 .filter_map(|language| {
22058 let language_name = language.lsp_id();
22059 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22060
22061 if snippets.is_empty() {
22062 None
22063 } else {
22064 Some((language.default_scope(), snippets))
22065 }
22066 })
22067 .collect();
22068
22069 if scopes.is_empty() {
22070 return Task::ready(Ok(CompletionResponse {
22071 completions: vec![],
22072 is_incomplete: false,
22073 }));
22074 }
22075
22076 let snapshot = buffer.read(cx).text_snapshot();
22077 let chars: String = snapshot
22078 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22079 .collect();
22080 let executor = cx.background_executor().clone();
22081
22082 cx.background_spawn(async move {
22083 let mut is_incomplete = false;
22084 let mut completions: Vec<Completion> = Vec::new();
22085 for (scope, snippets) in scopes.into_iter() {
22086 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22087 let mut last_word = chars
22088 .chars()
22089 .take_while(|c| classifier.is_word(*c))
22090 .collect::<String>();
22091 last_word = last_word.chars().rev().collect();
22092
22093 if last_word.is_empty() {
22094 return Ok(CompletionResponse {
22095 completions: vec![],
22096 is_incomplete: true,
22097 });
22098 }
22099
22100 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22101 let to_lsp = |point: &text::Anchor| {
22102 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22103 point_to_lsp(end)
22104 };
22105 let lsp_end = to_lsp(&buffer_position);
22106
22107 let candidates = snippets
22108 .iter()
22109 .enumerate()
22110 .flat_map(|(ix, snippet)| {
22111 snippet
22112 .prefix
22113 .iter()
22114 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
22115 })
22116 .collect::<Vec<StringMatchCandidate>>();
22117
22118 const MAX_RESULTS: usize = 100;
22119 let mut matches = fuzzy::match_strings(
22120 &candidates,
22121 &last_word,
22122 last_word.chars().any(|c| c.is_uppercase()),
22123 true,
22124 MAX_RESULTS,
22125 &Default::default(),
22126 executor.clone(),
22127 )
22128 .await;
22129
22130 if matches.len() >= MAX_RESULTS {
22131 is_incomplete = true;
22132 }
22133
22134 // Remove all candidates where the query's start does not match the start of any word in the candidate
22135 if let Some(query_start) = last_word.chars().next() {
22136 matches.retain(|string_match| {
22137 split_words(&string_match.string).any(|word| {
22138 // Check that the first codepoint of the word as lowercase matches the first
22139 // codepoint of the query as lowercase
22140 word.chars()
22141 .flat_map(|codepoint| codepoint.to_lowercase())
22142 .zip(query_start.to_lowercase())
22143 .all(|(word_cp, query_cp)| word_cp == query_cp)
22144 })
22145 });
22146 }
22147
22148 let matched_strings = matches
22149 .into_iter()
22150 .map(|m| m.string)
22151 .collect::<HashSet<_>>();
22152
22153 completions.extend(snippets.iter().filter_map(|snippet| {
22154 let matching_prefix = snippet
22155 .prefix
22156 .iter()
22157 .find(|prefix| matched_strings.contains(*prefix))?;
22158 let start = as_offset - last_word.len();
22159 let start = snapshot.anchor_before(start);
22160 let range = start..buffer_position;
22161 let lsp_start = to_lsp(&start);
22162 let lsp_range = lsp::Range {
22163 start: lsp_start,
22164 end: lsp_end,
22165 };
22166 Some(Completion {
22167 replace_range: range,
22168 new_text: snippet.body.clone(),
22169 source: CompletionSource::Lsp {
22170 insert_range: None,
22171 server_id: LanguageServerId(usize::MAX),
22172 resolved: true,
22173 lsp_completion: Box::new(lsp::CompletionItem {
22174 label: snippet.prefix.first().unwrap().clone(),
22175 kind: Some(CompletionItemKind::SNIPPET),
22176 label_details: snippet.description.as_ref().map(|description| {
22177 lsp::CompletionItemLabelDetails {
22178 detail: Some(description.clone()),
22179 description: None,
22180 }
22181 }),
22182 insert_text_format: Some(InsertTextFormat::SNIPPET),
22183 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22184 lsp::InsertReplaceEdit {
22185 new_text: snippet.body.clone(),
22186 insert: lsp_range,
22187 replace: lsp_range,
22188 },
22189 )),
22190 filter_text: Some(snippet.body.clone()),
22191 sort_text: Some(char::MAX.to_string()),
22192 ..lsp::CompletionItem::default()
22193 }),
22194 lsp_defaults: None,
22195 },
22196 label: CodeLabel {
22197 text: matching_prefix.clone(),
22198 runs: Vec::new(),
22199 filter_range: 0..matching_prefix.len(),
22200 },
22201 icon_path: None,
22202 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22203 single_line: snippet.name.clone().into(),
22204 plain_text: snippet
22205 .description
22206 .clone()
22207 .map(|description| description.into()),
22208 }),
22209 insert_text_mode: None,
22210 confirm: None,
22211 })
22212 }))
22213 }
22214
22215 Ok(CompletionResponse {
22216 completions,
22217 is_incomplete,
22218 })
22219 })
22220}
22221
22222impl CompletionProvider for Entity<Project> {
22223 fn completions(
22224 &self,
22225 _excerpt_id: ExcerptId,
22226 buffer: &Entity<Buffer>,
22227 buffer_position: text::Anchor,
22228 options: CompletionContext,
22229 _window: &mut Window,
22230 cx: &mut Context<Editor>,
22231 ) -> Task<Result<Vec<CompletionResponse>>> {
22232 self.update(cx, |project, cx| {
22233 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22234 let project_completions = project.completions(buffer, buffer_position, options, cx);
22235 cx.background_spawn(async move {
22236 let mut responses = project_completions.await?;
22237 let snippets = snippets.await?;
22238 if !snippets.completions.is_empty() {
22239 responses.push(snippets);
22240 }
22241 Ok(responses)
22242 })
22243 })
22244 }
22245
22246 fn resolve_completions(
22247 &self,
22248 buffer: Entity<Buffer>,
22249 completion_indices: Vec<usize>,
22250 completions: Rc<RefCell<Box<[Completion]>>>,
22251 cx: &mut Context<Editor>,
22252 ) -> Task<Result<bool>> {
22253 self.update(cx, |project, cx| {
22254 project.lsp_store().update(cx, |lsp_store, cx| {
22255 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22256 })
22257 })
22258 }
22259
22260 fn apply_additional_edits_for_completion(
22261 &self,
22262 buffer: Entity<Buffer>,
22263 completions: Rc<RefCell<Box<[Completion]>>>,
22264 completion_index: usize,
22265 push_to_history: bool,
22266 cx: &mut Context<Editor>,
22267 ) -> Task<Result<Option<language::Transaction>>> {
22268 self.update(cx, |project, cx| {
22269 project.lsp_store().update(cx, |lsp_store, cx| {
22270 lsp_store.apply_additional_edits_for_completion(
22271 buffer,
22272 completions,
22273 completion_index,
22274 push_to_history,
22275 cx,
22276 )
22277 })
22278 })
22279 }
22280
22281 fn is_completion_trigger(
22282 &self,
22283 buffer: &Entity<Buffer>,
22284 position: language::Anchor,
22285 text: &str,
22286 trigger_in_words: bool,
22287 menu_is_open: bool,
22288 cx: &mut Context<Editor>,
22289 ) -> bool {
22290 let mut chars = text.chars();
22291 let char = if let Some(char) = chars.next() {
22292 char
22293 } else {
22294 return false;
22295 };
22296 if chars.next().is_some() {
22297 return false;
22298 }
22299
22300 let buffer = buffer.read(cx);
22301 let snapshot = buffer.snapshot();
22302 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22303 return false;
22304 }
22305 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22306 if trigger_in_words && classifier.is_word(char) {
22307 return true;
22308 }
22309
22310 buffer.completion_triggers().contains(text)
22311 }
22312}
22313
22314impl SemanticsProvider for Entity<Project> {
22315 fn hover(
22316 &self,
22317 buffer: &Entity<Buffer>,
22318 position: text::Anchor,
22319 cx: &mut App,
22320 ) -> Option<Task<Vec<project::Hover>>> {
22321 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22322 }
22323
22324 fn document_highlights(
22325 &self,
22326 buffer: &Entity<Buffer>,
22327 position: text::Anchor,
22328 cx: &mut App,
22329 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22330 Some(self.update(cx, |project, cx| {
22331 project.document_highlights(buffer, position, cx)
22332 }))
22333 }
22334
22335 fn definitions(
22336 &self,
22337 buffer: &Entity<Buffer>,
22338 position: text::Anchor,
22339 kind: GotoDefinitionKind,
22340 cx: &mut App,
22341 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22342 Some(self.update(cx, |project, cx| match kind {
22343 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22344 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22345 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22346 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22347 }))
22348 }
22349
22350 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22351 self.update(cx, |project, cx| {
22352 if project
22353 .active_debug_session(cx)
22354 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22355 {
22356 return true;
22357 }
22358
22359 buffer.update(cx, |buffer, cx| {
22360 project.any_language_server_supports_inlay_hints(buffer, cx)
22361 })
22362 })
22363 }
22364
22365 fn inline_values(
22366 &self,
22367 buffer_handle: Entity<Buffer>,
22368 range: Range<text::Anchor>,
22369 cx: &mut App,
22370 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22371 self.update(cx, |project, cx| {
22372 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22373
22374 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22375 })
22376 }
22377
22378 fn inlay_hints(
22379 &self,
22380 buffer_handle: Entity<Buffer>,
22381 range: Range<text::Anchor>,
22382 cx: &mut App,
22383 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22384 Some(self.update(cx, |project, cx| {
22385 project.inlay_hints(buffer_handle, range, cx)
22386 }))
22387 }
22388
22389 fn resolve_inlay_hint(
22390 &self,
22391 hint: InlayHint,
22392 buffer_handle: Entity<Buffer>,
22393 server_id: LanguageServerId,
22394 cx: &mut App,
22395 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22396 Some(self.update(cx, |project, cx| {
22397 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22398 }))
22399 }
22400
22401 fn range_for_rename(
22402 &self,
22403 buffer: &Entity<Buffer>,
22404 position: text::Anchor,
22405 cx: &mut App,
22406 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22407 Some(self.update(cx, |project, cx| {
22408 let buffer = buffer.clone();
22409 let task = project.prepare_rename(buffer.clone(), position, cx);
22410 cx.spawn(async move |_, cx| {
22411 Ok(match task.await? {
22412 PrepareRenameResponse::Success(range) => Some(range),
22413 PrepareRenameResponse::InvalidPosition => None,
22414 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22415 // Fallback on using TreeSitter info to determine identifier range
22416 buffer.read_with(cx, |buffer, _| {
22417 let snapshot = buffer.snapshot();
22418 let (range, kind) = snapshot.surrounding_word(position, false);
22419 if kind != Some(CharKind::Word) {
22420 return None;
22421 }
22422 Some(
22423 snapshot.anchor_before(range.start)
22424 ..snapshot.anchor_after(range.end),
22425 )
22426 })?
22427 }
22428 })
22429 })
22430 }))
22431 }
22432
22433 fn perform_rename(
22434 &self,
22435 buffer: &Entity<Buffer>,
22436 position: text::Anchor,
22437 new_name: String,
22438 cx: &mut App,
22439 ) -> Option<Task<Result<ProjectTransaction>>> {
22440 Some(self.update(cx, |project, cx| {
22441 project.perform_rename(buffer.clone(), position, new_name, cx)
22442 }))
22443 }
22444}
22445
22446fn inlay_hint_settings(
22447 location: Anchor,
22448 snapshot: &MultiBufferSnapshot,
22449 cx: &mut Context<Editor>,
22450) -> InlayHintSettings {
22451 let file = snapshot.file_at(location);
22452 let language = snapshot.language_at(location).map(|l| l.name());
22453 language_settings(language, file, cx).inlay_hints
22454}
22455
22456fn consume_contiguous_rows(
22457 contiguous_row_selections: &mut Vec<Selection<Point>>,
22458 selection: &Selection<Point>,
22459 display_map: &DisplaySnapshot,
22460 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22461) -> (MultiBufferRow, MultiBufferRow) {
22462 contiguous_row_selections.push(selection.clone());
22463 let start_row = starting_row(selection, display_map);
22464 let mut end_row = ending_row(selection, display_map);
22465
22466 while let Some(next_selection) = selections.peek() {
22467 if next_selection.start.row <= end_row.0 {
22468 end_row = ending_row(next_selection, display_map);
22469 contiguous_row_selections.push(selections.next().unwrap().clone());
22470 } else {
22471 break;
22472 }
22473 }
22474 (start_row, end_row)
22475}
22476
22477fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22478 if selection.start.column > 0 {
22479 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22480 } else {
22481 MultiBufferRow(selection.start.row)
22482 }
22483}
22484
22485fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22486 if next_selection.end.column > 0 || next_selection.is_empty() {
22487 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22488 } else {
22489 MultiBufferRow(next_selection.end.row)
22490 }
22491}
22492
22493impl EditorSnapshot {
22494 pub fn remote_selections_in_range<'a>(
22495 &'a self,
22496 range: &'a Range<Anchor>,
22497 collaboration_hub: &dyn CollaborationHub,
22498 cx: &'a App,
22499 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22500 let participant_names = collaboration_hub.user_names(cx);
22501 let participant_indices = collaboration_hub.user_participant_indices(cx);
22502 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22503 let collaborators_by_replica_id = collaborators_by_peer_id
22504 .values()
22505 .map(|collaborator| (collaborator.replica_id, collaborator))
22506 .collect::<HashMap<_, _>>();
22507 self.buffer_snapshot
22508 .selections_in_range(range, false)
22509 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22510 if replica_id == AGENT_REPLICA_ID {
22511 Some(RemoteSelection {
22512 replica_id,
22513 selection,
22514 cursor_shape,
22515 line_mode,
22516 collaborator_id: CollaboratorId::Agent,
22517 user_name: Some("Agent".into()),
22518 color: cx.theme().players().agent(),
22519 })
22520 } else {
22521 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22522 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22523 let user_name = participant_names.get(&collaborator.user_id).cloned();
22524 Some(RemoteSelection {
22525 replica_id,
22526 selection,
22527 cursor_shape,
22528 line_mode,
22529 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22530 user_name,
22531 color: if let Some(index) = participant_index {
22532 cx.theme().players().color_for_participant(index.0)
22533 } else {
22534 cx.theme().players().absent()
22535 },
22536 })
22537 }
22538 })
22539 }
22540
22541 pub fn hunks_for_ranges(
22542 &self,
22543 ranges: impl IntoIterator<Item = Range<Point>>,
22544 ) -> Vec<MultiBufferDiffHunk> {
22545 let mut hunks = Vec::new();
22546 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22547 HashMap::default();
22548 for query_range in ranges {
22549 let query_rows =
22550 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22551 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22552 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22553 ) {
22554 // Include deleted hunks that are adjacent to the query range, because
22555 // otherwise they would be missed.
22556 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22557 if hunk.status().is_deleted() {
22558 intersects_range |= hunk.row_range.start == query_rows.end;
22559 intersects_range |= hunk.row_range.end == query_rows.start;
22560 }
22561 if intersects_range {
22562 if !processed_buffer_rows
22563 .entry(hunk.buffer_id)
22564 .or_default()
22565 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22566 {
22567 continue;
22568 }
22569 hunks.push(hunk);
22570 }
22571 }
22572 }
22573
22574 hunks
22575 }
22576
22577 fn display_diff_hunks_for_rows<'a>(
22578 &'a self,
22579 display_rows: Range<DisplayRow>,
22580 folded_buffers: &'a HashSet<BufferId>,
22581 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22582 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22583 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22584
22585 self.buffer_snapshot
22586 .diff_hunks_in_range(buffer_start..buffer_end)
22587 .filter_map(|hunk| {
22588 if folded_buffers.contains(&hunk.buffer_id) {
22589 return None;
22590 }
22591
22592 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22593 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22594
22595 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22596 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22597
22598 let display_hunk = if hunk_display_start.column() != 0 {
22599 DisplayDiffHunk::Folded {
22600 display_row: hunk_display_start.row(),
22601 }
22602 } else {
22603 let mut end_row = hunk_display_end.row();
22604 if hunk_display_end.column() > 0 {
22605 end_row.0 += 1;
22606 }
22607 let is_created_file = hunk.is_created_file();
22608 DisplayDiffHunk::Unfolded {
22609 status: hunk.status(),
22610 diff_base_byte_range: hunk.diff_base_byte_range,
22611 display_row_range: hunk_display_start.row()..end_row,
22612 multi_buffer_range: Anchor::range_in_buffer(
22613 hunk.excerpt_id,
22614 hunk.buffer_id,
22615 hunk.buffer_range,
22616 ),
22617 is_created_file,
22618 }
22619 };
22620
22621 Some(display_hunk)
22622 })
22623 }
22624
22625 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22626 self.display_snapshot.buffer_snapshot.language_at(position)
22627 }
22628
22629 pub fn is_focused(&self) -> bool {
22630 self.is_focused
22631 }
22632
22633 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22634 self.placeholder_text.as_ref()
22635 }
22636
22637 pub fn scroll_position(&self) -> gpui::Point<f32> {
22638 self.scroll_anchor.scroll_position(&self.display_snapshot)
22639 }
22640
22641 fn gutter_dimensions(
22642 &self,
22643 font_id: FontId,
22644 font_size: Pixels,
22645 max_line_number_width: Pixels,
22646 cx: &App,
22647 ) -> Option<GutterDimensions> {
22648 if !self.show_gutter {
22649 return None;
22650 }
22651
22652 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22653 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22654
22655 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22656 matches!(
22657 ProjectSettings::get_global(cx).git.git_gutter,
22658 Some(GitGutterSetting::TrackedFiles)
22659 )
22660 });
22661 let gutter_settings = EditorSettings::get_global(cx).gutter;
22662 let show_line_numbers = self
22663 .show_line_numbers
22664 .unwrap_or(gutter_settings.line_numbers);
22665 let line_gutter_width = if show_line_numbers {
22666 // Avoid flicker-like gutter resizes when the line number gains another digit by
22667 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22668 let min_width_for_number_on_gutter =
22669 ch_advance * gutter_settings.min_line_number_digits as f32;
22670 max_line_number_width.max(min_width_for_number_on_gutter)
22671 } else {
22672 0.0.into()
22673 };
22674
22675 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22676 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22677
22678 let git_blame_entries_width =
22679 self.git_blame_gutter_max_author_length
22680 .map(|max_author_length| {
22681 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22682 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22683
22684 /// The number of characters to dedicate to gaps and margins.
22685 const SPACING_WIDTH: usize = 4;
22686
22687 let max_char_count = max_author_length.min(renderer.max_author_length())
22688 + ::git::SHORT_SHA_LENGTH
22689 + MAX_RELATIVE_TIMESTAMP.len()
22690 + SPACING_WIDTH;
22691
22692 ch_advance * max_char_count
22693 });
22694
22695 let is_singleton = self.buffer_snapshot.is_singleton();
22696
22697 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22698 left_padding += if !is_singleton {
22699 ch_width * 4.0
22700 } else if show_runnables || show_breakpoints {
22701 ch_width * 3.0
22702 } else if show_git_gutter && show_line_numbers {
22703 ch_width * 2.0
22704 } else if show_git_gutter || show_line_numbers {
22705 ch_width
22706 } else {
22707 px(0.)
22708 };
22709
22710 let shows_folds = is_singleton && gutter_settings.folds;
22711
22712 let right_padding = if shows_folds && show_line_numbers {
22713 ch_width * 4.0
22714 } else if shows_folds || (!is_singleton && show_line_numbers) {
22715 ch_width * 3.0
22716 } else if show_line_numbers {
22717 ch_width
22718 } else {
22719 px(0.)
22720 };
22721
22722 Some(GutterDimensions {
22723 left_padding,
22724 right_padding,
22725 width: line_gutter_width + left_padding + right_padding,
22726 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22727 git_blame_entries_width,
22728 })
22729 }
22730
22731 pub fn render_crease_toggle(
22732 &self,
22733 buffer_row: MultiBufferRow,
22734 row_contains_cursor: bool,
22735 editor: Entity<Editor>,
22736 window: &mut Window,
22737 cx: &mut App,
22738 ) -> Option<AnyElement> {
22739 let folded = self.is_line_folded(buffer_row);
22740 let mut is_foldable = false;
22741
22742 if let Some(crease) = self
22743 .crease_snapshot
22744 .query_row(buffer_row, &self.buffer_snapshot)
22745 {
22746 is_foldable = true;
22747 match crease {
22748 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22749 if let Some(render_toggle) = render_toggle {
22750 let toggle_callback =
22751 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22752 if folded {
22753 editor.update(cx, |editor, cx| {
22754 editor.fold_at(buffer_row, window, cx)
22755 });
22756 } else {
22757 editor.update(cx, |editor, cx| {
22758 editor.unfold_at(buffer_row, window, cx)
22759 });
22760 }
22761 });
22762 return Some((render_toggle)(
22763 buffer_row,
22764 folded,
22765 toggle_callback,
22766 window,
22767 cx,
22768 ));
22769 }
22770 }
22771 }
22772 }
22773
22774 is_foldable |= self.starts_indent(buffer_row);
22775
22776 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22777 Some(
22778 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22779 .toggle_state(folded)
22780 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22781 if folded {
22782 this.unfold_at(buffer_row, window, cx);
22783 } else {
22784 this.fold_at(buffer_row, window, cx);
22785 }
22786 }))
22787 .into_any_element(),
22788 )
22789 } else {
22790 None
22791 }
22792 }
22793
22794 pub fn render_crease_trailer(
22795 &self,
22796 buffer_row: MultiBufferRow,
22797 window: &mut Window,
22798 cx: &mut App,
22799 ) -> Option<AnyElement> {
22800 let folded = self.is_line_folded(buffer_row);
22801 if let Crease::Inline { render_trailer, .. } = self
22802 .crease_snapshot
22803 .query_row(buffer_row, &self.buffer_snapshot)?
22804 {
22805 let render_trailer = render_trailer.as_ref()?;
22806 Some(render_trailer(buffer_row, folded, window, cx))
22807 } else {
22808 None
22809 }
22810 }
22811}
22812
22813impl Deref for EditorSnapshot {
22814 type Target = DisplaySnapshot;
22815
22816 fn deref(&self) -> &Self::Target {
22817 &self.display_snapshot
22818 }
22819}
22820
22821#[derive(Clone, Debug, PartialEq, Eq)]
22822pub enum EditorEvent {
22823 InputIgnored {
22824 text: Arc<str>,
22825 },
22826 InputHandled {
22827 utf16_range_to_replace: Option<Range<isize>>,
22828 text: Arc<str>,
22829 },
22830 ExcerptsAdded {
22831 buffer: Entity<Buffer>,
22832 predecessor: ExcerptId,
22833 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22834 },
22835 ExcerptsRemoved {
22836 ids: Vec<ExcerptId>,
22837 removed_buffer_ids: Vec<BufferId>,
22838 },
22839 BufferFoldToggled {
22840 ids: Vec<ExcerptId>,
22841 folded: bool,
22842 },
22843 ExcerptsEdited {
22844 ids: Vec<ExcerptId>,
22845 },
22846 ExcerptsExpanded {
22847 ids: Vec<ExcerptId>,
22848 },
22849 BufferEdited,
22850 Edited {
22851 transaction_id: clock::Lamport,
22852 },
22853 Reparsed(BufferId),
22854 Focused,
22855 FocusedIn,
22856 Blurred,
22857 DirtyChanged,
22858 Saved,
22859 TitleChanged,
22860 DiffBaseChanged,
22861 SelectionsChanged {
22862 local: bool,
22863 },
22864 ScrollPositionChanged {
22865 local: bool,
22866 autoscroll: bool,
22867 },
22868 Closed,
22869 TransactionUndone {
22870 transaction_id: clock::Lamport,
22871 },
22872 TransactionBegun {
22873 transaction_id: clock::Lamport,
22874 },
22875 Reloaded,
22876 CursorShapeChanged,
22877 BreadcrumbsChanged,
22878 PushedToNavHistory {
22879 anchor: Anchor,
22880 is_deactivate: bool,
22881 },
22882}
22883
22884impl EventEmitter<EditorEvent> for Editor {}
22885
22886impl Focusable for Editor {
22887 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22888 self.focus_handle.clone()
22889 }
22890}
22891
22892impl Render for Editor {
22893 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22894 let settings = ThemeSettings::get_global(cx);
22895
22896 let mut text_style = match self.mode {
22897 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22898 color: cx.theme().colors().editor_foreground,
22899 font_family: settings.ui_font.family.clone(),
22900 font_features: settings.ui_font.features.clone(),
22901 font_fallbacks: settings.ui_font.fallbacks.clone(),
22902 font_size: rems(0.875).into(),
22903 font_weight: settings.ui_font.weight,
22904 line_height: relative(settings.buffer_line_height.value()),
22905 ..Default::default()
22906 },
22907 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22908 color: cx.theme().colors().editor_foreground,
22909 font_family: settings.buffer_font.family.clone(),
22910 font_features: settings.buffer_font.features.clone(),
22911 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22912 font_size: settings.buffer_font_size(cx).into(),
22913 font_weight: settings.buffer_font.weight,
22914 line_height: relative(settings.buffer_line_height.value()),
22915 ..Default::default()
22916 },
22917 };
22918 if let Some(text_style_refinement) = &self.text_style_refinement {
22919 text_style.refine(text_style_refinement)
22920 }
22921
22922 let background = match self.mode {
22923 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22924 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22925 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22926 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22927 };
22928
22929 EditorElement::new(
22930 &cx.entity(),
22931 EditorStyle {
22932 background,
22933 border: cx.theme().colors().border,
22934 local_player: cx.theme().players().local(),
22935 text: text_style,
22936 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22937 syntax: cx.theme().syntax().clone(),
22938 status: cx.theme().status().clone(),
22939 inlay_hints_style: make_inlay_hints_style(cx),
22940 edit_prediction_styles: make_suggestion_styles(cx),
22941 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22942 show_underlines: self.diagnostics_enabled(),
22943 },
22944 )
22945 }
22946}
22947
22948impl EntityInputHandler for Editor {
22949 fn text_for_range(
22950 &mut self,
22951 range_utf16: Range<usize>,
22952 adjusted_range: &mut Option<Range<usize>>,
22953 _: &mut Window,
22954 cx: &mut Context<Self>,
22955 ) -> Option<String> {
22956 let snapshot = self.buffer.read(cx).read(cx);
22957 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22958 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22959 if (start.0..end.0) != range_utf16 {
22960 adjusted_range.replace(start.0..end.0);
22961 }
22962 Some(snapshot.text_for_range(start..end).collect())
22963 }
22964
22965 fn selected_text_range(
22966 &mut self,
22967 ignore_disabled_input: bool,
22968 _: &mut Window,
22969 cx: &mut Context<Self>,
22970 ) -> Option<UTF16Selection> {
22971 // Prevent the IME menu from appearing when holding down an alphabetic key
22972 // while input is disabled.
22973 if !ignore_disabled_input && !self.input_enabled {
22974 return None;
22975 }
22976
22977 let selection = self.selections.newest::<OffsetUtf16>(cx);
22978 let range = selection.range();
22979
22980 Some(UTF16Selection {
22981 range: range.start.0..range.end.0,
22982 reversed: selection.reversed,
22983 })
22984 }
22985
22986 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22987 let snapshot = self.buffer.read(cx).read(cx);
22988 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22989 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22990 }
22991
22992 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22993 self.clear_highlights::<InputComposition>(cx);
22994 self.ime_transaction.take();
22995 }
22996
22997 fn replace_text_in_range(
22998 &mut self,
22999 range_utf16: Option<Range<usize>>,
23000 text: &str,
23001 window: &mut Window,
23002 cx: &mut Context<Self>,
23003 ) {
23004 if !self.input_enabled {
23005 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23006 return;
23007 }
23008
23009 self.transact(window, cx, |this, window, cx| {
23010 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23011 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23012 Some(this.selection_replacement_ranges(range_utf16, cx))
23013 } else {
23014 this.marked_text_ranges(cx)
23015 };
23016
23017 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23018 let newest_selection_id = this.selections.newest_anchor().id;
23019 this.selections
23020 .all::<OffsetUtf16>(cx)
23021 .iter()
23022 .zip(ranges_to_replace.iter())
23023 .find_map(|(selection, range)| {
23024 if selection.id == newest_selection_id {
23025 Some(
23026 (range.start.0 as isize - selection.head().0 as isize)
23027 ..(range.end.0 as isize - selection.head().0 as isize),
23028 )
23029 } else {
23030 None
23031 }
23032 })
23033 });
23034
23035 cx.emit(EditorEvent::InputHandled {
23036 utf16_range_to_replace: range_to_replace,
23037 text: text.into(),
23038 });
23039
23040 if let Some(new_selected_ranges) = new_selected_ranges {
23041 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23042 selections.select_ranges(new_selected_ranges)
23043 });
23044 this.backspace(&Default::default(), window, cx);
23045 }
23046
23047 this.handle_input(text, window, cx);
23048 });
23049
23050 if let Some(transaction) = self.ime_transaction {
23051 self.buffer.update(cx, |buffer, cx| {
23052 buffer.group_until_transaction(transaction, cx);
23053 });
23054 }
23055
23056 self.unmark_text(window, cx);
23057 }
23058
23059 fn replace_and_mark_text_in_range(
23060 &mut self,
23061 range_utf16: Option<Range<usize>>,
23062 text: &str,
23063 new_selected_range_utf16: Option<Range<usize>>,
23064 window: &mut Window,
23065 cx: &mut Context<Self>,
23066 ) {
23067 if !self.input_enabled {
23068 return;
23069 }
23070
23071 let transaction = self.transact(window, cx, |this, window, cx| {
23072 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23073 let snapshot = this.buffer.read(cx).read(cx);
23074 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23075 for marked_range in &mut marked_ranges {
23076 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23077 marked_range.start.0 += relative_range_utf16.start;
23078 marked_range.start =
23079 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23080 marked_range.end =
23081 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23082 }
23083 }
23084 Some(marked_ranges)
23085 } else if let Some(range_utf16) = range_utf16 {
23086 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23087 Some(this.selection_replacement_ranges(range_utf16, cx))
23088 } else {
23089 None
23090 };
23091
23092 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23093 let newest_selection_id = this.selections.newest_anchor().id;
23094 this.selections
23095 .all::<OffsetUtf16>(cx)
23096 .iter()
23097 .zip(ranges_to_replace.iter())
23098 .find_map(|(selection, range)| {
23099 if selection.id == newest_selection_id {
23100 Some(
23101 (range.start.0 as isize - selection.head().0 as isize)
23102 ..(range.end.0 as isize - selection.head().0 as isize),
23103 )
23104 } else {
23105 None
23106 }
23107 })
23108 });
23109
23110 cx.emit(EditorEvent::InputHandled {
23111 utf16_range_to_replace: range_to_replace,
23112 text: text.into(),
23113 });
23114
23115 if let Some(ranges) = ranges_to_replace {
23116 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23117 s.select_ranges(ranges)
23118 });
23119 }
23120
23121 let marked_ranges = {
23122 let snapshot = this.buffer.read(cx).read(cx);
23123 this.selections
23124 .disjoint_anchors()
23125 .iter()
23126 .map(|selection| {
23127 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23128 })
23129 .collect::<Vec<_>>()
23130 };
23131
23132 if text.is_empty() {
23133 this.unmark_text(window, cx);
23134 } else {
23135 this.highlight_text::<InputComposition>(
23136 marked_ranges.clone(),
23137 HighlightStyle {
23138 underline: Some(UnderlineStyle {
23139 thickness: px(1.),
23140 color: None,
23141 wavy: false,
23142 }),
23143 ..Default::default()
23144 },
23145 cx,
23146 );
23147 }
23148
23149 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23150 let use_autoclose = this.use_autoclose;
23151 let use_auto_surround = this.use_auto_surround;
23152 this.set_use_autoclose(false);
23153 this.set_use_auto_surround(false);
23154 this.handle_input(text, window, cx);
23155 this.set_use_autoclose(use_autoclose);
23156 this.set_use_auto_surround(use_auto_surround);
23157
23158 if let Some(new_selected_range) = new_selected_range_utf16 {
23159 let snapshot = this.buffer.read(cx).read(cx);
23160 let new_selected_ranges = marked_ranges
23161 .into_iter()
23162 .map(|marked_range| {
23163 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23164 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23165 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23166 snapshot.clip_offset_utf16(new_start, Bias::Left)
23167 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23168 })
23169 .collect::<Vec<_>>();
23170
23171 drop(snapshot);
23172 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23173 selections.select_ranges(new_selected_ranges)
23174 });
23175 }
23176 });
23177
23178 self.ime_transaction = self.ime_transaction.or(transaction);
23179 if let Some(transaction) = self.ime_transaction {
23180 self.buffer.update(cx, |buffer, cx| {
23181 buffer.group_until_transaction(transaction, cx);
23182 });
23183 }
23184
23185 if self.text_highlights::<InputComposition>(cx).is_none() {
23186 self.ime_transaction.take();
23187 }
23188 }
23189
23190 fn bounds_for_range(
23191 &mut self,
23192 range_utf16: Range<usize>,
23193 element_bounds: gpui::Bounds<Pixels>,
23194 window: &mut Window,
23195 cx: &mut Context<Self>,
23196 ) -> Option<gpui::Bounds<Pixels>> {
23197 let text_layout_details = self.text_layout_details(window);
23198 let CharacterDimensions {
23199 em_width,
23200 em_advance,
23201 line_height,
23202 } = self.character_dimensions(window);
23203
23204 let snapshot = self.snapshot(window, cx);
23205 let scroll_position = snapshot.scroll_position();
23206 let scroll_left = scroll_position.x * em_advance;
23207
23208 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23209 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23210 + self.gutter_dimensions.full_width();
23211 let y = line_height * (start.row().as_f32() - scroll_position.y);
23212
23213 Some(Bounds {
23214 origin: element_bounds.origin + point(x, y),
23215 size: size(em_width, line_height),
23216 })
23217 }
23218
23219 fn character_index_for_point(
23220 &mut self,
23221 point: gpui::Point<Pixels>,
23222 _window: &mut Window,
23223 _cx: &mut Context<Self>,
23224 ) -> Option<usize> {
23225 let position_map = self.last_position_map.as_ref()?;
23226 if !position_map.text_hitbox.contains(&point) {
23227 return None;
23228 }
23229 let display_point = position_map.point_for_position(point).previous_valid;
23230 let anchor = position_map
23231 .snapshot
23232 .display_point_to_anchor(display_point, Bias::Left);
23233 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23234 Some(utf16_offset.0)
23235 }
23236}
23237
23238trait SelectionExt {
23239 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23240 fn spanned_rows(
23241 &self,
23242 include_end_if_at_line_start: bool,
23243 map: &DisplaySnapshot,
23244 ) -> Range<MultiBufferRow>;
23245}
23246
23247impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23248 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23249 let start = self
23250 .start
23251 .to_point(&map.buffer_snapshot)
23252 .to_display_point(map);
23253 let end = self
23254 .end
23255 .to_point(&map.buffer_snapshot)
23256 .to_display_point(map);
23257 if self.reversed {
23258 end..start
23259 } else {
23260 start..end
23261 }
23262 }
23263
23264 fn spanned_rows(
23265 &self,
23266 include_end_if_at_line_start: bool,
23267 map: &DisplaySnapshot,
23268 ) -> Range<MultiBufferRow> {
23269 let start = self.start.to_point(&map.buffer_snapshot);
23270 let mut end = self.end.to_point(&map.buffer_snapshot);
23271 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23272 end.row -= 1;
23273 }
23274
23275 let buffer_start = map.prev_line_boundary(start).0;
23276 let buffer_end = map.next_line_boundary(end).0;
23277 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23278 }
23279}
23280
23281impl<T: InvalidationRegion> InvalidationStack<T> {
23282 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23283 where
23284 S: Clone + ToOffset,
23285 {
23286 while let Some(region) = self.last() {
23287 let all_selections_inside_invalidation_ranges =
23288 if selections.len() == region.ranges().len() {
23289 selections
23290 .iter()
23291 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23292 .all(|(selection, invalidation_range)| {
23293 let head = selection.head().to_offset(buffer);
23294 invalidation_range.start <= head && invalidation_range.end >= head
23295 })
23296 } else {
23297 false
23298 };
23299
23300 if all_selections_inside_invalidation_ranges {
23301 break;
23302 } else {
23303 self.pop();
23304 }
23305 }
23306 }
23307}
23308
23309impl<T> Default for InvalidationStack<T> {
23310 fn default() -> Self {
23311 Self(Default::default())
23312 }
23313}
23314
23315impl<T> Deref for InvalidationStack<T> {
23316 type Target = Vec<T>;
23317
23318 fn deref(&self) -> &Self::Target {
23319 &self.0
23320 }
23321}
23322
23323impl<T> DerefMut for InvalidationStack<T> {
23324 fn deref_mut(&mut self) -> &mut Self::Target {
23325 &mut self.0
23326 }
23327}
23328
23329impl InvalidationRegion for SnippetState {
23330 fn ranges(&self) -> &[Range<Anchor>] {
23331 &self.ranges[self.active_index]
23332 }
23333}
23334
23335fn edit_prediction_edit_text(
23336 current_snapshot: &BufferSnapshot,
23337 edits: &[(Range<Anchor>, String)],
23338 edit_preview: &EditPreview,
23339 include_deletions: bool,
23340 cx: &App,
23341) -> HighlightedText {
23342 let edits = edits
23343 .iter()
23344 .map(|(anchor, text)| {
23345 (
23346 anchor.start.text_anchor..anchor.end.text_anchor,
23347 text.clone(),
23348 )
23349 })
23350 .collect::<Vec<_>>();
23351
23352 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23353}
23354
23355fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23356 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23357 // Just show the raw edit text with basic styling
23358 let mut text = String::new();
23359 let mut highlights = Vec::new();
23360
23361 let insertion_highlight_style = HighlightStyle {
23362 color: Some(cx.theme().colors().text),
23363 ..Default::default()
23364 };
23365
23366 for (_, edit_text) in edits {
23367 let start_offset = text.len();
23368 text.push_str(edit_text);
23369 let end_offset = text.len();
23370
23371 if start_offset < end_offset {
23372 highlights.push((start_offset..end_offset, insertion_highlight_style));
23373 }
23374 }
23375
23376 HighlightedText {
23377 text: text.into(),
23378 highlights,
23379 }
23380}
23381
23382pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23383 match severity {
23384 lsp::DiagnosticSeverity::ERROR => colors.error,
23385 lsp::DiagnosticSeverity::WARNING => colors.warning,
23386 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23387 lsp::DiagnosticSeverity::HINT => colors.info,
23388 _ => colors.ignored,
23389 }
23390}
23391
23392pub fn styled_runs_for_code_label<'a>(
23393 label: &'a CodeLabel,
23394 syntax_theme: &'a theme::SyntaxTheme,
23395) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23396 let fade_out = HighlightStyle {
23397 fade_out: Some(0.35),
23398 ..Default::default()
23399 };
23400
23401 let mut prev_end = label.filter_range.end;
23402 label
23403 .runs
23404 .iter()
23405 .enumerate()
23406 .flat_map(move |(ix, (range, highlight_id))| {
23407 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23408 style
23409 } else {
23410 return Default::default();
23411 };
23412 let mut muted_style = style;
23413 muted_style.highlight(fade_out);
23414
23415 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23416 if range.start >= label.filter_range.end {
23417 if range.start > prev_end {
23418 runs.push((prev_end..range.start, fade_out));
23419 }
23420 runs.push((range.clone(), muted_style));
23421 } else if range.end <= label.filter_range.end {
23422 runs.push((range.clone(), style));
23423 } else {
23424 runs.push((range.start..label.filter_range.end, style));
23425 runs.push((label.filter_range.end..range.end, muted_style));
23426 }
23427 prev_end = cmp::max(prev_end, range.end);
23428
23429 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23430 runs.push((prev_end..label.text.len(), fade_out));
23431 }
23432
23433 runs
23434 })
23435}
23436
23437pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23438 let mut prev_index = 0;
23439 let mut prev_codepoint: Option<char> = None;
23440 text.char_indices()
23441 .chain([(text.len(), '\0')])
23442 .filter_map(move |(index, codepoint)| {
23443 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23444 let is_boundary = index == text.len()
23445 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23446 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23447 if is_boundary {
23448 let chunk = &text[prev_index..index];
23449 prev_index = index;
23450 Some(chunk)
23451 } else {
23452 None
23453 }
23454 })
23455}
23456
23457pub trait RangeToAnchorExt: Sized {
23458 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23459
23460 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23461 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23462 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23463 }
23464}
23465
23466impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23467 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23468 let start_offset = self.start.to_offset(snapshot);
23469 let end_offset = self.end.to_offset(snapshot);
23470 if start_offset == end_offset {
23471 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23472 } else {
23473 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23474 }
23475 }
23476}
23477
23478pub trait RowExt {
23479 fn as_f32(&self) -> f32;
23480
23481 fn next_row(&self) -> Self;
23482
23483 fn previous_row(&self) -> Self;
23484
23485 fn minus(&self, other: Self) -> u32;
23486}
23487
23488impl RowExt for DisplayRow {
23489 fn as_f32(&self) -> f32 {
23490 self.0 as f32
23491 }
23492
23493 fn next_row(&self) -> Self {
23494 Self(self.0 + 1)
23495 }
23496
23497 fn previous_row(&self) -> Self {
23498 Self(self.0.saturating_sub(1))
23499 }
23500
23501 fn minus(&self, other: Self) -> u32 {
23502 self.0 - other.0
23503 }
23504}
23505
23506impl RowExt for MultiBufferRow {
23507 fn as_f32(&self) -> f32 {
23508 self.0 as f32
23509 }
23510
23511 fn next_row(&self) -> Self {
23512 Self(self.0 + 1)
23513 }
23514
23515 fn previous_row(&self) -> Self {
23516 Self(self.0.saturating_sub(1))
23517 }
23518
23519 fn minus(&self, other: Self) -> u32 {
23520 self.0 - other.0
23521 }
23522}
23523
23524trait RowRangeExt {
23525 type Row;
23526
23527 fn len(&self) -> usize;
23528
23529 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23530}
23531
23532impl RowRangeExt for Range<MultiBufferRow> {
23533 type Row = MultiBufferRow;
23534
23535 fn len(&self) -> usize {
23536 (self.end.0 - self.start.0) as usize
23537 }
23538
23539 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23540 (self.start.0..self.end.0).map(MultiBufferRow)
23541 }
23542}
23543
23544impl RowRangeExt for Range<DisplayRow> {
23545 type Row = DisplayRow;
23546
23547 fn len(&self) -> usize {
23548 (self.end.0 - self.start.0) as usize
23549 }
23550
23551 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23552 (self.start.0..self.end.0).map(DisplayRow)
23553 }
23554}
23555
23556/// If select range has more than one line, we
23557/// just point the cursor to range.start.
23558fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23559 if range.start.row == range.end.row {
23560 range
23561 } else {
23562 range.start..range.start
23563 }
23564}
23565pub struct KillRing(ClipboardItem);
23566impl Global for KillRing {}
23567
23568const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23569
23570enum BreakpointPromptEditAction {
23571 Log,
23572 Condition,
23573 HitCondition,
23574}
23575
23576struct BreakpointPromptEditor {
23577 pub(crate) prompt: Entity<Editor>,
23578 editor: WeakEntity<Editor>,
23579 breakpoint_anchor: Anchor,
23580 breakpoint: Breakpoint,
23581 edit_action: BreakpointPromptEditAction,
23582 block_ids: HashSet<CustomBlockId>,
23583 editor_margins: Arc<Mutex<EditorMargins>>,
23584 _subscriptions: Vec<Subscription>,
23585}
23586
23587impl BreakpointPromptEditor {
23588 const MAX_LINES: u8 = 4;
23589
23590 fn new(
23591 editor: WeakEntity<Editor>,
23592 breakpoint_anchor: Anchor,
23593 breakpoint: Breakpoint,
23594 edit_action: BreakpointPromptEditAction,
23595 window: &mut Window,
23596 cx: &mut Context<Self>,
23597 ) -> Self {
23598 let base_text = match edit_action {
23599 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23600 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23601 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23602 }
23603 .map(|msg| msg.to_string())
23604 .unwrap_or_default();
23605
23606 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23607 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23608
23609 let prompt = cx.new(|cx| {
23610 let mut prompt = Editor::new(
23611 EditorMode::AutoHeight {
23612 min_lines: 1,
23613 max_lines: Some(Self::MAX_LINES as usize),
23614 },
23615 buffer,
23616 None,
23617 window,
23618 cx,
23619 );
23620 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23621 prompt.set_show_cursor_when_unfocused(false, cx);
23622 prompt.set_placeholder_text(
23623 match edit_action {
23624 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23625 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23626 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23627 },
23628 cx,
23629 );
23630
23631 prompt
23632 });
23633
23634 Self {
23635 prompt,
23636 editor,
23637 breakpoint_anchor,
23638 breakpoint,
23639 edit_action,
23640 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23641 block_ids: Default::default(),
23642 _subscriptions: vec![],
23643 }
23644 }
23645
23646 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23647 self.block_ids.extend(block_ids)
23648 }
23649
23650 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23651 if let Some(editor) = self.editor.upgrade() {
23652 let message = self
23653 .prompt
23654 .read(cx)
23655 .buffer
23656 .read(cx)
23657 .as_singleton()
23658 .expect("A multi buffer in breakpoint prompt isn't possible")
23659 .read(cx)
23660 .as_rope()
23661 .to_string();
23662
23663 editor.update(cx, |editor, cx| {
23664 editor.edit_breakpoint_at_anchor(
23665 self.breakpoint_anchor,
23666 self.breakpoint.clone(),
23667 match self.edit_action {
23668 BreakpointPromptEditAction::Log => {
23669 BreakpointEditAction::EditLogMessage(message.into())
23670 }
23671 BreakpointPromptEditAction::Condition => {
23672 BreakpointEditAction::EditCondition(message.into())
23673 }
23674 BreakpointPromptEditAction::HitCondition => {
23675 BreakpointEditAction::EditHitCondition(message.into())
23676 }
23677 },
23678 cx,
23679 );
23680
23681 editor.remove_blocks(self.block_ids.clone(), None, cx);
23682 cx.focus_self(window);
23683 });
23684 }
23685 }
23686
23687 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23688 self.editor
23689 .update(cx, |editor, cx| {
23690 editor.remove_blocks(self.block_ids.clone(), None, cx);
23691 window.focus(&editor.focus_handle);
23692 })
23693 .log_err();
23694 }
23695
23696 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23697 let settings = ThemeSettings::get_global(cx);
23698 let text_style = TextStyle {
23699 color: if self.prompt.read(cx).read_only(cx) {
23700 cx.theme().colors().text_disabled
23701 } else {
23702 cx.theme().colors().text
23703 },
23704 font_family: settings.buffer_font.family.clone(),
23705 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23706 font_size: settings.buffer_font_size(cx).into(),
23707 font_weight: settings.buffer_font.weight,
23708 line_height: relative(settings.buffer_line_height.value()),
23709 ..Default::default()
23710 };
23711 EditorElement::new(
23712 &self.prompt,
23713 EditorStyle {
23714 background: cx.theme().colors().editor_background,
23715 local_player: cx.theme().players().local(),
23716 text: text_style,
23717 ..Default::default()
23718 },
23719 )
23720 }
23721}
23722
23723impl Render for BreakpointPromptEditor {
23724 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23725 let editor_margins = *self.editor_margins.lock();
23726 let gutter_dimensions = editor_margins.gutter;
23727 h_flex()
23728 .key_context("Editor")
23729 .bg(cx.theme().colors().editor_background)
23730 .border_y_1()
23731 .border_color(cx.theme().status().info_border)
23732 .size_full()
23733 .py(window.line_height() / 2.5)
23734 .on_action(cx.listener(Self::confirm))
23735 .on_action(cx.listener(Self::cancel))
23736 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23737 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23738 }
23739}
23740
23741impl Focusable for BreakpointPromptEditor {
23742 fn focus_handle(&self, cx: &App) -> FocusHandle {
23743 self.prompt.focus_handle(cx)
23744 }
23745}
23746
23747fn all_edits_insertions_or_deletions(
23748 edits: &Vec<(Range<Anchor>, String)>,
23749 snapshot: &MultiBufferSnapshot,
23750) -> bool {
23751 let mut all_insertions = true;
23752 let mut all_deletions = true;
23753
23754 for (range, new_text) in edits.iter() {
23755 let range_is_empty = range.to_offset(&snapshot).is_empty();
23756 let text_is_empty = new_text.is_empty();
23757
23758 if range_is_empty != text_is_empty {
23759 if range_is_empty {
23760 all_deletions = false;
23761 } else {
23762 all_insertions = false;
23763 }
23764 } else {
23765 return false;
23766 }
23767
23768 if !all_insertions && !all_deletions {
23769 return false;
23770 }
23771 }
23772 all_insertions || all_deletions
23773}
23774
23775struct MissingEditPredictionKeybindingTooltip;
23776
23777impl Render for MissingEditPredictionKeybindingTooltip {
23778 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23779 ui::tooltip_container(window, cx, |container, _, cx| {
23780 container
23781 .flex_shrink_0()
23782 .max_w_80()
23783 .min_h(rems_from_px(124.))
23784 .justify_between()
23785 .child(
23786 v_flex()
23787 .flex_1()
23788 .text_ui_sm(cx)
23789 .child(Label::new("Conflict with Accept Keybinding"))
23790 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23791 )
23792 .child(
23793 h_flex()
23794 .pb_1()
23795 .gap_1()
23796 .items_end()
23797 .w_full()
23798 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23799 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23800 }))
23801 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23802 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23803 })),
23804 )
23805 })
23806 }
23807}
23808
23809#[derive(Debug, Clone, Copy, PartialEq)]
23810pub struct LineHighlight {
23811 pub background: Background,
23812 pub border: Option<gpui::Hsla>,
23813 pub include_gutter: bool,
23814 pub type_id: Option<TypeId>,
23815}
23816
23817struct LineManipulationResult {
23818 pub new_text: String,
23819 pub line_count_before: usize,
23820 pub line_count_after: usize,
23821}
23822
23823fn render_diff_hunk_controls(
23824 row: u32,
23825 status: &DiffHunkStatus,
23826 hunk_range: Range<Anchor>,
23827 is_created_file: bool,
23828 line_height: Pixels,
23829 editor: &Entity<Editor>,
23830 _window: &mut Window,
23831 cx: &mut App,
23832) -> AnyElement {
23833 h_flex()
23834 .h(line_height)
23835 .mr_1()
23836 .gap_1()
23837 .px_0p5()
23838 .pb_1()
23839 .border_x_1()
23840 .border_b_1()
23841 .border_color(cx.theme().colors().border_variant)
23842 .rounded_b_lg()
23843 .bg(cx.theme().colors().editor_background)
23844 .gap_1()
23845 .block_mouse_except_scroll()
23846 .shadow_md()
23847 .child(if status.has_secondary_hunk() {
23848 Button::new(("stage", row as u64), "Stage")
23849 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23850 .tooltip({
23851 let focus_handle = editor.focus_handle(cx);
23852 move |window, cx| {
23853 Tooltip::for_action_in(
23854 "Stage Hunk",
23855 &::git::ToggleStaged,
23856 &focus_handle,
23857 window,
23858 cx,
23859 )
23860 }
23861 })
23862 .on_click({
23863 let editor = editor.clone();
23864 move |_event, _window, cx| {
23865 editor.update(cx, |editor, cx| {
23866 editor.stage_or_unstage_diff_hunks(
23867 true,
23868 vec![hunk_range.start..hunk_range.start],
23869 cx,
23870 );
23871 });
23872 }
23873 })
23874 } else {
23875 Button::new(("unstage", row as u64), "Unstage")
23876 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23877 .tooltip({
23878 let focus_handle = editor.focus_handle(cx);
23879 move |window, cx| {
23880 Tooltip::for_action_in(
23881 "Unstage Hunk",
23882 &::git::ToggleStaged,
23883 &focus_handle,
23884 window,
23885 cx,
23886 )
23887 }
23888 })
23889 .on_click({
23890 let editor = editor.clone();
23891 move |_event, _window, cx| {
23892 editor.update(cx, |editor, cx| {
23893 editor.stage_or_unstage_diff_hunks(
23894 false,
23895 vec![hunk_range.start..hunk_range.start],
23896 cx,
23897 );
23898 });
23899 }
23900 })
23901 })
23902 .child(
23903 Button::new(("restore", row as u64), "Restore")
23904 .tooltip({
23905 let focus_handle = editor.focus_handle(cx);
23906 move |window, cx| {
23907 Tooltip::for_action_in(
23908 "Restore Hunk",
23909 &::git::Restore,
23910 &focus_handle,
23911 window,
23912 cx,
23913 )
23914 }
23915 })
23916 .on_click({
23917 let editor = editor.clone();
23918 move |_event, window, cx| {
23919 editor.update(cx, |editor, cx| {
23920 let snapshot = editor.snapshot(window, cx);
23921 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23922 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23923 });
23924 }
23925 })
23926 .disabled(is_created_file),
23927 )
23928 .when(
23929 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23930 |el| {
23931 el.child(
23932 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23933 .shape(IconButtonShape::Square)
23934 .icon_size(IconSize::Small)
23935 // .disabled(!has_multiple_hunks)
23936 .tooltip({
23937 let focus_handle = editor.focus_handle(cx);
23938 move |window, cx| {
23939 Tooltip::for_action_in(
23940 "Next Hunk",
23941 &GoToHunk,
23942 &focus_handle,
23943 window,
23944 cx,
23945 )
23946 }
23947 })
23948 .on_click({
23949 let editor = editor.clone();
23950 move |_event, window, cx| {
23951 editor.update(cx, |editor, cx| {
23952 let snapshot = editor.snapshot(window, cx);
23953 let position =
23954 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23955 editor.go_to_hunk_before_or_after_position(
23956 &snapshot,
23957 position,
23958 Direction::Next,
23959 window,
23960 cx,
23961 );
23962 editor.expand_selected_diff_hunks(cx);
23963 });
23964 }
23965 }),
23966 )
23967 .child(
23968 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23969 .shape(IconButtonShape::Square)
23970 .icon_size(IconSize::Small)
23971 // .disabled(!has_multiple_hunks)
23972 .tooltip({
23973 let focus_handle = editor.focus_handle(cx);
23974 move |window, cx| {
23975 Tooltip::for_action_in(
23976 "Previous Hunk",
23977 &GoToPreviousHunk,
23978 &focus_handle,
23979 window,
23980 cx,
23981 )
23982 }
23983 })
23984 .on_click({
23985 let editor = editor.clone();
23986 move |_event, window, cx| {
23987 editor.update(cx, |editor, cx| {
23988 let snapshot = editor.snapshot(window, cx);
23989 let point =
23990 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23991 editor.go_to_hunk_before_or_after_position(
23992 &snapshot,
23993 point,
23994 Direction::Prev,
23995 window,
23996 cx,
23997 );
23998 editor.expand_selected_diff_hunks(cx);
23999 });
24000 }
24001 }),
24002 )
24003 },
24004 )
24005 .into_any_element()
24006}