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::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
253struct InlineValueCache {
254 enabled: bool,
255 inlays: Vec<InlayId>,
256 refresh_task: Task<Option<()>>,
257}
258
259impl InlineValueCache {
260 fn new(enabled: bool) -> Self {
261 Self {
262 enabled,
263 inlays: Vec::new(),
264 refresh_task: Task::ready(None),
265 }
266 }
267}
268
269#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
270pub enum InlayId {
271 EditPrediction(usize),
272 DebuggerValue(usize),
273 // LSP
274 Hint(usize),
275 Color(usize),
276}
277
278impl InlayId {
279 fn id(&self) -> usize {
280 match self {
281 Self::EditPrediction(id) => *id,
282 Self::DebuggerValue(id) => *id,
283 Self::Hint(id) => *id,
284 Self::Color(id) => *id,
285 }
286 }
287}
288
289pub enum ActiveDebugLine {}
290pub enum DebugStackFrameLine {}
291enum DocumentHighlightRead {}
292enum DocumentHighlightWrite {}
293enum InputComposition {}
294pub enum PendingInput {}
295enum SelectedTextHighlight {}
296
297pub enum ConflictsOuter {}
298pub enum ConflictsOurs {}
299pub enum ConflictsTheirs {}
300pub enum ConflictsOursMarker {}
301pub enum ConflictsTheirsMarker {}
302
303#[derive(Debug, Copy, Clone, PartialEq, Eq)]
304pub enum Navigated {
305 Yes,
306 No,
307}
308
309impl Navigated {
310 pub fn from_bool(yes: bool) -> Navigated {
311 if yes { Navigated::Yes } else { Navigated::No }
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316enum DisplayDiffHunk {
317 Folded {
318 display_row: DisplayRow,
319 },
320 Unfolded {
321 is_created_file: bool,
322 diff_base_byte_range: Range<usize>,
323 display_row_range: Range<DisplayRow>,
324 multi_buffer_range: Range<Anchor>,
325 status: DiffHunkStatus,
326 },
327}
328
329pub enum HideMouseCursorOrigin {
330 TypingAction,
331 MovementAction,
332}
333
334pub fn init_settings(cx: &mut App) {
335 EditorSettings::register(cx);
336}
337
338pub fn init(cx: &mut App) {
339 init_settings(cx);
340
341 cx.set_global(GlobalBlameRenderer(Arc::new(())));
342
343 workspace::register_project_item::<Editor>(cx);
344 workspace::FollowableViewRegistry::register::<Editor>(cx);
345 workspace::register_serializable_item::<Editor>(cx);
346
347 cx.observe_new(
348 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
349 workspace.register_action(Editor::new_file);
350 workspace.register_action(Editor::new_file_vertical);
351 workspace.register_action(Editor::new_file_horizontal);
352 workspace.register_action(Editor::cancel_language_server_work);
353 workspace.register_action(Editor::toggle_focus);
354 },
355 )
356 .detach();
357
358 cx.on_action(move |_: &workspace::NewFile, cx| {
359 let app_state = workspace::AppState::global(cx);
360 if let Some(app_state) = app_state.upgrade() {
361 workspace::open_new(
362 Default::default(),
363 app_state,
364 cx,
365 |workspace, window, cx| {
366 Editor::new_file(workspace, &Default::default(), window, cx)
367 },
368 )
369 .detach();
370 }
371 });
372 cx.on_action(move |_: &workspace::NewWindow, cx| {
373 let app_state = workspace::AppState::global(cx);
374 if let Some(app_state) = app_state.upgrade() {
375 workspace::open_new(
376 Default::default(),
377 app_state,
378 cx,
379 |workspace, window, cx| {
380 cx.activate(true);
381 Editor::new_file(workspace, &Default::default(), window, cx)
382 },
383 )
384 .detach();
385 }
386 });
387}
388
389pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
390 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
391}
392
393pub trait DiagnosticRenderer {
394 fn render_group(
395 &self,
396 diagnostic_group: Vec<DiagnosticEntry<Point>>,
397 buffer_id: BufferId,
398 snapshot: EditorSnapshot,
399 editor: WeakEntity<Editor>,
400 cx: &mut App,
401 ) -> Vec<BlockProperties<Anchor>>;
402
403 fn render_hover(
404 &self,
405 diagnostic_group: Vec<DiagnosticEntry<Point>>,
406 range: Range<Point>,
407 buffer_id: BufferId,
408 cx: &mut App,
409 ) -> Option<Entity<markdown::Markdown>>;
410
411 fn open_link(
412 &self,
413 editor: &mut Editor,
414 link: SharedString,
415 window: &mut Window,
416 cx: &mut Context<Editor>,
417 );
418}
419
420pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
421
422impl GlobalDiagnosticRenderer {
423 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
424 cx.try_global::<Self>().map(|g| g.0.clone())
425 }
426}
427
428impl gpui::Global for GlobalDiagnosticRenderer {}
429pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
430 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
431}
432
433pub struct SearchWithinRange;
434
435trait InvalidationRegion {
436 fn ranges(&self) -> &[Range<Anchor>];
437}
438
439#[derive(Clone, Debug, PartialEq)]
440pub enum SelectPhase {
441 Begin {
442 position: DisplayPoint,
443 add: bool,
444 click_count: usize,
445 },
446 BeginColumnar {
447 position: DisplayPoint,
448 reset: bool,
449 mode: ColumnarMode,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug, PartialEq)]
465pub enum ColumnarMode {
466 FromMouse,
467 FromSelection,
468}
469
470#[derive(Clone, Debug)]
471pub enum SelectMode {
472 Character,
473 Word(Range<Anchor>),
474 Line(Range<Anchor>),
475 All,
476}
477
478#[derive(Clone, PartialEq, Eq, Debug)]
479pub enum EditorMode {
480 SingleLine,
481 AutoHeight {
482 min_lines: usize,
483 max_lines: Option<usize>,
484 },
485 Full {
486 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
487 scale_ui_elements_with_buffer_font_size: bool,
488 /// When set to `true`, the editor will render a background for the active line.
489 show_active_line_background: bool,
490 /// When set to `true`, the editor's height will be determined by its content.
491 sized_by_content: bool,
492 },
493 Minimap {
494 parent: WeakEntity<Editor>,
495 },
496}
497
498impl EditorMode {
499 pub fn full() -> Self {
500 Self::Full {
501 scale_ui_elements_with_buffer_font_size: true,
502 show_active_line_background: true,
503 sized_by_content: false,
504 }
505 }
506
507 #[inline]
508 pub fn is_full(&self) -> bool {
509 matches!(self, Self::Full { .. })
510 }
511
512 #[inline]
513 pub fn is_single_line(&self) -> bool {
514 matches!(self, Self::SingleLine { .. })
515 }
516
517 #[inline]
518 fn is_minimap(&self) -> bool {
519 matches!(self, Self::Minimap { .. })
520 }
521}
522
523#[derive(Copy, Clone, Debug)]
524pub enum SoftWrap {
525 /// Prefer not to wrap at all.
526 ///
527 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
528 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
529 GitDiff,
530 /// Prefer a single line generally, unless an overly long line is encountered.
531 None,
532 /// Soft wrap lines that exceed the editor width.
533 EditorWidth,
534 /// Soft wrap lines at the preferred line length.
535 Column(u32),
536 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
537 Bounded(u32),
538}
539
540#[derive(Clone)]
541pub struct EditorStyle {
542 pub background: Hsla,
543 pub border: Hsla,
544 pub local_player: PlayerColor,
545 pub text: TextStyle,
546 pub scrollbar_width: Pixels,
547 pub syntax: Arc<SyntaxTheme>,
548 pub status: StatusColors,
549 pub inlay_hints_style: HighlightStyle,
550 pub edit_prediction_styles: EditPredictionStyles,
551 pub unnecessary_code_fade: f32,
552 pub show_underlines: bool,
553}
554
555impl Default for EditorStyle {
556 fn default() -> Self {
557 Self {
558 background: Hsla::default(),
559 border: Hsla::default(),
560 local_player: PlayerColor::default(),
561 text: TextStyle::default(),
562 scrollbar_width: Pixels::default(),
563 syntax: Default::default(),
564 // HACK: Status colors don't have a real default.
565 // We should look into removing the status colors from the editor
566 // style and retrieve them directly from the theme.
567 status: StatusColors::dark(),
568 inlay_hints_style: HighlightStyle::default(),
569 edit_prediction_styles: EditPredictionStyles {
570 insertion: HighlightStyle::default(),
571 whitespace: HighlightStyle::default(),
572 },
573 unnecessary_code_fade: Default::default(),
574 show_underlines: true,
575 }
576 }
577}
578
579pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
580 let show_background = language_settings::language_settings(None, None, cx)
581 .inlay_hints
582 .show_background;
583
584 HighlightStyle {
585 color: Some(cx.theme().status().hint),
586 background_color: show_background.then(|| cx.theme().status().hint_background),
587 ..HighlightStyle::default()
588 }
589}
590
591pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
592 EditPredictionStyles {
593 insertion: HighlightStyle {
594 color: Some(cx.theme().status().predictive),
595 ..HighlightStyle::default()
596 },
597 whitespace: HighlightStyle {
598 background_color: Some(cx.theme().status().created_background),
599 ..HighlightStyle::default()
600 },
601 }
602}
603
604type CompletionId = usize;
605
606pub(crate) enum EditDisplayMode {
607 TabAccept,
608 DiffPopover,
609 Inline,
610}
611
612enum EditPrediction {
613 Edit {
614 edits: Vec<(Range<Anchor>, String)>,
615 edit_preview: Option<EditPreview>,
616 display_mode: EditDisplayMode,
617 snapshot: BufferSnapshot,
618 },
619 Move {
620 target: Anchor,
621 snapshot: BufferSnapshot,
622 },
623}
624
625struct EditPredictionState {
626 inlay_ids: Vec<InlayId>,
627 completion: EditPrediction,
628 completion_id: Option<SharedString>,
629 invalidation_range: Range<Anchor>,
630}
631
632enum EditPredictionSettings {
633 Disabled,
634 Enabled {
635 show_in_menu: bool,
636 preview_requires_modifier: bool,
637 },
638}
639
640enum EditPredictionHighlight {}
641
642#[derive(Debug, Clone)]
643struct InlineDiagnostic {
644 message: SharedString,
645 group_id: usize,
646 is_primary: bool,
647 start: Point,
648 severity: lsp::DiagnosticSeverity,
649}
650
651pub enum MenuEditPredictionsPolicy {
652 Never,
653 ByProvider,
654}
655
656pub enum EditPredictionPreview {
657 /// Modifier is not pressed
658 Inactive { released_too_fast: bool },
659 /// Modifier pressed
660 Active {
661 since: Instant,
662 previous_scroll_position: Option<ScrollAnchor>,
663 },
664}
665
666impl EditPredictionPreview {
667 pub fn released_too_fast(&self) -> bool {
668 match self {
669 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
670 EditPredictionPreview::Active { .. } => false,
671 }
672 }
673
674 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
675 if let EditPredictionPreview::Active {
676 previous_scroll_position,
677 ..
678 } = self
679 {
680 *previous_scroll_position = scroll_position;
681 }
682 }
683}
684
685pub struct ContextMenuOptions {
686 pub min_entries_visible: usize,
687 pub max_entries_visible: usize,
688 pub placement: Option<ContextMenuPlacement>,
689}
690
691#[derive(Debug, Clone, PartialEq, Eq)]
692pub enum ContextMenuPlacement {
693 Above,
694 Below,
695}
696
697#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
698struct EditorActionId(usize);
699
700impl EditorActionId {
701 pub fn post_inc(&mut self) -> Self {
702 let answer = self.0;
703
704 *self = Self(answer + 1);
705
706 Self(answer)
707 }
708}
709
710// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
711// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
712
713type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
714type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
715
716#[derive(Default)]
717struct ScrollbarMarkerState {
718 scrollbar_size: Size<Pixels>,
719 dirty: bool,
720 markers: Arc<[PaintQuad]>,
721 pending_refresh: Option<Task<Result<()>>>,
722}
723
724impl ScrollbarMarkerState {
725 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
726 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
727 }
728}
729
730#[derive(Clone, Copy, PartialEq, Eq)]
731pub enum MinimapVisibility {
732 Disabled,
733 Enabled {
734 /// The configuration currently present in the users settings.
735 setting_configuration: bool,
736 /// Whether to override the currently set visibility from the users setting.
737 toggle_override: bool,
738 },
739}
740
741impl MinimapVisibility {
742 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
743 if mode.is_full() {
744 Self::Enabled {
745 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
746 toggle_override: false,
747 }
748 } else {
749 Self::Disabled
750 }
751 }
752
753 fn hidden(&self) -> Self {
754 match *self {
755 Self::Enabled {
756 setting_configuration,
757 ..
758 } => Self::Enabled {
759 setting_configuration,
760 toggle_override: setting_configuration,
761 },
762 Self::Disabled => Self::Disabled,
763 }
764 }
765
766 fn disabled(&self) -> bool {
767 match *self {
768 Self::Disabled => true,
769 _ => false,
770 }
771 }
772
773 fn settings_visibility(&self) -> bool {
774 match *self {
775 Self::Enabled {
776 setting_configuration,
777 ..
778 } => setting_configuration,
779 _ => false,
780 }
781 }
782
783 fn visible(&self) -> bool {
784 match *self {
785 Self::Enabled {
786 setting_configuration,
787 toggle_override,
788 } => setting_configuration ^ toggle_override,
789 _ => false,
790 }
791 }
792
793 fn toggle_visibility(&self) -> Self {
794 match *self {
795 Self::Enabled {
796 toggle_override,
797 setting_configuration,
798 } => Self::Enabled {
799 setting_configuration,
800 toggle_override: !toggle_override,
801 },
802 Self::Disabled => Self::Disabled,
803 }
804 }
805}
806
807#[derive(Clone, Debug)]
808struct RunnableTasks {
809 templates: Vec<(TaskSourceKind, TaskTemplate)>,
810 offset: multi_buffer::Anchor,
811 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
812 column: u32,
813 // Values of all named captures, including those starting with '_'
814 extra_variables: HashMap<String, String>,
815 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
816 context_range: Range<BufferOffset>,
817}
818
819impl RunnableTasks {
820 fn resolve<'a>(
821 &'a self,
822 cx: &'a task::TaskContext,
823 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
824 self.templates.iter().filter_map(|(kind, template)| {
825 template
826 .resolve_task(&kind.to_id_base(), cx)
827 .map(|task| (kind.clone(), task))
828 })
829 }
830}
831
832#[derive(Clone)]
833pub struct ResolvedTasks {
834 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
835 position: Anchor,
836}
837
838#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
839struct BufferOffset(usize);
840
841// Addons allow storing per-editor state in other crates (e.g. Vim)
842pub trait Addon: 'static {
843 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
844
845 fn render_buffer_header_controls(
846 &self,
847 _: &ExcerptInfo,
848 _: &Window,
849 _: &App,
850 ) -> Option<AnyElement> {
851 None
852 }
853
854 fn to_any(&self) -> &dyn std::any::Any;
855
856 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
857 None
858 }
859}
860
861struct ChangeLocation {
862 current: Option<Vec<Anchor>>,
863 original: Vec<Anchor>,
864}
865impl ChangeLocation {
866 fn locations(&self) -> &[Anchor] {
867 self.current.as_ref().unwrap_or(&self.original)
868 }
869}
870
871/// A set of caret positions, registered when the editor was edited.
872pub struct ChangeList {
873 changes: Vec<ChangeLocation>,
874 /// Currently "selected" change.
875 position: Option<usize>,
876}
877
878impl ChangeList {
879 pub fn new() -> Self {
880 Self {
881 changes: Vec::new(),
882 position: None,
883 }
884 }
885
886 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
887 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
888 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
889 if self.changes.is_empty() {
890 return None;
891 }
892
893 let prev = self.position.unwrap_or(self.changes.len());
894 let next = if direction == Direction::Prev {
895 prev.saturating_sub(count)
896 } else {
897 (prev + count).min(self.changes.len() - 1)
898 };
899 self.position = Some(next);
900 self.changes.get(next).map(|change| change.locations())
901 }
902
903 /// Adds a new change to the list, resetting the change list position.
904 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
905 self.position.take();
906 if let Some(last) = self.changes.last_mut()
907 && group
908 {
909 last.current = Some(new_positions)
910 } else {
911 self.changes.push(ChangeLocation {
912 original: new_positions,
913 current: None,
914 });
915 }
916 }
917
918 pub fn last(&self) -> Option<&[Anchor]> {
919 self.changes.last().map(|change| change.locations())
920 }
921
922 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
923 self.changes.last().map(|change| change.original.as_slice())
924 }
925
926 pub fn invert_last_group(&mut self) {
927 if let Some(last) = self.changes.last_mut() {
928 if let Some(current) = last.current.as_mut() {
929 mem::swap(&mut last.original, current);
930 }
931 }
932 }
933}
934
935#[derive(Clone)]
936struct InlineBlamePopoverState {
937 scroll_handle: ScrollHandle,
938 commit_message: Option<ParsedCommitMessage>,
939 markdown: Entity<Markdown>,
940}
941
942struct InlineBlamePopover {
943 position: gpui::Point<Pixels>,
944 hide_task: Option<Task<()>>,
945 popover_bounds: Option<Bounds<Pixels>>,
946 popover_state: InlineBlamePopoverState,
947 keyboard_grace: bool,
948}
949
950enum SelectionDragState {
951 /// State when no drag related activity is detected.
952 None,
953 /// State when the mouse is down on a selection that is about to be dragged.
954 ReadyToDrag {
955 selection: Selection<Anchor>,
956 click_position: gpui::Point<Pixels>,
957 mouse_down_time: Instant,
958 },
959 /// State when the mouse is dragging the selection in the editor.
960 Dragging {
961 selection: Selection<Anchor>,
962 drop_cursor: Selection<Anchor>,
963 hide_drop_cursor: bool,
964 },
965}
966
967enum ColumnarSelectionState {
968 FromMouse {
969 selection_tail: Anchor,
970 display_point: Option<DisplayPoint>,
971 },
972 FromSelection {
973 selection_tail: Anchor,
974 },
975}
976
977/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
978/// a breakpoint on them.
979#[derive(Clone, Copy, Debug, PartialEq, Eq)]
980struct PhantomBreakpointIndicator {
981 display_row: DisplayRow,
982 /// There's a small debounce between hovering over the line and showing the indicator.
983 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
984 is_active: bool,
985 collides_with_existing_breakpoint: bool,
986}
987
988/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
989///
990/// See the [module level documentation](self) for more information.
991pub struct Editor {
992 focus_handle: FocusHandle,
993 last_focused_descendant: Option<WeakFocusHandle>,
994 /// The text buffer being edited
995 buffer: Entity<MultiBuffer>,
996 /// Map of how text in the buffer should be displayed.
997 /// Handles soft wraps, folds, fake inlay text insertions, etc.
998 pub display_map: Entity<DisplayMap>,
999 pub selections: SelectionsCollection,
1000 pub scroll_manager: ScrollManager,
1001 /// When inline assist editors are linked, they all render cursors because
1002 /// typing enters text into each of them, even the ones that aren't focused.
1003 pub(crate) show_cursor_when_unfocused: bool,
1004 columnar_selection_state: Option<ColumnarSelectionState>,
1005 add_selections_state: Option<AddSelectionsState>,
1006 select_next_state: Option<SelectNextState>,
1007 select_prev_state: Option<SelectNextState>,
1008 selection_history: SelectionHistory,
1009 defer_selection_effects: bool,
1010 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1011 autoclose_regions: Vec<AutocloseRegion>,
1012 snippet_stack: InvalidationStack<SnippetState>,
1013 select_syntax_node_history: SelectSyntaxNodeHistory,
1014 ime_transaction: Option<TransactionId>,
1015 pub diagnostics_max_severity: DiagnosticSeverity,
1016 active_diagnostics: ActiveDiagnostic,
1017 show_inline_diagnostics: bool,
1018 inline_diagnostics_update: Task<()>,
1019 inline_diagnostics_enabled: bool,
1020 diagnostics_enabled: bool,
1021 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1022 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1023 hard_wrap: Option<usize>,
1024
1025 // TODO: make this a access method
1026 pub project: Option<Entity<Project>>,
1027 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1028 completion_provider: Option<Rc<dyn CompletionProvider>>,
1029 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1030 blink_manager: Entity<BlinkManager>,
1031 show_cursor_names: bool,
1032 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1033 pub show_local_selections: bool,
1034 mode: EditorMode,
1035 show_breadcrumbs: bool,
1036 show_gutter: bool,
1037 show_scrollbars: ScrollbarAxes,
1038 minimap_visibility: MinimapVisibility,
1039 offset_content: bool,
1040 disable_expand_excerpt_buttons: bool,
1041 show_line_numbers: Option<bool>,
1042 use_relative_line_numbers: Option<bool>,
1043 show_git_diff_gutter: Option<bool>,
1044 show_code_actions: Option<bool>,
1045 show_runnables: Option<bool>,
1046 show_breakpoints: Option<bool>,
1047 show_wrap_guides: Option<bool>,
1048 show_indent_guides: Option<bool>,
1049 placeholder_text: Option<Arc<str>>,
1050 highlight_order: usize,
1051 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1052 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1053 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1054 scrollbar_marker_state: ScrollbarMarkerState,
1055 active_indent_guides_state: ActiveIndentGuidesState,
1056 nav_history: Option<ItemNavHistory>,
1057 context_menu: RefCell<Option<CodeContextMenu>>,
1058 context_menu_options: Option<ContextMenuOptions>,
1059 mouse_context_menu: Option<MouseContextMenu>,
1060 completion_tasks: Vec<(CompletionId, Task<()>)>,
1061 inline_blame_popover: Option<InlineBlamePopover>,
1062 inline_blame_popover_show_task: Option<Task<()>>,
1063 signature_help_state: SignatureHelpState,
1064 auto_signature_help: Option<bool>,
1065 find_all_references_task_sources: Vec<Anchor>,
1066 next_completion_id: CompletionId,
1067 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1068 code_actions_task: Option<Task<Result<()>>>,
1069 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1070 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1071 document_highlights_task: Option<Task<()>>,
1072 linked_editing_range_task: Option<Task<Option<()>>>,
1073 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1074 pending_rename: Option<RenameState>,
1075 searchable: bool,
1076 cursor_shape: CursorShape,
1077 current_line_highlight: Option<CurrentLineHighlight>,
1078 collapse_matches: bool,
1079 autoindent_mode: Option<AutoindentMode>,
1080 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1081 input_enabled: bool,
1082 use_modal_editing: bool,
1083 read_only: bool,
1084 leader_id: Option<CollaboratorId>,
1085 remote_id: Option<ViewId>,
1086 pub hover_state: HoverState,
1087 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1088 gutter_hovered: bool,
1089 hovered_link_state: Option<HoveredLinkState>,
1090 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1091 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1092 active_edit_prediction: Option<EditPredictionState>,
1093 /// Used to prevent flickering as the user types while the menu is open
1094 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1095 edit_prediction_settings: EditPredictionSettings,
1096 edit_predictions_hidden_for_vim_mode: bool,
1097 show_edit_predictions_override: Option<bool>,
1098 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1099 edit_prediction_preview: EditPredictionPreview,
1100 edit_prediction_indent_conflict: bool,
1101 edit_prediction_requires_modifier_in_indent_conflict: bool,
1102 inlay_hint_cache: InlayHintCache,
1103 next_inlay_id: usize,
1104 _subscriptions: Vec<Subscription>,
1105 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1106 gutter_dimensions: GutterDimensions,
1107 style: Option<EditorStyle>,
1108 text_style_refinement: Option<TextStyleRefinement>,
1109 next_editor_action_id: EditorActionId,
1110 editor_actions: Rc<
1111 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1112 >,
1113 use_autoclose: bool,
1114 use_auto_surround: bool,
1115 auto_replace_emoji_shortcode: bool,
1116 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1117 show_git_blame_gutter: bool,
1118 show_git_blame_inline: bool,
1119 show_git_blame_inline_delay_task: Option<Task<()>>,
1120 git_blame_inline_enabled: bool,
1121 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1122 serialize_dirty_buffers: bool,
1123 show_selection_menu: Option<bool>,
1124 blame: Option<Entity<GitBlame>>,
1125 blame_subscription: Option<Subscription>,
1126 custom_context_menu: Option<
1127 Box<
1128 dyn 'static
1129 + Fn(
1130 &mut Self,
1131 DisplayPoint,
1132 &mut Window,
1133 &mut Context<Self>,
1134 ) -> Option<Entity<ui::ContextMenu>>,
1135 >,
1136 >,
1137 last_bounds: Option<Bounds<Pixels>>,
1138 last_position_map: Option<Rc<PositionMap>>,
1139 expect_bounds_change: Option<Bounds<Pixels>>,
1140 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1141 tasks_update_task: Option<Task<()>>,
1142 breakpoint_store: Option<Entity<BreakpointStore>>,
1143 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1144 hovered_diff_hunk_row: Option<DisplayRow>,
1145 pull_diagnostics_task: Task<()>,
1146 in_project_search: bool,
1147 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1148 breadcrumb_header: Option<String>,
1149 focused_block: Option<FocusedBlock>,
1150 next_scroll_position: NextScrollCursorCenterTopBottom,
1151 addons: HashMap<TypeId, Box<dyn Addon>>,
1152 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1153 load_diff_task: Option<Shared<Task<()>>>,
1154 /// Whether we are temporarily displaying a diff other than git's
1155 temporary_diff_override: bool,
1156 selection_mark_mode: bool,
1157 toggle_fold_multiple_buffers: Task<()>,
1158 _scroll_cursor_center_top_bottom_task: Task<()>,
1159 serialize_selections: Task<()>,
1160 serialize_folds: Task<()>,
1161 mouse_cursor_hidden: bool,
1162 minimap: Option<Entity<Self>>,
1163 hide_mouse_mode: HideMouseMode,
1164 pub change_list: ChangeList,
1165 inline_value_cache: InlineValueCache,
1166 selection_drag_state: SelectionDragState,
1167 next_color_inlay_id: usize,
1168 colors: Option<LspColorData>,
1169 folding_newlines: Task<()>,
1170}
1171
1172#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1173enum NextScrollCursorCenterTopBottom {
1174 #[default]
1175 Center,
1176 Top,
1177 Bottom,
1178}
1179
1180impl NextScrollCursorCenterTopBottom {
1181 fn next(&self) -> Self {
1182 match self {
1183 Self::Center => Self::Top,
1184 Self::Top => Self::Bottom,
1185 Self::Bottom => Self::Center,
1186 }
1187 }
1188}
1189
1190#[derive(Clone)]
1191pub struct EditorSnapshot {
1192 pub mode: EditorMode,
1193 show_gutter: bool,
1194 show_line_numbers: Option<bool>,
1195 show_git_diff_gutter: Option<bool>,
1196 show_code_actions: Option<bool>,
1197 show_runnables: Option<bool>,
1198 show_breakpoints: Option<bool>,
1199 git_blame_gutter_max_author_length: Option<usize>,
1200 pub display_snapshot: DisplaySnapshot,
1201 pub placeholder_text: Option<Arc<str>>,
1202 is_focused: bool,
1203 scroll_anchor: ScrollAnchor,
1204 ongoing_scroll: OngoingScroll,
1205 current_line_highlight: CurrentLineHighlight,
1206 gutter_hovered: bool,
1207}
1208
1209#[derive(Default, Debug, Clone, Copy)]
1210pub struct GutterDimensions {
1211 pub left_padding: Pixels,
1212 pub right_padding: Pixels,
1213 pub width: Pixels,
1214 pub margin: Pixels,
1215 pub git_blame_entries_width: Option<Pixels>,
1216}
1217
1218impl GutterDimensions {
1219 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1220 Self {
1221 margin: Self::default_gutter_margin(font_id, font_size, cx),
1222 ..Default::default()
1223 }
1224 }
1225
1226 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1227 -cx.text_system().descent(font_id, font_size)
1228 }
1229 /// The full width of the space taken up by the gutter.
1230 pub fn full_width(&self) -> Pixels {
1231 self.margin + self.width
1232 }
1233
1234 /// The width of the space reserved for the fold indicators,
1235 /// use alongside 'justify_end' and `gutter_width` to
1236 /// right align content with the line numbers
1237 pub fn fold_area_width(&self) -> Pixels {
1238 self.margin + self.right_padding
1239 }
1240}
1241
1242struct CharacterDimensions {
1243 em_width: Pixels,
1244 em_advance: Pixels,
1245 line_height: Pixels,
1246}
1247
1248#[derive(Debug)]
1249pub struct RemoteSelection {
1250 pub replica_id: ReplicaId,
1251 pub selection: Selection<Anchor>,
1252 pub cursor_shape: CursorShape,
1253 pub collaborator_id: CollaboratorId,
1254 pub line_mode: bool,
1255 pub user_name: Option<SharedString>,
1256 pub color: PlayerColor,
1257}
1258
1259#[derive(Clone, Debug)]
1260struct SelectionHistoryEntry {
1261 selections: Arc<[Selection<Anchor>]>,
1262 select_next_state: Option<SelectNextState>,
1263 select_prev_state: Option<SelectNextState>,
1264 add_selections_state: Option<AddSelectionsState>,
1265}
1266
1267#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1268enum SelectionHistoryMode {
1269 Normal,
1270 Undoing,
1271 Redoing,
1272 Skipping,
1273}
1274
1275#[derive(Clone, PartialEq, Eq, Hash)]
1276struct HoveredCursor {
1277 replica_id: u16,
1278 selection_id: usize,
1279}
1280
1281impl Default for SelectionHistoryMode {
1282 fn default() -> Self {
1283 Self::Normal
1284 }
1285}
1286
1287#[derive(Debug)]
1288/// SelectionEffects controls the side-effects of updating the selection.
1289///
1290/// The default behaviour does "what you mostly want":
1291/// - it pushes to the nav history if the cursor moved by >10 lines
1292/// - it re-triggers completion requests
1293/// - it scrolls to fit
1294///
1295/// You might want to modify these behaviours. For example when doing a "jump"
1296/// like go to definition, we always want to add to nav history; but when scrolling
1297/// in vim mode we never do.
1298///
1299/// Similarly, you might want to disable scrolling if you don't want the viewport to
1300/// move.
1301#[derive(Clone)]
1302pub struct SelectionEffects {
1303 nav_history: Option<bool>,
1304 completions: bool,
1305 scroll: Option<Autoscroll>,
1306}
1307
1308impl Default for SelectionEffects {
1309 fn default() -> Self {
1310 Self {
1311 nav_history: None,
1312 completions: true,
1313 scroll: Some(Autoscroll::fit()),
1314 }
1315 }
1316}
1317impl SelectionEffects {
1318 pub fn scroll(scroll: Autoscroll) -> Self {
1319 Self {
1320 scroll: Some(scroll),
1321 ..Default::default()
1322 }
1323 }
1324
1325 pub fn no_scroll() -> Self {
1326 Self {
1327 scroll: None,
1328 ..Default::default()
1329 }
1330 }
1331
1332 pub fn completions(self, completions: bool) -> Self {
1333 Self {
1334 completions,
1335 ..self
1336 }
1337 }
1338
1339 pub fn nav_history(self, nav_history: bool) -> Self {
1340 Self {
1341 nav_history: Some(nav_history),
1342 ..self
1343 }
1344 }
1345}
1346
1347struct DeferredSelectionEffectsState {
1348 changed: bool,
1349 effects: SelectionEffects,
1350 old_cursor_position: Anchor,
1351 history_entry: SelectionHistoryEntry,
1352}
1353
1354#[derive(Default)]
1355struct SelectionHistory {
1356 #[allow(clippy::type_complexity)]
1357 selections_by_transaction:
1358 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1359 mode: SelectionHistoryMode,
1360 undo_stack: VecDeque<SelectionHistoryEntry>,
1361 redo_stack: VecDeque<SelectionHistoryEntry>,
1362}
1363
1364impl SelectionHistory {
1365 #[track_caller]
1366 fn insert_transaction(
1367 &mut self,
1368 transaction_id: TransactionId,
1369 selections: Arc<[Selection<Anchor>]>,
1370 ) {
1371 if selections.is_empty() {
1372 log::error!(
1373 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1374 std::panic::Location::caller()
1375 );
1376 return;
1377 }
1378 self.selections_by_transaction
1379 .insert(transaction_id, (selections, None));
1380 }
1381
1382 #[allow(clippy::type_complexity)]
1383 fn transaction(
1384 &self,
1385 transaction_id: TransactionId,
1386 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1387 self.selections_by_transaction.get(&transaction_id)
1388 }
1389
1390 #[allow(clippy::type_complexity)]
1391 fn transaction_mut(
1392 &mut self,
1393 transaction_id: TransactionId,
1394 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1395 self.selections_by_transaction.get_mut(&transaction_id)
1396 }
1397
1398 fn push(&mut self, entry: SelectionHistoryEntry) {
1399 if !entry.selections.is_empty() {
1400 match self.mode {
1401 SelectionHistoryMode::Normal => {
1402 self.push_undo(entry);
1403 self.redo_stack.clear();
1404 }
1405 SelectionHistoryMode::Undoing => self.push_redo(entry),
1406 SelectionHistoryMode::Redoing => self.push_undo(entry),
1407 SelectionHistoryMode::Skipping => {}
1408 }
1409 }
1410 }
1411
1412 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1413 if self
1414 .undo_stack
1415 .back()
1416 .map_or(true, |e| e.selections != entry.selections)
1417 {
1418 self.undo_stack.push_back(entry);
1419 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1420 self.undo_stack.pop_front();
1421 }
1422 }
1423 }
1424
1425 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1426 if self
1427 .redo_stack
1428 .back()
1429 .map_or(true, |e| e.selections != entry.selections)
1430 {
1431 self.redo_stack.push_back(entry);
1432 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1433 self.redo_stack.pop_front();
1434 }
1435 }
1436 }
1437}
1438
1439#[derive(Clone, Copy)]
1440pub struct RowHighlightOptions {
1441 pub autoscroll: bool,
1442 pub include_gutter: bool,
1443}
1444
1445impl Default for RowHighlightOptions {
1446 fn default() -> Self {
1447 Self {
1448 autoscroll: Default::default(),
1449 include_gutter: true,
1450 }
1451 }
1452}
1453
1454struct RowHighlight {
1455 index: usize,
1456 range: Range<Anchor>,
1457 color: Hsla,
1458 options: RowHighlightOptions,
1459 type_id: TypeId,
1460}
1461
1462#[derive(Clone, Debug)]
1463struct AddSelectionsState {
1464 groups: Vec<AddSelectionsGroup>,
1465}
1466
1467#[derive(Clone, Debug)]
1468struct AddSelectionsGroup {
1469 above: bool,
1470 stack: Vec<usize>,
1471}
1472
1473#[derive(Clone)]
1474struct SelectNextState {
1475 query: AhoCorasick,
1476 wordwise: bool,
1477 done: bool,
1478}
1479
1480impl std::fmt::Debug for SelectNextState {
1481 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1482 f.debug_struct(std::any::type_name::<Self>())
1483 .field("wordwise", &self.wordwise)
1484 .field("done", &self.done)
1485 .finish()
1486 }
1487}
1488
1489#[derive(Debug)]
1490struct AutocloseRegion {
1491 selection_id: usize,
1492 range: Range<Anchor>,
1493 pair: BracketPair,
1494}
1495
1496#[derive(Debug)]
1497struct SnippetState {
1498 ranges: Vec<Vec<Range<Anchor>>>,
1499 active_index: usize,
1500 choices: Vec<Option<Vec<String>>>,
1501}
1502
1503#[doc(hidden)]
1504pub struct RenameState {
1505 pub range: Range<Anchor>,
1506 pub old_name: Arc<str>,
1507 pub editor: Entity<Editor>,
1508 block_id: CustomBlockId,
1509}
1510
1511struct InvalidationStack<T>(Vec<T>);
1512
1513struct RegisteredEditPredictionProvider {
1514 provider: Arc<dyn EditPredictionProviderHandle>,
1515 _subscription: Subscription,
1516}
1517
1518#[derive(Debug, PartialEq, Eq)]
1519pub struct ActiveDiagnosticGroup {
1520 pub active_range: Range<Anchor>,
1521 pub active_message: String,
1522 pub group_id: usize,
1523 pub blocks: HashSet<CustomBlockId>,
1524}
1525
1526#[derive(Debug, PartialEq, Eq)]
1527
1528pub(crate) enum ActiveDiagnostic {
1529 None,
1530 All,
1531 Group(ActiveDiagnosticGroup),
1532}
1533
1534#[derive(Serialize, Deserialize, Clone, Debug)]
1535pub struct ClipboardSelection {
1536 /// The number of bytes in this selection.
1537 pub len: usize,
1538 /// Whether this was a full-line selection.
1539 pub is_entire_line: bool,
1540 /// The indentation of the first line when this content was originally copied.
1541 pub first_line_indent: u32,
1542}
1543
1544// selections, scroll behavior, was newest selection reversed
1545type SelectSyntaxNodeHistoryState = (
1546 Box<[Selection<usize>]>,
1547 SelectSyntaxNodeScrollBehavior,
1548 bool,
1549);
1550
1551#[derive(Default)]
1552struct SelectSyntaxNodeHistory {
1553 stack: Vec<SelectSyntaxNodeHistoryState>,
1554 // disable temporarily to allow changing selections without losing the stack
1555 pub disable_clearing: bool,
1556}
1557
1558impl SelectSyntaxNodeHistory {
1559 pub fn try_clear(&mut self) {
1560 if !self.disable_clearing {
1561 self.stack.clear();
1562 }
1563 }
1564
1565 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1566 self.stack.push(selection);
1567 }
1568
1569 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1570 self.stack.pop()
1571 }
1572}
1573
1574enum SelectSyntaxNodeScrollBehavior {
1575 CursorTop,
1576 FitSelection,
1577 CursorBottom,
1578}
1579
1580#[derive(Debug)]
1581pub(crate) struct NavigationData {
1582 cursor_anchor: Anchor,
1583 cursor_position: Point,
1584 scroll_anchor: ScrollAnchor,
1585 scroll_top_row: u32,
1586}
1587
1588#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1589pub enum GotoDefinitionKind {
1590 Symbol,
1591 Declaration,
1592 Type,
1593 Implementation,
1594}
1595
1596#[derive(Debug, Clone)]
1597enum InlayHintRefreshReason {
1598 ModifiersChanged(bool),
1599 Toggle(bool),
1600 SettingsChange(InlayHintSettings),
1601 NewLinesShown,
1602 BufferEdited(HashSet<Arc<Language>>),
1603 RefreshRequested,
1604 ExcerptsRemoved(Vec<ExcerptId>),
1605}
1606
1607impl InlayHintRefreshReason {
1608 fn description(&self) -> &'static str {
1609 match self {
1610 Self::ModifiersChanged(_) => "modifiers changed",
1611 Self::Toggle(_) => "toggle",
1612 Self::SettingsChange(_) => "settings change",
1613 Self::NewLinesShown => "new lines shown",
1614 Self::BufferEdited(_) => "buffer edited",
1615 Self::RefreshRequested => "refresh requested",
1616 Self::ExcerptsRemoved(_) => "excerpts removed",
1617 }
1618 }
1619}
1620
1621pub enum FormatTarget {
1622 Buffers(HashSet<Entity<Buffer>>),
1623 Ranges(Vec<Range<MultiBufferPoint>>),
1624}
1625
1626pub(crate) struct FocusedBlock {
1627 id: BlockId,
1628 focus_handle: WeakFocusHandle,
1629}
1630
1631#[derive(Clone)]
1632enum JumpData {
1633 MultiBufferRow {
1634 row: MultiBufferRow,
1635 line_offset_from_top: u32,
1636 },
1637 MultiBufferPoint {
1638 excerpt_id: ExcerptId,
1639 position: Point,
1640 anchor: text::Anchor,
1641 line_offset_from_top: u32,
1642 },
1643}
1644
1645pub enum MultibufferSelectionMode {
1646 First,
1647 All,
1648}
1649
1650#[derive(Clone, Copy, Debug, Default)]
1651pub struct RewrapOptions {
1652 pub override_language_settings: bool,
1653 pub preserve_existing_whitespace: bool,
1654}
1655
1656impl Editor {
1657 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1658 let buffer = cx.new(|cx| Buffer::local("", cx));
1659 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1660 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1661 }
1662
1663 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1664 let buffer = cx.new(|cx| Buffer::local("", cx));
1665 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1666 Self::new(EditorMode::full(), buffer, None, window, cx)
1667 }
1668
1669 pub fn auto_height(
1670 min_lines: usize,
1671 max_lines: usize,
1672 window: &mut Window,
1673 cx: &mut Context<Self>,
1674 ) -> Self {
1675 let buffer = cx.new(|cx| Buffer::local("", cx));
1676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1677 Self::new(
1678 EditorMode::AutoHeight {
1679 min_lines,
1680 max_lines: Some(max_lines),
1681 },
1682 buffer,
1683 None,
1684 window,
1685 cx,
1686 )
1687 }
1688
1689 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1690 /// The editor grows as tall as needed to fit its content.
1691 pub fn auto_height_unbounded(
1692 min_lines: usize,
1693 window: &mut Window,
1694 cx: &mut Context<Self>,
1695 ) -> Self {
1696 let buffer = cx.new(|cx| Buffer::local("", cx));
1697 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1698 Self::new(
1699 EditorMode::AutoHeight {
1700 min_lines,
1701 max_lines: None,
1702 },
1703 buffer,
1704 None,
1705 window,
1706 cx,
1707 )
1708 }
1709
1710 pub fn for_buffer(
1711 buffer: Entity<Buffer>,
1712 project: Option<Entity<Project>>,
1713 window: &mut Window,
1714 cx: &mut Context<Self>,
1715 ) -> Self {
1716 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1717 Self::new(EditorMode::full(), buffer, project, window, cx)
1718 }
1719
1720 pub fn for_multibuffer(
1721 buffer: Entity<MultiBuffer>,
1722 project: Option<Entity<Project>>,
1723 window: &mut Window,
1724 cx: &mut Context<Self>,
1725 ) -> Self {
1726 Self::new(EditorMode::full(), buffer, project, window, cx)
1727 }
1728
1729 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1730 let mut clone = Self::new(
1731 self.mode.clone(),
1732 self.buffer.clone(),
1733 self.project.clone(),
1734 window,
1735 cx,
1736 );
1737 self.display_map.update(cx, |display_map, cx| {
1738 let snapshot = display_map.snapshot(cx);
1739 clone.display_map.update(cx, |display_map, cx| {
1740 display_map.set_state(&snapshot, cx);
1741 });
1742 });
1743 clone.folds_did_change(cx);
1744 clone.selections.clone_state(&self.selections);
1745 clone.scroll_manager.clone_state(&self.scroll_manager);
1746 clone.searchable = self.searchable;
1747 clone.read_only = self.read_only;
1748 clone
1749 }
1750
1751 pub fn new(
1752 mode: EditorMode,
1753 buffer: Entity<MultiBuffer>,
1754 project: Option<Entity<Project>>,
1755 window: &mut Window,
1756 cx: &mut Context<Self>,
1757 ) -> Self {
1758 Editor::new_internal(mode, buffer, project, None, window, cx)
1759 }
1760
1761 fn new_internal(
1762 mode: EditorMode,
1763 buffer: Entity<MultiBuffer>,
1764 project: Option<Entity<Project>>,
1765 display_map: Option<Entity<DisplayMap>>,
1766 window: &mut Window,
1767 cx: &mut Context<Self>,
1768 ) -> Self {
1769 debug_assert!(
1770 display_map.is_none() || mode.is_minimap(),
1771 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1772 );
1773
1774 let full_mode = mode.is_full();
1775 let is_minimap = mode.is_minimap();
1776 let diagnostics_max_severity = if full_mode {
1777 EditorSettings::get_global(cx)
1778 .diagnostics_max_severity
1779 .unwrap_or(DiagnosticSeverity::Hint)
1780 } else {
1781 DiagnosticSeverity::Off
1782 };
1783 let style = window.text_style();
1784 let font_size = style.font_size.to_pixels(window.rem_size());
1785 let editor = cx.entity().downgrade();
1786 let fold_placeholder = FoldPlaceholder {
1787 constrain_width: true,
1788 render: Arc::new(move |fold_id, fold_range, cx| {
1789 let editor = editor.clone();
1790 div()
1791 .id(fold_id)
1792 .bg(cx.theme().colors().ghost_element_background)
1793 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1794 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1795 .rounded_xs()
1796 .size_full()
1797 .cursor_pointer()
1798 .child("⋯")
1799 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1800 .on_click(move |_, _window, cx| {
1801 editor
1802 .update(cx, |editor, cx| {
1803 editor.unfold_ranges(
1804 &[fold_range.start..fold_range.end],
1805 true,
1806 false,
1807 cx,
1808 );
1809 cx.stop_propagation();
1810 })
1811 .ok();
1812 })
1813 .into_any()
1814 }),
1815 merge_adjacent: true,
1816 ..FoldPlaceholder::default()
1817 };
1818 let display_map = display_map.unwrap_or_else(|| {
1819 cx.new(|cx| {
1820 DisplayMap::new(
1821 buffer.clone(),
1822 style.font(),
1823 font_size,
1824 None,
1825 FILE_HEADER_HEIGHT,
1826 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1827 fold_placeholder,
1828 diagnostics_max_severity,
1829 cx,
1830 )
1831 })
1832 });
1833
1834 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1835
1836 let blink_manager = cx.new(|cx| {
1837 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1838 if is_minimap {
1839 blink_manager.disable(cx);
1840 }
1841 blink_manager
1842 });
1843
1844 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1845 .then(|| language_settings::SoftWrap::None);
1846
1847 let mut project_subscriptions = Vec::new();
1848 if full_mode {
1849 if let Some(project) = project.as_ref() {
1850 project_subscriptions.push(cx.subscribe_in(
1851 project,
1852 window,
1853 |editor, _, event, window, cx| match event {
1854 project::Event::RefreshCodeLens => {
1855 // we always query lens with actions, without storing them, always refreshing them
1856 }
1857 project::Event::RefreshInlayHints => {
1858 editor
1859 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1860 }
1861 project::Event::LanguageServerAdded(..)
1862 | project::Event::LanguageServerRemoved(..) => {
1863 if editor.tasks_update_task.is_none() {
1864 editor.tasks_update_task =
1865 Some(editor.refresh_runnables(window, cx));
1866 }
1867 }
1868 project::Event::SnippetEdit(id, snippet_edits) => {
1869 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1870 let focus_handle = editor.focus_handle(cx);
1871 if focus_handle.is_focused(window) {
1872 let snapshot = buffer.read(cx).snapshot();
1873 for (range, snippet) in snippet_edits {
1874 let editor_range =
1875 language::range_from_lsp(*range).to_offset(&snapshot);
1876 editor
1877 .insert_snippet(
1878 &[editor_range],
1879 snippet.clone(),
1880 window,
1881 cx,
1882 )
1883 .ok();
1884 }
1885 }
1886 }
1887 }
1888 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1889 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1890 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1891 }
1892 }
1893 _ => {}
1894 },
1895 ));
1896 if let Some(task_inventory) = project
1897 .read(cx)
1898 .task_store()
1899 .read(cx)
1900 .task_inventory()
1901 .cloned()
1902 {
1903 project_subscriptions.push(cx.observe_in(
1904 &task_inventory,
1905 window,
1906 |editor, _, window, cx| {
1907 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1908 },
1909 ));
1910 };
1911
1912 project_subscriptions.push(cx.subscribe_in(
1913 &project.read(cx).breakpoint_store(),
1914 window,
1915 |editor, _, event, window, cx| match event {
1916 BreakpointStoreEvent::ClearDebugLines => {
1917 editor.clear_row_highlights::<ActiveDebugLine>();
1918 editor.refresh_inline_values(cx);
1919 }
1920 BreakpointStoreEvent::SetDebugLine => {
1921 if editor.go_to_active_debug_line(window, cx) {
1922 cx.stop_propagation();
1923 }
1924
1925 editor.refresh_inline_values(cx);
1926 }
1927 _ => {}
1928 },
1929 ));
1930 let git_store = project.read(cx).git_store().clone();
1931 let project = project.clone();
1932 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1933 match event {
1934 GitStoreEvent::RepositoryUpdated(
1935 _,
1936 RepositoryEvent::Updated {
1937 new_instance: true, ..
1938 },
1939 _,
1940 ) => {
1941 this.load_diff_task = Some(
1942 update_uncommitted_diff_for_buffer(
1943 cx.entity(),
1944 &project,
1945 this.buffer.read(cx).all_buffers(),
1946 this.buffer.clone(),
1947 cx,
1948 )
1949 .shared(),
1950 );
1951 }
1952 _ => {}
1953 }
1954 }));
1955 }
1956 }
1957
1958 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1959
1960 let inlay_hint_settings =
1961 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1962 let focus_handle = cx.focus_handle();
1963 if !is_minimap {
1964 cx.on_focus(&focus_handle, window, Self::handle_focus)
1965 .detach();
1966 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1967 .detach();
1968 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1969 .detach();
1970 cx.on_blur(&focus_handle, window, Self::handle_blur)
1971 .detach();
1972 cx.observe_pending_input(window, Self::observe_pending_input)
1973 .detach();
1974 }
1975
1976 let show_indent_guides = if matches!(
1977 mode,
1978 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1979 ) {
1980 Some(false)
1981 } else {
1982 None
1983 };
1984
1985 let breakpoint_store = match (&mode, project.as_ref()) {
1986 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1987 _ => None,
1988 };
1989
1990 let mut code_action_providers = Vec::new();
1991 let mut load_uncommitted_diff = None;
1992 if let Some(project) = project.clone() {
1993 load_uncommitted_diff = Some(
1994 update_uncommitted_diff_for_buffer(
1995 cx.entity(),
1996 &project,
1997 buffer.read(cx).all_buffers(),
1998 buffer.clone(),
1999 cx,
2000 )
2001 .shared(),
2002 );
2003 code_action_providers.push(Rc::new(project) as Rc<_>);
2004 }
2005
2006 let mut editor = Self {
2007 focus_handle,
2008 show_cursor_when_unfocused: false,
2009 last_focused_descendant: None,
2010 buffer: buffer.clone(),
2011 display_map: display_map.clone(),
2012 selections,
2013 scroll_manager: ScrollManager::new(cx),
2014 columnar_selection_state: None,
2015 add_selections_state: None,
2016 select_next_state: None,
2017 select_prev_state: None,
2018 selection_history: SelectionHistory::default(),
2019 defer_selection_effects: false,
2020 deferred_selection_effects_state: None,
2021 autoclose_regions: Vec::new(),
2022 snippet_stack: InvalidationStack::default(),
2023 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2024 ime_transaction: None,
2025 active_diagnostics: ActiveDiagnostic::None,
2026 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2027 inline_diagnostics_update: Task::ready(()),
2028 inline_diagnostics: Vec::new(),
2029 soft_wrap_mode_override,
2030 diagnostics_max_severity,
2031 hard_wrap: None,
2032 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2033 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2034 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2035 project,
2036 blink_manager: blink_manager.clone(),
2037 show_local_selections: true,
2038 show_scrollbars: ScrollbarAxes {
2039 horizontal: full_mode,
2040 vertical: full_mode,
2041 },
2042 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2043 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2044 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2045 show_gutter: full_mode,
2046 show_line_numbers: (!full_mode).then_some(false),
2047 use_relative_line_numbers: None,
2048 disable_expand_excerpt_buttons: !full_mode,
2049 show_git_diff_gutter: None,
2050 show_code_actions: None,
2051 show_runnables: None,
2052 show_breakpoints: None,
2053 show_wrap_guides: None,
2054 show_indent_guides,
2055 placeholder_text: None,
2056 highlight_order: 0,
2057 highlighted_rows: HashMap::default(),
2058 background_highlights: TreeMap::default(),
2059 gutter_highlights: TreeMap::default(),
2060 scrollbar_marker_state: ScrollbarMarkerState::default(),
2061 active_indent_guides_state: ActiveIndentGuidesState::default(),
2062 nav_history: None,
2063 context_menu: RefCell::new(None),
2064 context_menu_options: None,
2065 mouse_context_menu: None,
2066 completion_tasks: Vec::new(),
2067 inline_blame_popover: None,
2068 inline_blame_popover_show_task: None,
2069 signature_help_state: SignatureHelpState::default(),
2070 auto_signature_help: None,
2071 find_all_references_task_sources: Vec::new(),
2072 next_completion_id: 0,
2073 next_inlay_id: 0,
2074 code_action_providers,
2075 available_code_actions: None,
2076 code_actions_task: None,
2077 quick_selection_highlight_task: None,
2078 debounced_selection_highlight_task: None,
2079 document_highlights_task: None,
2080 linked_editing_range_task: None,
2081 pending_rename: None,
2082 searchable: !is_minimap,
2083 cursor_shape: EditorSettings::get_global(cx)
2084 .cursor_shape
2085 .unwrap_or_default(),
2086 current_line_highlight: None,
2087 autoindent_mode: Some(AutoindentMode::EachLine),
2088 collapse_matches: false,
2089 workspace: None,
2090 input_enabled: !is_minimap,
2091 use_modal_editing: full_mode,
2092 read_only: is_minimap,
2093 use_autoclose: true,
2094 use_auto_surround: true,
2095 auto_replace_emoji_shortcode: false,
2096 jsx_tag_auto_close_enabled_in_any_buffer: false,
2097 leader_id: None,
2098 remote_id: None,
2099 hover_state: HoverState::default(),
2100 pending_mouse_down: None,
2101 hovered_link_state: None,
2102 edit_prediction_provider: None,
2103 active_edit_prediction: None,
2104 stale_edit_prediction_in_menu: None,
2105 edit_prediction_preview: EditPredictionPreview::Inactive {
2106 released_too_fast: false,
2107 },
2108 inline_diagnostics_enabled: full_mode,
2109 diagnostics_enabled: full_mode,
2110 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2111 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2112 gutter_hovered: false,
2113 pixel_position_of_newest_cursor: None,
2114 last_bounds: None,
2115 last_position_map: None,
2116 expect_bounds_change: None,
2117 gutter_dimensions: GutterDimensions::default(),
2118 style: None,
2119 show_cursor_names: false,
2120 hovered_cursors: HashMap::default(),
2121 next_editor_action_id: EditorActionId::default(),
2122 editor_actions: Rc::default(),
2123 edit_predictions_hidden_for_vim_mode: false,
2124 show_edit_predictions_override: None,
2125 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2126 edit_prediction_settings: EditPredictionSettings::Disabled,
2127 edit_prediction_indent_conflict: false,
2128 edit_prediction_requires_modifier_in_indent_conflict: true,
2129 custom_context_menu: None,
2130 show_git_blame_gutter: false,
2131 show_git_blame_inline: false,
2132 show_selection_menu: None,
2133 show_git_blame_inline_delay_task: None,
2134 git_blame_inline_enabled: full_mode
2135 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2136 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2137 serialize_dirty_buffers: !is_minimap
2138 && ProjectSettings::get_global(cx)
2139 .session
2140 .restore_unsaved_buffers,
2141 blame: None,
2142 blame_subscription: None,
2143 tasks: BTreeMap::default(),
2144
2145 breakpoint_store,
2146 gutter_breakpoint_indicator: (None, None),
2147 hovered_diff_hunk_row: None,
2148 _subscriptions: (!is_minimap)
2149 .then(|| {
2150 vec![
2151 cx.observe(&buffer, Self::on_buffer_changed),
2152 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2153 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2154 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2155 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2156 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2157 cx.observe_window_activation(window, |editor, window, cx| {
2158 let active = window.is_window_active();
2159 editor.blink_manager.update(cx, |blink_manager, cx| {
2160 if active {
2161 blink_manager.enable(cx);
2162 } else {
2163 blink_manager.disable(cx);
2164 }
2165 });
2166 if active {
2167 editor.show_mouse_cursor(cx);
2168 }
2169 }),
2170 ]
2171 })
2172 .unwrap_or_default(),
2173 tasks_update_task: None,
2174 pull_diagnostics_task: Task::ready(()),
2175 colors: None,
2176 next_color_inlay_id: 0,
2177 linked_edit_ranges: Default::default(),
2178 in_project_search: false,
2179 previous_search_ranges: None,
2180 breadcrumb_header: None,
2181 focused_block: None,
2182 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2183 addons: HashMap::default(),
2184 registered_buffers: HashMap::default(),
2185 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2186 selection_mark_mode: false,
2187 toggle_fold_multiple_buffers: Task::ready(()),
2188 serialize_selections: Task::ready(()),
2189 serialize_folds: Task::ready(()),
2190 text_style_refinement: None,
2191 load_diff_task: load_uncommitted_diff,
2192 temporary_diff_override: false,
2193 mouse_cursor_hidden: false,
2194 minimap: None,
2195 hide_mouse_mode: EditorSettings::get_global(cx)
2196 .hide_mouse
2197 .unwrap_or_default(),
2198 change_list: ChangeList::new(),
2199 mode,
2200 selection_drag_state: SelectionDragState::None,
2201 folding_newlines: Task::ready(()),
2202 };
2203
2204 if is_minimap {
2205 return editor;
2206 }
2207
2208 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2209 editor
2210 ._subscriptions
2211 .push(cx.observe(breakpoints, |_, _, cx| {
2212 cx.notify();
2213 }));
2214 }
2215 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2216 editor._subscriptions.extend(project_subscriptions);
2217
2218 editor._subscriptions.push(cx.subscribe_in(
2219 &cx.entity(),
2220 window,
2221 |editor, _, e: &EditorEvent, window, cx| match e {
2222 EditorEvent::ScrollPositionChanged { local, .. } => {
2223 if *local {
2224 let new_anchor = editor.scroll_manager.anchor();
2225 let snapshot = editor.snapshot(window, cx);
2226 editor.update_restoration_data(cx, move |data| {
2227 data.scroll_position = (
2228 new_anchor.top_row(&snapshot.buffer_snapshot),
2229 new_anchor.offset,
2230 );
2231 });
2232 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2233 editor.inline_blame_popover.take();
2234 }
2235 }
2236 EditorEvent::Edited { .. } => {
2237 if !vim_enabled(cx) {
2238 let (map, selections) = editor.selections.all_adjusted_display(cx);
2239 let pop_state = editor
2240 .change_list
2241 .last()
2242 .map(|previous| {
2243 previous.len() == selections.len()
2244 && previous.iter().enumerate().all(|(ix, p)| {
2245 p.to_display_point(&map).row()
2246 == selections[ix].head().row()
2247 })
2248 })
2249 .unwrap_or(false);
2250 let new_positions = selections
2251 .into_iter()
2252 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2253 .collect();
2254 editor
2255 .change_list
2256 .push_to_change_list(pop_state, new_positions);
2257 }
2258 }
2259 _ => (),
2260 },
2261 ));
2262
2263 if let Some(dap_store) = editor
2264 .project
2265 .as_ref()
2266 .map(|project| project.read(cx).dap_store())
2267 {
2268 let weak_editor = cx.weak_entity();
2269
2270 editor
2271 ._subscriptions
2272 .push(
2273 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2274 let session_entity = cx.entity();
2275 weak_editor
2276 .update(cx, |editor, cx| {
2277 editor._subscriptions.push(
2278 cx.subscribe(&session_entity, Self::on_debug_session_event),
2279 );
2280 })
2281 .ok();
2282 }),
2283 );
2284
2285 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2286 editor
2287 ._subscriptions
2288 .push(cx.subscribe(&session, Self::on_debug_session_event));
2289 }
2290 }
2291
2292 // skip adding the initial selection to selection history
2293 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2294 editor.end_selection(window, cx);
2295 editor.selection_history.mode = SelectionHistoryMode::Normal;
2296
2297 editor.scroll_manager.show_scrollbars(window, cx);
2298 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2299
2300 if full_mode {
2301 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2302 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2303
2304 if editor.git_blame_inline_enabled {
2305 editor.start_git_blame_inline(false, window, cx);
2306 }
2307
2308 editor.go_to_active_debug_line(window, cx);
2309
2310 if let Some(buffer) = buffer.read(cx).as_singleton() {
2311 if let Some(project) = editor.project.as_ref() {
2312 let handle = project.update(cx, |project, cx| {
2313 project.register_buffer_with_language_servers(&buffer, cx)
2314 });
2315 editor
2316 .registered_buffers
2317 .insert(buffer.read(cx).remote_id(), handle);
2318 }
2319 }
2320
2321 editor.minimap =
2322 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2323 editor.colors = Some(LspColorData::new(cx));
2324 editor.update_lsp_data(false, None, window, cx);
2325 }
2326
2327 if editor.mode.is_full() {
2328 editor.report_editor_event("Editor Opened", None, cx);
2329 }
2330
2331 editor
2332 }
2333
2334 pub fn deploy_mouse_context_menu(
2335 &mut self,
2336 position: gpui::Point<Pixels>,
2337 context_menu: Entity<ContextMenu>,
2338 window: &mut Window,
2339 cx: &mut Context<Self>,
2340 ) {
2341 self.mouse_context_menu = Some(MouseContextMenu::new(
2342 self,
2343 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2344 context_menu,
2345 window,
2346 cx,
2347 ));
2348 }
2349
2350 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2351 self.mouse_context_menu
2352 .as_ref()
2353 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2354 }
2355
2356 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2357 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2358 }
2359
2360 fn key_context_internal(
2361 &self,
2362 has_active_edit_prediction: bool,
2363 window: &Window,
2364 cx: &App,
2365 ) -> KeyContext {
2366 let mut key_context = KeyContext::new_with_defaults();
2367 key_context.add("Editor");
2368 let mode = match self.mode {
2369 EditorMode::SingleLine { .. } => "single_line",
2370 EditorMode::AutoHeight { .. } => "auto_height",
2371 EditorMode::Minimap { .. } => "minimap",
2372 EditorMode::Full { .. } => "full",
2373 };
2374
2375 if EditorSettings::jupyter_enabled(cx) {
2376 key_context.add("jupyter");
2377 }
2378
2379 key_context.set("mode", mode);
2380 if self.pending_rename.is_some() {
2381 key_context.add("renaming");
2382 }
2383
2384 match self.context_menu.borrow().as_ref() {
2385 Some(CodeContextMenu::Completions(menu)) => {
2386 if menu.visible() {
2387 key_context.add("menu");
2388 key_context.add("showing_completions");
2389 }
2390 }
2391 Some(CodeContextMenu::CodeActions(menu)) => {
2392 if menu.visible() {
2393 key_context.add("menu");
2394 key_context.add("showing_code_actions")
2395 }
2396 }
2397 None => {}
2398 }
2399
2400 if self.signature_help_state.has_multiple_signatures() {
2401 key_context.add("showing_signature_help");
2402 }
2403
2404 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2405 if !self.focus_handle(cx).contains_focused(window, cx)
2406 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2407 {
2408 for addon in self.addons.values() {
2409 addon.extend_key_context(&mut key_context, cx)
2410 }
2411 }
2412
2413 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2414 if let Some(extension) = singleton_buffer
2415 .read(cx)
2416 .file()
2417 .and_then(|file| file.path().extension()?.to_str())
2418 {
2419 key_context.set("extension", extension.to_string());
2420 }
2421 } else {
2422 key_context.add("multibuffer");
2423 }
2424
2425 if has_active_edit_prediction {
2426 if self.edit_prediction_in_conflict() {
2427 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2428 } else {
2429 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2430 key_context.add("copilot_suggestion");
2431 }
2432 }
2433
2434 if self.selection_mark_mode {
2435 key_context.add("selection_mode");
2436 }
2437
2438 key_context
2439 }
2440
2441 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2442 if self.mouse_cursor_hidden {
2443 self.mouse_cursor_hidden = false;
2444 cx.notify();
2445 }
2446 }
2447
2448 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2449 let hide_mouse_cursor = match origin {
2450 HideMouseCursorOrigin::TypingAction => {
2451 matches!(
2452 self.hide_mouse_mode,
2453 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2454 )
2455 }
2456 HideMouseCursorOrigin::MovementAction => {
2457 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2458 }
2459 };
2460 if self.mouse_cursor_hidden != hide_mouse_cursor {
2461 self.mouse_cursor_hidden = hide_mouse_cursor;
2462 cx.notify();
2463 }
2464 }
2465
2466 pub fn edit_prediction_in_conflict(&self) -> bool {
2467 if !self.show_edit_predictions_in_menu() {
2468 return false;
2469 }
2470
2471 let showing_completions = self
2472 .context_menu
2473 .borrow()
2474 .as_ref()
2475 .map_or(false, |context| {
2476 matches!(context, CodeContextMenu::Completions(_))
2477 });
2478
2479 showing_completions
2480 || self.edit_prediction_requires_modifier()
2481 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2482 // bindings to insert tab characters.
2483 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2484 }
2485
2486 pub fn accept_edit_prediction_keybind(
2487 &self,
2488 accept_partial: bool,
2489 window: &Window,
2490 cx: &App,
2491 ) -> AcceptEditPredictionBinding {
2492 let key_context = self.key_context_internal(true, window, cx);
2493 let in_conflict = self.edit_prediction_in_conflict();
2494
2495 let bindings = if accept_partial {
2496 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2497 } else {
2498 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2499 };
2500
2501 // TODO: if the binding contains multiple keystrokes, display all of them, not
2502 // just the first one.
2503 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2504 !in_conflict
2505 || binding
2506 .keystrokes()
2507 .first()
2508 .map_or(false, |keystroke| keystroke.modifiers.modified())
2509 }))
2510 }
2511
2512 pub fn new_file(
2513 workspace: &mut Workspace,
2514 _: &workspace::NewFile,
2515 window: &mut Window,
2516 cx: &mut Context<Workspace>,
2517 ) {
2518 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2519 "Failed to create buffer",
2520 window,
2521 cx,
2522 |e, _, _| match e.error_code() {
2523 ErrorCode::RemoteUpgradeRequired => Some(format!(
2524 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2525 e.error_tag("required").unwrap_or("the latest version")
2526 )),
2527 _ => None,
2528 },
2529 );
2530 }
2531
2532 pub fn new_in_workspace(
2533 workspace: &mut Workspace,
2534 window: &mut Window,
2535 cx: &mut Context<Workspace>,
2536 ) -> Task<Result<Entity<Editor>>> {
2537 let project = workspace.project().clone();
2538 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2539
2540 cx.spawn_in(window, async move |workspace, cx| {
2541 let buffer = create.await?;
2542 workspace.update_in(cx, |workspace, window, cx| {
2543 let editor =
2544 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2545 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2546 editor
2547 })
2548 })
2549 }
2550
2551 fn new_file_vertical(
2552 workspace: &mut Workspace,
2553 _: &workspace::NewFileSplitVertical,
2554 window: &mut Window,
2555 cx: &mut Context<Workspace>,
2556 ) {
2557 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2558 }
2559
2560 fn new_file_horizontal(
2561 workspace: &mut Workspace,
2562 _: &workspace::NewFileSplitHorizontal,
2563 window: &mut Window,
2564 cx: &mut Context<Workspace>,
2565 ) {
2566 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2567 }
2568
2569 fn new_file_in_direction(
2570 workspace: &mut Workspace,
2571 direction: SplitDirection,
2572 window: &mut Window,
2573 cx: &mut Context<Workspace>,
2574 ) {
2575 let project = workspace.project().clone();
2576 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2577
2578 cx.spawn_in(window, async move |workspace, cx| {
2579 let buffer = create.await?;
2580 workspace.update_in(cx, move |workspace, window, cx| {
2581 workspace.split_item(
2582 direction,
2583 Box::new(
2584 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2585 ),
2586 window,
2587 cx,
2588 )
2589 })?;
2590 anyhow::Ok(())
2591 })
2592 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2593 match e.error_code() {
2594 ErrorCode::RemoteUpgradeRequired => Some(format!(
2595 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2596 e.error_tag("required").unwrap_or("the latest version")
2597 )),
2598 _ => None,
2599 }
2600 });
2601 }
2602
2603 pub fn leader_id(&self) -> Option<CollaboratorId> {
2604 self.leader_id
2605 }
2606
2607 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2608 &self.buffer
2609 }
2610
2611 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2612 self.workspace.as_ref()?.0.upgrade()
2613 }
2614
2615 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2616 self.buffer().read(cx).title(cx)
2617 }
2618
2619 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2620 let git_blame_gutter_max_author_length = self
2621 .render_git_blame_gutter(cx)
2622 .then(|| {
2623 if let Some(blame) = self.blame.as_ref() {
2624 let max_author_length =
2625 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2626 Some(max_author_length)
2627 } else {
2628 None
2629 }
2630 })
2631 .flatten();
2632
2633 EditorSnapshot {
2634 mode: self.mode.clone(),
2635 show_gutter: self.show_gutter,
2636 show_line_numbers: self.show_line_numbers,
2637 show_git_diff_gutter: self.show_git_diff_gutter,
2638 show_code_actions: self.show_code_actions,
2639 show_runnables: self.show_runnables,
2640 show_breakpoints: self.show_breakpoints,
2641 git_blame_gutter_max_author_length,
2642 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2643 scroll_anchor: self.scroll_manager.anchor(),
2644 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2645 placeholder_text: self.placeholder_text.clone(),
2646 is_focused: self.focus_handle.is_focused(window),
2647 current_line_highlight: self
2648 .current_line_highlight
2649 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2650 gutter_hovered: self.gutter_hovered,
2651 }
2652 }
2653
2654 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2655 self.buffer.read(cx).language_at(point, cx)
2656 }
2657
2658 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2659 self.buffer.read(cx).read(cx).file_at(point).cloned()
2660 }
2661
2662 pub fn active_excerpt(
2663 &self,
2664 cx: &App,
2665 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2666 self.buffer
2667 .read(cx)
2668 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2669 }
2670
2671 pub fn mode(&self) -> &EditorMode {
2672 &self.mode
2673 }
2674
2675 pub fn set_mode(&mut self, mode: EditorMode) {
2676 self.mode = mode;
2677 }
2678
2679 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2680 self.collaboration_hub.as_deref()
2681 }
2682
2683 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2684 self.collaboration_hub = Some(hub);
2685 }
2686
2687 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2688 self.in_project_search = in_project_search;
2689 }
2690
2691 pub fn set_custom_context_menu(
2692 &mut self,
2693 f: impl 'static
2694 + Fn(
2695 &mut Self,
2696 DisplayPoint,
2697 &mut Window,
2698 &mut Context<Self>,
2699 ) -> Option<Entity<ui::ContextMenu>>,
2700 ) {
2701 self.custom_context_menu = Some(Box::new(f))
2702 }
2703
2704 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2705 self.completion_provider = provider;
2706 }
2707
2708 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2709 self.semantics_provider.clone()
2710 }
2711
2712 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2713 self.semantics_provider = provider;
2714 }
2715
2716 pub fn set_edit_prediction_provider<T>(
2717 &mut self,
2718 provider: Option<Entity<T>>,
2719 window: &mut Window,
2720 cx: &mut Context<Self>,
2721 ) where
2722 T: EditPredictionProvider,
2723 {
2724 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2725 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2726 if this.focus_handle.is_focused(window) {
2727 this.update_visible_edit_prediction(window, cx);
2728 }
2729 }),
2730 provider: Arc::new(provider),
2731 });
2732 self.update_edit_prediction_settings(cx);
2733 self.refresh_edit_prediction(false, false, window, cx);
2734 }
2735
2736 pub fn placeholder_text(&self) -> Option<&str> {
2737 self.placeholder_text.as_deref()
2738 }
2739
2740 pub fn set_placeholder_text(
2741 &mut self,
2742 placeholder_text: impl Into<Arc<str>>,
2743 cx: &mut Context<Self>,
2744 ) {
2745 let placeholder_text = Some(placeholder_text.into());
2746 if self.placeholder_text != placeholder_text {
2747 self.placeholder_text = placeholder_text;
2748 cx.notify();
2749 }
2750 }
2751
2752 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2753 self.cursor_shape = cursor_shape;
2754
2755 // Disrupt blink for immediate user feedback that the cursor shape has changed
2756 self.blink_manager.update(cx, BlinkManager::show_cursor);
2757
2758 cx.notify();
2759 }
2760
2761 pub fn set_current_line_highlight(
2762 &mut self,
2763 current_line_highlight: Option<CurrentLineHighlight>,
2764 ) {
2765 self.current_line_highlight = current_line_highlight;
2766 }
2767
2768 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2769 self.collapse_matches = collapse_matches;
2770 }
2771
2772 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2773 let buffers = self.buffer.read(cx).all_buffers();
2774 let Some(project) = self.project.as_ref() else {
2775 return;
2776 };
2777 project.update(cx, |project, cx| {
2778 for buffer in buffers {
2779 self.registered_buffers
2780 .entry(buffer.read(cx).remote_id())
2781 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2782 }
2783 })
2784 }
2785
2786 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2787 if self.collapse_matches {
2788 return range.start..range.start;
2789 }
2790 range.clone()
2791 }
2792
2793 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2794 if self.display_map.read(cx).clip_at_line_ends != clip {
2795 self.display_map
2796 .update(cx, |map, _| map.clip_at_line_ends = clip);
2797 }
2798 }
2799
2800 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2801 self.input_enabled = input_enabled;
2802 }
2803
2804 pub fn set_edit_predictions_hidden_for_vim_mode(
2805 &mut self,
2806 hidden: bool,
2807 window: &mut Window,
2808 cx: &mut Context<Self>,
2809 ) {
2810 if hidden != self.edit_predictions_hidden_for_vim_mode {
2811 self.edit_predictions_hidden_for_vim_mode = hidden;
2812 if hidden {
2813 self.update_visible_edit_prediction(window, cx);
2814 } else {
2815 self.refresh_edit_prediction(true, false, window, cx);
2816 }
2817 }
2818 }
2819
2820 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2821 self.menu_edit_predictions_policy = value;
2822 }
2823
2824 pub fn set_autoindent(&mut self, autoindent: bool) {
2825 if autoindent {
2826 self.autoindent_mode = Some(AutoindentMode::EachLine);
2827 } else {
2828 self.autoindent_mode = None;
2829 }
2830 }
2831
2832 pub fn read_only(&self, cx: &App) -> bool {
2833 self.read_only || self.buffer.read(cx).read_only()
2834 }
2835
2836 pub fn set_read_only(&mut self, read_only: bool) {
2837 self.read_only = read_only;
2838 }
2839
2840 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2841 self.use_autoclose = autoclose;
2842 }
2843
2844 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2845 self.use_auto_surround = auto_surround;
2846 }
2847
2848 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2849 self.auto_replace_emoji_shortcode = auto_replace;
2850 }
2851
2852 pub fn toggle_edit_predictions(
2853 &mut self,
2854 _: &ToggleEditPrediction,
2855 window: &mut Window,
2856 cx: &mut Context<Self>,
2857 ) {
2858 if self.show_edit_predictions_override.is_some() {
2859 self.set_show_edit_predictions(None, window, cx);
2860 } else {
2861 let show_edit_predictions = !self.edit_predictions_enabled();
2862 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2863 }
2864 }
2865
2866 pub fn set_show_edit_predictions(
2867 &mut self,
2868 show_edit_predictions: Option<bool>,
2869 window: &mut Window,
2870 cx: &mut Context<Self>,
2871 ) {
2872 self.show_edit_predictions_override = show_edit_predictions;
2873 self.update_edit_prediction_settings(cx);
2874
2875 if let Some(false) = show_edit_predictions {
2876 self.discard_edit_prediction(false, cx);
2877 } else {
2878 self.refresh_edit_prediction(false, true, window, cx);
2879 }
2880 }
2881
2882 fn edit_predictions_disabled_in_scope(
2883 &self,
2884 buffer: &Entity<Buffer>,
2885 buffer_position: language::Anchor,
2886 cx: &App,
2887 ) -> bool {
2888 let snapshot = buffer.read(cx).snapshot();
2889 let settings = snapshot.settings_at(buffer_position, cx);
2890
2891 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2892 return false;
2893 };
2894
2895 scope.override_name().map_or(false, |scope_name| {
2896 settings
2897 .edit_predictions_disabled_in
2898 .iter()
2899 .any(|s| s == scope_name)
2900 })
2901 }
2902
2903 pub fn set_use_modal_editing(&mut self, to: bool) {
2904 self.use_modal_editing = to;
2905 }
2906
2907 pub fn use_modal_editing(&self) -> bool {
2908 self.use_modal_editing
2909 }
2910
2911 fn selections_did_change(
2912 &mut self,
2913 local: bool,
2914 old_cursor_position: &Anchor,
2915 effects: SelectionEffects,
2916 window: &mut Window,
2917 cx: &mut Context<Self>,
2918 ) {
2919 window.invalidate_character_coordinates();
2920
2921 // Copy selections to primary selection buffer
2922 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2923 if local {
2924 let selections = self.selections.all::<usize>(cx);
2925 let buffer_handle = self.buffer.read(cx).read(cx);
2926
2927 let mut text = String::new();
2928 for (index, selection) in selections.iter().enumerate() {
2929 let text_for_selection = buffer_handle
2930 .text_for_range(selection.start..selection.end)
2931 .collect::<String>();
2932
2933 text.push_str(&text_for_selection);
2934 if index != selections.len() - 1 {
2935 text.push('\n');
2936 }
2937 }
2938
2939 if !text.is_empty() {
2940 cx.write_to_primary(ClipboardItem::new_string(text));
2941 }
2942 }
2943
2944 let selection_anchors = self.selections.disjoint_anchors();
2945
2946 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2947 self.buffer.update(cx, |buffer, cx| {
2948 buffer.set_active_selections(
2949 &selection_anchors,
2950 self.selections.line_mode,
2951 self.cursor_shape,
2952 cx,
2953 )
2954 });
2955 }
2956 let display_map = self
2957 .display_map
2958 .update(cx, |display_map, cx| display_map.snapshot(cx));
2959 let buffer = &display_map.buffer_snapshot;
2960 if self.selections.count() == 1 {
2961 self.add_selections_state = None;
2962 }
2963 self.select_next_state = None;
2964 self.select_prev_state = None;
2965 self.select_syntax_node_history.try_clear();
2966 self.invalidate_autoclose_regions(&selection_anchors, buffer);
2967 self.snippet_stack.invalidate(&selection_anchors, buffer);
2968 self.take_rename(false, window, cx);
2969
2970 let newest_selection = self.selections.newest_anchor();
2971 let new_cursor_position = newest_selection.head();
2972 let selection_start = newest_selection.start;
2973
2974 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2975 self.push_to_nav_history(
2976 *old_cursor_position,
2977 Some(new_cursor_position.to_point(buffer)),
2978 false,
2979 effects.nav_history == Some(true),
2980 cx,
2981 );
2982 }
2983
2984 if local {
2985 if let Some(buffer_id) = new_cursor_position.buffer_id {
2986 if !self.registered_buffers.contains_key(&buffer_id) {
2987 if let Some(project) = self.project.as_ref() {
2988 project.update(cx, |project, cx| {
2989 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2990 return;
2991 };
2992 self.registered_buffers.insert(
2993 buffer_id,
2994 project.register_buffer_with_language_servers(&buffer, cx),
2995 );
2996 })
2997 }
2998 }
2999 }
3000
3001 let mut context_menu = self.context_menu.borrow_mut();
3002 let completion_menu = match context_menu.as_ref() {
3003 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3004 Some(CodeContextMenu::CodeActions(_)) => {
3005 *context_menu = None;
3006 None
3007 }
3008 None => None,
3009 };
3010 let completion_position = completion_menu.map(|menu| menu.initial_position);
3011 drop(context_menu);
3012
3013 if effects.completions {
3014 if let Some(completion_position) = completion_position {
3015 let start_offset = selection_start.to_offset(buffer);
3016 let position_matches = start_offset == completion_position.to_offset(buffer);
3017 let continue_showing = if position_matches {
3018 if self.snippet_stack.is_empty() {
3019 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3020 } else {
3021 // Snippet choices can be shown even when the cursor is in whitespace.
3022 // Dismissing the menu with actions like backspace is handled by
3023 // invalidation regions.
3024 true
3025 }
3026 } else {
3027 false
3028 };
3029
3030 if continue_showing {
3031 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3032 } else {
3033 self.hide_context_menu(window, cx);
3034 }
3035 }
3036 }
3037
3038 hide_hover(self, cx);
3039
3040 if old_cursor_position.to_display_point(&display_map).row()
3041 != new_cursor_position.to_display_point(&display_map).row()
3042 {
3043 self.available_code_actions.take();
3044 }
3045 self.refresh_code_actions(window, cx);
3046 self.refresh_document_highlights(cx);
3047 self.refresh_selected_text_highlights(false, window, cx);
3048 refresh_matching_bracket_highlights(self, window, cx);
3049 self.update_visible_edit_prediction(window, cx);
3050 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3051 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3052 self.inline_blame_popover.take();
3053 if self.git_blame_inline_enabled {
3054 self.start_inline_blame_timer(window, cx);
3055 }
3056 }
3057
3058 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3059 cx.emit(EditorEvent::SelectionsChanged { local });
3060
3061 let selections = &self.selections.disjoint;
3062 if selections.len() == 1 {
3063 cx.emit(SearchEvent::ActiveMatchChanged)
3064 }
3065 if local {
3066 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3067 let inmemory_selections = selections
3068 .iter()
3069 .map(|s| {
3070 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3071 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3072 })
3073 .collect();
3074 self.update_restoration_data(cx, |data| {
3075 data.selections = inmemory_selections;
3076 });
3077
3078 if WorkspaceSettings::get(None, cx).restore_on_startup
3079 != RestoreOnStartupBehavior::None
3080 {
3081 if let Some(workspace_id) =
3082 self.workspace.as_ref().and_then(|workspace| workspace.1)
3083 {
3084 let snapshot = self.buffer().read(cx).snapshot(cx);
3085 let selections = selections.clone();
3086 let background_executor = cx.background_executor().clone();
3087 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3088 self.serialize_selections = cx.background_spawn(async move {
3089 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3090 let db_selections = selections
3091 .iter()
3092 .map(|selection| {
3093 (
3094 selection.start.to_offset(&snapshot),
3095 selection.end.to_offset(&snapshot),
3096 )
3097 })
3098 .collect();
3099
3100 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3101 .await
3102 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3103 .log_err();
3104 });
3105 }
3106 }
3107 }
3108 }
3109
3110 cx.notify();
3111 }
3112
3113 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3114 use text::ToOffset as _;
3115 use text::ToPoint as _;
3116
3117 if self.mode.is_minimap()
3118 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3119 {
3120 return;
3121 }
3122
3123 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3124 return;
3125 };
3126
3127 let snapshot = singleton.read(cx).snapshot();
3128 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3129 let display_snapshot = display_map.snapshot(cx);
3130
3131 display_snapshot
3132 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3133 .map(|fold| {
3134 fold.range.start.text_anchor.to_point(&snapshot)
3135 ..fold.range.end.text_anchor.to_point(&snapshot)
3136 })
3137 .collect()
3138 });
3139 self.update_restoration_data(cx, |data| {
3140 data.folds = inmemory_folds;
3141 });
3142
3143 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3144 return;
3145 };
3146 let background_executor = cx.background_executor().clone();
3147 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3148 let db_folds = self.display_map.update(cx, |display_map, cx| {
3149 display_map
3150 .snapshot(cx)
3151 .folds_in_range(0..snapshot.len())
3152 .map(|fold| {
3153 (
3154 fold.range.start.text_anchor.to_offset(&snapshot),
3155 fold.range.end.text_anchor.to_offset(&snapshot),
3156 )
3157 })
3158 .collect()
3159 });
3160 self.serialize_folds = cx.background_spawn(async move {
3161 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3162 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3163 .await
3164 .with_context(|| {
3165 format!(
3166 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3167 )
3168 })
3169 .log_err();
3170 });
3171 }
3172
3173 pub fn sync_selections(
3174 &mut self,
3175 other: Entity<Editor>,
3176 cx: &mut Context<Self>,
3177 ) -> gpui::Subscription {
3178 let other_selections = other.read(cx).selections.disjoint.to_vec();
3179 self.selections.change_with(cx, |selections| {
3180 selections.select_anchors(other_selections);
3181 });
3182
3183 let other_subscription =
3184 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3185 EditorEvent::SelectionsChanged { local: true } => {
3186 let other_selections = other.read(cx).selections.disjoint.to_vec();
3187 if other_selections.is_empty() {
3188 return;
3189 }
3190 this.selections.change_with(cx, |selections| {
3191 selections.select_anchors(other_selections);
3192 });
3193 }
3194 _ => {}
3195 });
3196
3197 let this_subscription =
3198 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3199 EditorEvent::SelectionsChanged { local: true } => {
3200 let these_selections = this.selections.disjoint.to_vec();
3201 if these_selections.is_empty() {
3202 return;
3203 }
3204 other.update(cx, |other_editor, cx| {
3205 other_editor.selections.change_with(cx, |selections| {
3206 selections.select_anchors(these_selections);
3207 })
3208 });
3209 }
3210 _ => {}
3211 });
3212
3213 Subscription::join(other_subscription, this_subscription)
3214 }
3215
3216 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3217 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3218 /// effects of selection change occur at the end of the transaction.
3219 pub fn change_selections<R>(
3220 &mut self,
3221 effects: SelectionEffects,
3222 window: &mut Window,
3223 cx: &mut Context<Self>,
3224 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3225 ) -> R {
3226 if let Some(state) = &mut self.deferred_selection_effects_state {
3227 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3228 state.effects.completions = effects.completions;
3229 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3230 let (changed, result) = self.selections.change_with(cx, change);
3231 state.changed |= changed;
3232 return result;
3233 }
3234 let mut state = DeferredSelectionEffectsState {
3235 changed: false,
3236 effects,
3237 old_cursor_position: self.selections.newest_anchor().head(),
3238 history_entry: SelectionHistoryEntry {
3239 selections: self.selections.disjoint_anchors(),
3240 select_next_state: self.select_next_state.clone(),
3241 select_prev_state: self.select_prev_state.clone(),
3242 add_selections_state: self.add_selections_state.clone(),
3243 },
3244 };
3245 let (changed, result) = self.selections.change_with(cx, change);
3246 state.changed = state.changed || changed;
3247 if self.defer_selection_effects {
3248 self.deferred_selection_effects_state = Some(state);
3249 } else {
3250 self.apply_selection_effects(state, window, cx);
3251 }
3252 result
3253 }
3254
3255 /// Defers the effects of selection change, so that the effects of multiple calls to
3256 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3257 /// to selection history and the state of popovers based on selection position aren't
3258 /// erroneously updated.
3259 pub fn with_selection_effects_deferred<R>(
3260 &mut self,
3261 window: &mut Window,
3262 cx: &mut Context<Self>,
3263 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3264 ) -> R {
3265 let already_deferred = self.defer_selection_effects;
3266 self.defer_selection_effects = true;
3267 let result = update(self, window, cx);
3268 if !already_deferred {
3269 self.defer_selection_effects = false;
3270 if let Some(state) = self.deferred_selection_effects_state.take() {
3271 self.apply_selection_effects(state, window, cx);
3272 }
3273 }
3274 result
3275 }
3276
3277 fn apply_selection_effects(
3278 &mut self,
3279 state: DeferredSelectionEffectsState,
3280 window: &mut Window,
3281 cx: &mut Context<Self>,
3282 ) {
3283 if state.changed {
3284 self.selection_history.push(state.history_entry);
3285
3286 if let Some(autoscroll) = state.effects.scroll {
3287 self.request_autoscroll(autoscroll, cx);
3288 }
3289
3290 let old_cursor_position = &state.old_cursor_position;
3291
3292 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3293
3294 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3295 self.show_signature_help(&ShowSignatureHelp, window, cx);
3296 }
3297 }
3298 }
3299
3300 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3301 where
3302 I: IntoIterator<Item = (Range<S>, T)>,
3303 S: ToOffset,
3304 T: Into<Arc<str>>,
3305 {
3306 if self.read_only(cx) {
3307 return;
3308 }
3309
3310 self.buffer
3311 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3312 }
3313
3314 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3315 where
3316 I: IntoIterator<Item = (Range<S>, T)>,
3317 S: ToOffset,
3318 T: Into<Arc<str>>,
3319 {
3320 if self.read_only(cx) {
3321 return;
3322 }
3323
3324 self.buffer.update(cx, |buffer, cx| {
3325 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3326 });
3327 }
3328
3329 pub fn edit_with_block_indent<I, S, T>(
3330 &mut self,
3331 edits: I,
3332 original_indent_columns: Vec<Option<u32>>,
3333 cx: &mut Context<Self>,
3334 ) where
3335 I: IntoIterator<Item = (Range<S>, T)>,
3336 S: ToOffset,
3337 T: Into<Arc<str>>,
3338 {
3339 if self.read_only(cx) {
3340 return;
3341 }
3342
3343 self.buffer.update(cx, |buffer, cx| {
3344 buffer.edit(
3345 edits,
3346 Some(AutoindentMode::Block {
3347 original_indent_columns,
3348 }),
3349 cx,
3350 )
3351 });
3352 }
3353
3354 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3355 self.hide_context_menu(window, cx);
3356
3357 match phase {
3358 SelectPhase::Begin {
3359 position,
3360 add,
3361 click_count,
3362 } => self.begin_selection(position, add, click_count, window, cx),
3363 SelectPhase::BeginColumnar {
3364 position,
3365 goal_column,
3366 reset,
3367 mode,
3368 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3369 SelectPhase::Extend {
3370 position,
3371 click_count,
3372 } => self.extend_selection(position, click_count, window, cx),
3373 SelectPhase::Update {
3374 position,
3375 goal_column,
3376 scroll_delta,
3377 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3378 SelectPhase::End => self.end_selection(window, cx),
3379 }
3380 }
3381
3382 fn extend_selection(
3383 &mut self,
3384 position: DisplayPoint,
3385 click_count: usize,
3386 window: &mut Window,
3387 cx: &mut Context<Self>,
3388 ) {
3389 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3390 let tail = self.selections.newest::<usize>(cx).tail();
3391 self.begin_selection(position, false, click_count, window, cx);
3392
3393 let position = position.to_offset(&display_map, Bias::Left);
3394 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3395
3396 let mut pending_selection = self
3397 .selections
3398 .pending_anchor()
3399 .expect("extend_selection not called with pending selection");
3400 if position >= tail {
3401 pending_selection.start = tail_anchor;
3402 } else {
3403 pending_selection.end = tail_anchor;
3404 pending_selection.reversed = true;
3405 }
3406
3407 let mut pending_mode = self.selections.pending_mode().unwrap();
3408 match &mut pending_mode {
3409 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3410 _ => {}
3411 }
3412
3413 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3414 SelectionEffects::scroll(Autoscroll::fit())
3415 } else {
3416 SelectionEffects::no_scroll()
3417 };
3418
3419 self.change_selections(effects, window, cx, |s| {
3420 s.set_pending(pending_selection, pending_mode)
3421 });
3422 }
3423
3424 fn begin_selection(
3425 &mut self,
3426 position: DisplayPoint,
3427 add: bool,
3428 click_count: usize,
3429 window: &mut Window,
3430 cx: &mut Context<Self>,
3431 ) {
3432 if !self.focus_handle.is_focused(window) {
3433 self.last_focused_descendant = None;
3434 window.focus(&self.focus_handle);
3435 }
3436
3437 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3438 let buffer = &display_map.buffer_snapshot;
3439 let position = display_map.clip_point(position, Bias::Left);
3440
3441 let start;
3442 let end;
3443 let mode;
3444 let mut auto_scroll;
3445 match click_count {
3446 1 => {
3447 start = buffer.anchor_before(position.to_point(&display_map));
3448 end = start;
3449 mode = SelectMode::Character;
3450 auto_scroll = true;
3451 }
3452 2 => {
3453 let position = display_map
3454 .clip_point(position, Bias::Left)
3455 .to_offset(&display_map, Bias::Left);
3456 let (range, _) = buffer.surrounding_word(position, false);
3457 start = buffer.anchor_before(range.start);
3458 end = buffer.anchor_before(range.end);
3459 mode = SelectMode::Word(start..end);
3460 auto_scroll = true;
3461 }
3462 3 => {
3463 let position = display_map
3464 .clip_point(position, Bias::Left)
3465 .to_point(&display_map);
3466 let line_start = display_map.prev_line_boundary(position).0;
3467 let next_line_start = buffer.clip_point(
3468 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3469 Bias::Left,
3470 );
3471 start = buffer.anchor_before(line_start);
3472 end = buffer.anchor_before(next_line_start);
3473 mode = SelectMode::Line(start..end);
3474 auto_scroll = true;
3475 }
3476 _ => {
3477 start = buffer.anchor_before(0);
3478 end = buffer.anchor_before(buffer.len());
3479 mode = SelectMode::All;
3480 auto_scroll = false;
3481 }
3482 }
3483 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3484
3485 let point_to_delete: Option<usize> = {
3486 let selected_points: Vec<Selection<Point>> =
3487 self.selections.disjoint_in_range(start..end, cx);
3488
3489 if !add || click_count > 1 {
3490 None
3491 } else if !selected_points.is_empty() {
3492 Some(selected_points[0].id)
3493 } else {
3494 let clicked_point_already_selected =
3495 self.selections.disjoint.iter().find(|selection| {
3496 selection.start.to_point(buffer) == start.to_point(buffer)
3497 || selection.end.to_point(buffer) == end.to_point(buffer)
3498 });
3499
3500 clicked_point_already_selected.map(|selection| selection.id)
3501 }
3502 };
3503
3504 let selections_count = self.selections.count();
3505 let effects = if auto_scroll {
3506 SelectionEffects::default()
3507 } else {
3508 SelectionEffects::no_scroll()
3509 };
3510
3511 self.change_selections(effects, window, cx, |s| {
3512 if let Some(point_to_delete) = point_to_delete {
3513 s.delete(point_to_delete);
3514
3515 if selections_count == 1 {
3516 s.set_pending_anchor_range(start..end, mode);
3517 }
3518 } else {
3519 if !add {
3520 s.clear_disjoint();
3521 }
3522
3523 s.set_pending_anchor_range(start..end, mode);
3524 }
3525 });
3526 }
3527
3528 fn begin_columnar_selection(
3529 &mut self,
3530 position: DisplayPoint,
3531 goal_column: u32,
3532 reset: bool,
3533 mode: ColumnarMode,
3534 window: &mut Window,
3535 cx: &mut Context<Self>,
3536 ) {
3537 if !self.focus_handle.is_focused(window) {
3538 self.last_focused_descendant = None;
3539 window.focus(&self.focus_handle);
3540 }
3541
3542 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3543
3544 if reset {
3545 let pointer_position = display_map
3546 .buffer_snapshot
3547 .anchor_before(position.to_point(&display_map));
3548
3549 self.change_selections(
3550 SelectionEffects::scroll(Autoscroll::newest()),
3551 window,
3552 cx,
3553 |s| {
3554 s.clear_disjoint();
3555 s.set_pending_anchor_range(
3556 pointer_position..pointer_position,
3557 SelectMode::Character,
3558 );
3559 },
3560 );
3561 };
3562
3563 let tail = self.selections.newest::<Point>(cx).tail();
3564 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3565 self.columnar_selection_state = match mode {
3566 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3567 selection_tail: selection_anchor,
3568 display_point: if reset {
3569 if position.column() != goal_column {
3570 Some(DisplayPoint::new(position.row(), goal_column))
3571 } else {
3572 None
3573 }
3574 } else {
3575 None
3576 },
3577 }),
3578 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3579 selection_tail: selection_anchor,
3580 }),
3581 };
3582
3583 if !reset {
3584 self.select_columns(position, goal_column, &display_map, window, cx);
3585 }
3586 }
3587
3588 fn update_selection(
3589 &mut self,
3590 position: DisplayPoint,
3591 goal_column: u32,
3592 scroll_delta: gpui::Point<f32>,
3593 window: &mut Window,
3594 cx: &mut Context<Self>,
3595 ) {
3596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3597
3598 if self.columnar_selection_state.is_some() {
3599 self.select_columns(position, goal_column, &display_map, window, cx);
3600 } else if let Some(mut pending) = self.selections.pending_anchor() {
3601 let buffer = &display_map.buffer_snapshot;
3602 let head;
3603 let tail;
3604 let mode = self.selections.pending_mode().unwrap();
3605 match &mode {
3606 SelectMode::Character => {
3607 head = position.to_point(&display_map);
3608 tail = pending.tail().to_point(buffer);
3609 }
3610 SelectMode::Word(original_range) => {
3611 let offset = display_map
3612 .clip_point(position, Bias::Left)
3613 .to_offset(&display_map, Bias::Left);
3614 let original_range = original_range.to_offset(buffer);
3615
3616 let head_offset = if buffer.is_inside_word(offset, false)
3617 || original_range.contains(&offset)
3618 {
3619 let (word_range, _) = buffer.surrounding_word(offset, false);
3620 if word_range.start < original_range.start {
3621 word_range.start
3622 } else {
3623 word_range.end
3624 }
3625 } else {
3626 offset
3627 };
3628
3629 head = head_offset.to_point(buffer);
3630 if head_offset <= original_range.start {
3631 tail = original_range.end.to_point(buffer);
3632 } else {
3633 tail = original_range.start.to_point(buffer);
3634 }
3635 }
3636 SelectMode::Line(original_range) => {
3637 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3638
3639 let position = display_map
3640 .clip_point(position, Bias::Left)
3641 .to_point(&display_map);
3642 let line_start = display_map.prev_line_boundary(position).0;
3643 let next_line_start = buffer.clip_point(
3644 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3645 Bias::Left,
3646 );
3647
3648 if line_start < original_range.start {
3649 head = line_start
3650 } else {
3651 head = next_line_start
3652 }
3653
3654 if head <= original_range.start {
3655 tail = original_range.end;
3656 } else {
3657 tail = original_range.start;
3658 }
3659 }
3660 SelectMode::All => {
3661 return;
3662 }
3663 };
3664
3665 if head < tail {
3666 pending.start = buffer.anchor_before(head);
3667 pending.end = buffer.anchor_before(tail);
3668 pending.reversed = true;
3669 } else {
3670 pending.start = buffer.anchor_before(tail);
3671 pending.end = buffer.anchor_before(head);
3672 pending.reversed = false;
3673 }
3674
3675 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3676 s.set_pending(pending, mode);
3677 });
3678 } else {
3679 log::error!("update_selection dispatched with no pending selection");
3680 return;
3681 }
3682
3683 self.apply_scroll_delta(scroll_delta, window, cx);
3684 cx.notify();
3685 }
3686
3687 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3688 self.columnar_selection_state.take();
3689 if self.selections.pending_anchor().is_some() {
3690 let selections = self.selections.all::<usize>(cx);
3691 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3692 s.select(selections);
3693 s.clear_pending();
3694 });
3695 }
3696 }
3697
3698 fn select_columns(
3699 &mut self,
3700 head: DisplayPoint,
3701 goal_column: u32,
3702 display_map: &DisplaySnapshot,
3703 window: &mut Window,
3704 cx: &mut Context<Self>,
3705 ) {
3706 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3707 return;
3708 };
3709
3710 let tail = match columnar_state {
3711 ColumnarSelectionState::FromMouse {
3712 selection_tail,
3713 display_point,
3714 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3715 ColumnarSelectionState::FromSelection { selection_tail } => {
3716 selection_tail.to_display_point(&display_map)
3717 }
3718 };
3719
3720 let start_row = cmp::min(tail.row(), head.row());
3721 let end_row = cmp::max(tail.row(), head.row());
3722 let start_column = cmp::min(tail.column(), goal_column);
3723 let end_column = cmp::max(tail.column(), goal_column);
3724 let reversed = start_column < tail.column();
3725
3726 let selection_ranges = (start_row.0..=end_row.0)
3727 .map(DisplayRow)
3728 .filter_map(|row| {
3729 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3730 || start_column <= display_map.line_len(row))
3731 && !display_map.is_block_line(row)
3732 {
3733 let start = display_map
3734 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3735 .to_point(display_map);
3736 let end = display_map
3737 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3738 .to_point(display_map);
3739 if reversed {
3740 Some(end..start)
3741 } else {
3742 Some(start..end)
3743 }
3744 } else {
3745 None
3746 }
3747 })
3748 .collect::<Vec<_>>();
3749
3750 let ranges = match columnar_state {
3751 ColumnarSelectionState::FromMouse { .. } => {
3752 let mut non_empty_ranges = selection_ranges
3753 .iter()
3754 .filter(|selection_range| selection_range.start != selection_range.end)
3755 .peekable();
3756 if non_empty_ranges.peek().is_some() {
3757 non_empty_ranges.cloned().collect()
3758 } else {
3759 selection_ranges
3760 }
3761 }
3762 _ => selection_ranges,
3763 };
3764
3765 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3766 s.select_ranges(ranges);
3767 });
3768 cx.notify();
3769 }
3770
3771 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3772 self.selections
3773 .all_adjusted(cx)
3774 .iter()
3775 .any(|selection| !selection.is_empty())
3776 }
3777
3778 pub fn has_pending_nonempty_selection(&self) -> bool {
3779 let pending_nonempty_selection = match self.selections.pending_anchor() {
3780 Some(Selection { start, end, .. }) => start != end,
3781 None => false,
3782 };
3783
3784 pending_nonempty_selection
3785 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3786 }
3787
3788 pub fn has_pending_selection(&self) -> bool {
3789 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3790 }
3791
3792 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3793 self.selection_mark_mode = false;
3794 self.selection_drag_state = SelectionDragState::None;
3795
3796 if self.clear_expanded_diff_hunks(cx) {
3797 cx.notify();
3798 return;
3799 }
3800 if self.dismiss_menus_and_popups(true, window, cx) {
3801 return;
3802 }
3803
3804 if self.mode.is_full()
3805 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3806 {
3807 return;
3808 }
3809
3810 cx.propagate();
3811 }
3812
3813 pub fn dismiss_menus_and_popups(
3814 &mut self,
3815 is_user_requested: bool,
3816 window: &mut Window,
3817 cx: &mut Context<Self>,
3818 ) -> bool {
3819 if self.take_rename(false, window, cx).is_some() {
3820 return true;
3821 }
3822
3823 if hide_hover(self, cx) {
3824 return true;
3825 }
3826
3827 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3828 return true;
3829 }
3830
3831 if self.hide_context_menu(window, cx).is_some() {
3832 return true;
3833 }
3834
3835 if self.mouse_context_menu.take().is_some() {
3836 return true;
3837 }
3838
3839 if is_user_requested && self.discard_edit_prediction(true, cx) {
3840 return true;
3841 }
3842
3843 if self.snippet_stack.pop().is_some() {
3844 return true;
3845 }
3846
3847 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3848 self.dismiss_diagnostics(cx);
3849 return true;
3850 }
3851
3852 false
3853 }
3854
3855 fn linked_editing_ranges_for(
3856 &self,
3857 selection: Range<text::Anchor>,
3858 cx: &App,
3859 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3860 if self.linked_edit_ranges.is_empty() {
3861 return None;
3862 }
3863 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3864 selection.end.buffer_id.and_then(|end_buffer_id| {
3865 if selection.start.buffer_id != Some(end_buffer_id) {
3866 return None;
3867 }
3868 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3869 let snapshot = buffer.read(cx).snapshot();
3870 self.linked_edit_ranges
3871 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3872 .map(|ranges| (ranges, snapshot, buffer))
3873 })?;
3874 use text::ToOffset as TO;
3875 // find offset from the start of current range to current cursor position
3876 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3877
3878 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3879 let start_difference = start_offset - start_byte_offset;
3880 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3881 let end_difference = end_offset - start_byte_offset;
3882 // Current range has associated linked ranges.
3883 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3884 for range in linked_ranges.iter() {
3885 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3886 let end_offset = start_offset + end_difference;
3887 let start_offset = start_offset + start_difference;
3888 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3889 continue;
3890 }
3891 if self.selections.disjoint_anchor_ranges().any(|s| {
3892 if s.start.buffer_id != selection.start.buffer_id
3893 || s.end.buffer_id != selection.end.buffer_id
3894 {
3895 return false;
3896 }
3897 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3898 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3899 }) {
3900 continue;
3901 }
3902 let start = buffer_snapshot.anchor_after(start_offset);
3903 let end = buffer_snapshot.anchor_after(end_offset);
3904 linked_edits
3905 .entry(buffer.clone())
3906 .or_default()
3907 .push(start..end);
3908 }
3909 Some(linked_edits)
3910 }
3911
3912 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3913 let text: Arc<str> = text.into();
3914
3915 if self.read_only(cx) {
3916 return;
3917 }
3918
3919 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3920
3921 let selections = self.selections.all_adjusted(cx);
3922 let mut bracket_inserted = false;
3923 let mut edits = Vec::new();
3924 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3925 let mut new_selections = Vec::with_capacity(selections.len());
3926 let mut new_autoclose_regions = Vec::new();
3927 let snapshot = self.buffer.read(cx).read(cx);
3928 let mut clear_linked_edit_ranges = false;
3929
3930 for (selection, autoclose_region) in
3931 self.selections_with_autoclose_regions(selections, &snapshot)
3932 {
3933 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3934 // Determine if the inserted text matches the opening or closing
3935 // bracket of any of this language's bracket pairs.
3936 let mut bracket_pair = None;
3937 let mut is_bracket_pair_start = false;
3938 let mut is_bracket_pair_end = false;
3939 if !text.is_empty() {
3940 let mut bracket_pair_matching_end = None;
3941 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3942 // and they are removing the character that triggered IME popup.
3943 for (pair, enabled) in scope.brackets() {
3944 if !pair.close && !pair.surround {
3945 continue;
3946 }
3947
3948 if enabled && pair.start.ends_with(text.as_ref()) {
3949 let prefix_len = pair.start.len() - text.len();
3950 let preceding_text_matches_prefix = prefix_len == 0
3951 || (selection.start.column >= (prefix_len as u32)
3952 && snapshot.contains_str_at(
3953 Point::new(
3954 selection.start.row,
3955 selection.start.column - (prefix_len as u32),
3956 ),
3957 &pair.start[..prefix_len],
3958 ));
3959 if preceding_text_matches_prefix {
3960 bracket_pair = Some(pair.clone());
3961 is_bracket_pair_start = true;
3962 break;
3963 }
3964 }
3965 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3966 {
3967 // take first bracket pair matching end, but don't break in case a later bracket
3968 // pair matches start
3969 bracket_pair_matching_end = Some(pair.clone());
3970 }
3971 }
3972 if let Some(end) = bracket_pair_matching_end
3973 && bracket_pair.is_none()
3974 {
3975 bracket_pair = Some(end);
3976 is_bracket_pair_end = true;
3977 }
3978 }
3979
3980 if let Some(bracket_pair) = bracket_pair {
3981 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3982 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3983 let auto_surround =
3984 self.use_auto_surround && snapshot_settings.use_auto_surround;
3985 if selection.is_empty() {
3986 if is_bracket_pair_start {
3987 // If the inserted text is a suffix of an opening bracket and the
3988 // selection is preceded by the rest of the opening bracket, then
3989 // insert the closing bracket.
3990 let following_text_allows_autoclose = snapshot
3991 .chars_at(selection.start)
3992 .next()
3993 .map_or(true, |c| scope.should_autoclose_before(c));
3994
3995 let preceding_text_allows_autoclose = selection.start.column == 0
3996 || snapshot.reversed_chars_at(selection.start).next().map_or(
3997 true,
3998 |c| {
3999 bracket_pair.start != bracket_pair.end
4000 || !snapshot
4001 .char_classifier_at(selection.start)
4002 .is_word(c)
4003 },
4004 );
4005
4006 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4007 && bracket_pair.start.len() == 1
4008 {
4009 let target = bracket_pair.start.chars().next().unwrap();
4010 let current_line_count = snapshot
4011 .reversed_chars_at(selection.start)
4012 .take_while(|&c| c != '\n')
4013 .filter(|&c| c == target)
4014 .count();
4015 current_line_count % 2 == 1
4016 } else {
4017 false
4018 };
4019
4020 if autoclose
4021 && bracket_pair.close
4022 && following_text_allows_autoclose
4023 && preceding_text_allows_autoclose
4024 && !is_closing_quote
4025 {
4026 let anchor = snapshot.anchor_before(selection.end);
4027 new_selections.push((selection.map(|_| anchor), text.len()));
4028 new_autoclose_regions.push((
4029 anchor,
4030 text.len(),
4031 selection.id,
4032 bracket_pair.clone(),
4033 ));
4034 edits.push((
4035 selection.range(),
4036 format!("{}{}", text, bracket_pair.end).into(),
4037 ));
4038 bracket_inserted = true;
4039 continue;
4040 }
4041 }
4042
4043 if let Some(region) = autoclose_region {
4044 // If the selection is followed by an auto-inserted closing bracket,
4045 // then don't insert that closing bracket again; just move the selection
4046 // past the closing bracket.
4047 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4048 && text.as_ref() == region.pair.end.as_str()
4049 && snapshot.contains_str_at(region.range.end, text.as_ref());
4050 if should_skip {
4051 let anchor = snapshot.anchor_after(selection.end);
4052 new_selections
4053 .push((selection.map(|_| anchor), region.pair.end.len()));
4054 continue;
4055 }
4056 }
4057
4058 let always_treat_brackets_as_autoclosed = snapshot
4059 .language_settings_at(selection.start, cx)
4060 .always_treat_brackets_as_autoclosed;
4061 if always_treat_brackets_as_autoclosed
4062 && is_bracket_pair_end
4063 && snapshot.contains_str_at(selection.end, text.as_ref())
4064 {
4065 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4066 // and the inserted text is a closing bracket and the selection is followed
4067 // by the closing bracket then move the selection past the closing bracket.
4068 let anchor = snapshot.anchor_after(selection.end);
4069 new_selections.push((selection.map(|_| anchor), text.len()));
4070 continue;
4071 }
4072 }
4073 // If an opening bracket is 1 character long and is typed while
4074 // text is selected, then surround that text with the bracket pair.
4075 else if auto_surround
4076 && bracket_pair.surround
4077 && is_bracket_pair_start
4078 && bracket_pair.start.chars().count() == 1
4079 {
4080 edits.push((selection.start..selection.start, text.clone()));
4081 edits.push((
4082 selection.end..selection.end,
4083 bracket_pair.end.as_str().into(),
4084 ));
4085 bracket_inserted = true;
4086 new_selections.push((
4087 Selection {
4088 id: selection.id,
4089 start: snapshot.anchor_after(selection.start),
4090 end: snapshot.anchor_before(selection.end),
4091 reversed: selection.reversed,
4092 goal: selection.goal,
4093 },
4094 0,
4095 ));
4096 continue;
4097 }
4098 }
4099 }
4100
4101 if self.auto_replace_emoji_shortcode
4102 && selection.is_empty()
4103 && text.as_ref().ends_with(':')
4104 {
4105 if let Some(possible_emoji_short_code) =
4106 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4107 {
4108 if !possible_emoji_short_code.is_empty() {
4109 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4110 let emoji_shortcode_start = Point::new(
4111 selection.start.row,
4112 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4113 );
4114
4115 // Remove shortcode from buffer
4116 edits.push((
4117 emoji_shortcode_start..selection.start,
4118 "".to_string().into(),
4119 ));
4120 new_selections.push((
4121 Selection {
4122 id: selection.id,
4123 start: snapshot.anchor_after(emoji_shortcode_start),
4124 end: snapshot.anchor_before(selection.start),
4125 reversed: selection.reversed,
4126 goal: selection.goal,
4127 },
4128 0,
4129 ));
4130
4131 // Insert emoji
4132 let selection_start_anchor = snapshot.anchor_after(selection.start);
4133 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4134 edits.push((selection.start..selection.end, emoji.to_string().into()));
4135
4136 continue;
4137 }
4138 }
4139 }
4140 }
4141
4142 // If not handling any auto-close operation, then just replace the selected
4143 // text with the given input and move the selection to the end of the
4144 // newly inserted text.
4145 let anchor = snapshot.anchor_after(selection.end);
4146 if !self.linked_edit_ranges.is_empty() {
4147 let start_anchor = snapshot.anchor_before(selection.start);
4148
4149 let is_word_char = text.chars().next().map_or(true, |char| {
4150 let classifier = snapshot
4151 .char_classifier_at(start_anchor.to_offset(&snapshot))
4152 .ignore_punctuation(true);
4153 classifier.is_word(char)
4154 });
4155
4156 if is_word_char {
4157 if let Some(ranges) = self
4158 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4159 {
4160 for (buffer, edits) in ranges {
4161 linked_edits
4162 .entry(buffer.clone())
4163 .or_default()
4164 .extend(edits.into_iter().map(|range| (range, text.clone())));
4165 }
4166 }
4167 } else {
4168 clear_linked_edit_ranges = true;
4169 }
4170 }
4171
4172 new_selections.push((selection.map(|_| anchor), 0));
4173 edits.push((selection.start..selection.end, text.clone()));
4174 }
4175
4176 drop(snapshot);
4177
4178 self.transact(window, cx, |this, window, cx| {
4179 if clear_linked_edit_ranges {
4180 this.linked_edit_ranges.clear();
4181 }
4182 let initial_buffer_versions =
4183 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4184
4185 this.buffer.update(cx, |buffer, cx| {
4186 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4187 });
4188 for (buffer, edits) in linked_edits {
4189 buffer.update(cx, |buffer, cx| {
4190 let snapshot = buffer.snapshot();
4191 let edits = edits
4192 .into_iter()
4193 .map(|(range, text)| {
4194 use text::ToPoint as TP;
4195 let end_point = TP::to_point(&range.end, &snapshot);
4196 let start_point = TP::to_point(&range.start, &snapshot);
4197 (start_point..end_point, text)
4198 })
4199 .sorted_by_key(|(range, _)| range.start);
4200 buffer.edit(edits, None, cx);
4201 })
4202 }
4203 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4204 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4205 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4206 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4207 .zip(new_selection_deltas)
4208 .map(|(selection, delta)| Selection {
4209 id: selection.id,
4210 start: selection.start + delta,
4211 end: selection.end + delta,
4212 reversed: selection.reversed,
4213 goal: SelectionGoal::None,
4214 })
4215 .collect::<Vec<_>>();
4216
4217 let mut i = 0;
4218 for (position, delta, selection_id, pair) in new_autoclose_regions {
4219 let position = position.to_offset(&map.buffer_snapshot) + delta;
4220 let start = map.buffer_snapshot.anchor_before(position);
4221 let end = map.buffer_snapshot.anchor_after(position);
4222 while let Some(existing_state) = this.autoclose_regions.get(i) {
4223 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4224 Ordering::Less => i += 1,
4225 Ordering::Greater => break,
4226 Ordering::Equal => {
4227 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4228 Ordering::Less => i += 1,
4229 Ordering::Equal => break,
4230 Ordering::Greater => break,
4231 }
4232 }
4233 }
4234 }
4235 this.autoclose_regions.insert(
4236 i,
4237 AutocloseRegion {
4238 selection_id,
4239 range: start..end,
4240 pair,
4241 },
4242 );
4243 }
4244
4245 let had_active_edit_prediction = this.has_active_edit_prediction();
4246 this.change_selections(
4247 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4248 window,
4249 cx,
4250 |s| s.select(new_selections),
4251 );
4252
4253 if !bracket_inserted {
4254 if let Some(on_type_format_task) =
4255 this.trigger_on_type_formatting(text.to_string(), window, cx)
4256 {
4257 on_type_format_task.detach_and_log_err(cx);
4258 }
4259 }
4260
4261 let editor_settings = EditorSettings::get_global(cx);
4262 if bracket_inserted
4263 && (editor_settings.auto_signature_help
4264 || editor_settings.show_signature_help_after_edits)
4265 {
4266 this.show_signature_help(&ShowSignatureHelp, window, cx);
4267 }
4268
4269 let trigger_in_words =
4270 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4271 if this.hard_wrap.is_some() {
4272 let latest: Range<Point> = this.selections.newest(cx).range();
4273 if latest.is_empty()
4274 && this
4275 .buffer()
4276 .read(cx)
4277 .snapshot(cx)
4278 .line_len(MultiBufferRow(latest.start.row))
4279 == latest.start.column
4280 {
4281 this.rewrap_impl(
4282 RewrapOptions {
4283 override_language_settings: true,
4284 preserve_existing_whitespace: true,
4285 },
4286 cx,
4287 )
4288 }
4289 }
4290 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4291 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4292 this.refresh_edit_prediction(true, false, window, cx);
4293 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4294 });
4295 }
4296
4297 fn find_possible_emoji_shortcode_at_position(
4298 snapshot: &MultiBufferSnapshot,
4299 position: Point,
4300 ) -> Option<String> {
4301 let mut chars = Vec::new();
4302 let mut found_colon = false;
4303 for char in snapshot.reversed_chars_at(position).take(100) {
4304 // Found a possible emoji shortcode in the middle of the buffer
4305 if found_colon {
4306 if char.is_whitespace() {
4307 chars.reverse();
4308 return Some(chars.iter().collect());
4309 }
4310 // If the previous character is not a whitespace, we are in the middle of a word
4311 // and we only want to complete the shortcode if the word is made up of other emojis
4312 let mut containing_word = String::new();
4313 for ch in snapshot
4314 .reversed_chars_at(position)
4315 .skip(chars.len() + 1)
4316 .take(100)
4317 {
4318 if ch.is_whitespace() {
4319 break;
4320 }
4321 containing_word.push(ch);
4322 }
4323 let containing_word = containing_word.chars().rev().collect::<String>();
4324 if util::word_consists_of_emojis(containing_word.as_str()) {
4325 chars.reverse();
4326 return Some(chars.iter().collect());
4327 }
4328 }
4329
4330 if char.is_whitespace() || !char.is_ascii() {
4331 return None;
4332 }
4333 if char == ':' {
4334 found_colon = true;
4335 } else {
4336 chars.push(char);
4337 }
4338 }
4339 // Found a possible emoji shortcode at the beginning of the buffer
4340 chars.reverse();
4341 Some(chars.iter().collect())
4342 }
4343
4344 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4345 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4346 self.transact(window, cx, |this, window, cx| {
4347 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4348 let selections = this.selections.all::<usize>(cx);
4349 let multi_buffer = this.buffer.read(cx);
4350 let buffer = multi_buffer.snapshot(cx);
4351 selections
4352 .iter()
4353 .map(|selection| {
4354 let start_point = selection.start.to_point(&buffer);
4355 let mut existing_indent =
4356 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4357 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4358 let start = selection.start;
4359 let end = selection.end;
4360 let selection_is_empty = start == end;
4361 let language_scope = buffer.language_scope_at(start);
4362 let (
4363 comment_delimiter,
4364 doc_delimiter,
4365 insert_extra_newline,
4366 indent_on_newline,
4367 indent_on_extra_newline,
4368 ) = if let Some(language) = &language_scope {
4369 let mut insert_extra_newline =
4370 insert_extra_newline_brackets(&buffer, start..end, language)
4371 || insert_extra_newline_tree_sitter(&buffer, start..end);
4372
4373 // Comment extension on newline is allowed only for cursor selections
4374 let comment_delimiter = maybe!({
4375 if !selection_is_empty {
4376 return None;
4377 }
4378
4379 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4380 return None;
4381 }
4382
4383 let delimiters = language.line_comment_prefixes();
4384 let max_len_of_delimiter =
4385 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4386 let (snapshot, range) =
4387 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4388
4389 let num_of_whitespaces = snapshot
4390 .chars_for_range(range.clone())
4391 .take_while(|c| c.is_whitespace())
4392 .count();
4393 let comment_candidate = snapshot
4394 .chars_for_range(range.clone())
4395 .skip(num_of_whitespaces)
4396 .take(max_len_of_delimiter)
4397 .collect::<String>();
4398 let (delimiter, trimmed_len) = delimiters
4399 .iter()
4400 .filter_map(|delimiter| {
4401 let prefix = delimiter.trim_end();
4402 if comment_candidate.starts_with(prefix) {
4403 Some((delimiter, prefix.len()))
4404 } else {
4405 None
4406 }
4407 })
4408 .max_by_key(|(_, len)| *len)?;
4409
4410 if let Some(BlockCommentConfig {
4411 start: block_start, ..
4412 }) = language.block_comment()
4413 {
4414 let block_start_trimmed = block_start.trim_end();
4415 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4416 let line_content = snapshot
4417 .chars_for_range(range)
4418 .skip(num_of_whitespaces)
4419 .take(block_start_trimmed.len())
4420 .collect::<String>();
4421
4422 if line_content.starts_with(block_start_trimmed) {
4423 return None;
4424 }
4425 }
4426 }
4427
4428 let cursor_is_placed_after_comment_marker =
4429 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4430 if cursor_is_placed_after_comment_marker {
4431 Some(delimiter.clone())
4432 } else {
4433 None
4434 }
4435 });
4436
4437 let mut indent_on_newline = IndentSize::spaces(0);
4438 let mut indent_on_extra_newline = IndentSize::spaces(0);
4439
4440 let doc_delimiter = maybe!({
4441 if !selection_is_empty {
4442 return None;
4443 }
4444
4445 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4446 return None;
4447 }
4448
4449 let BlockCommentConfig {
4450 start: start_tag,
4451 end: end_tag,
4452 prefix: delimiter,
4453 tab_size: len,
4454 } = language.documentation_comment()?;
4455 let is_within_block_comment = buffer
4456 .language_scope_at(start_point)
4457 .is_some_and(|scope| scope.override_name() == Some("comment"));
4458 if !is_within_block_comment {
4459 return None;
4460 }
4461
4462 let (snapshot, range) =
4463 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4464
4465 let num_of_whitespaces = snapshot
4466 .chars_for_range(range.clone())
4467 .take_while(|c| c.is_whitespace())
4468 .count();
4469
4470 // 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.
4471 let column = start_point.column;
4472 let cursor_is_after_start_tag = {
4473 let start_tag_len = start_tag.len();
4474 let start_tag_line = snapshot
4475 .chars_for_range(range.clone())
4476 .skip(num_of_whitespaces)
4477 .take(start_tag_len)
4478 .collect::<String>();
4479 if start_tag_line.starts_with(start_tag.as_ref()) {
4480 num_of_whitespaces + start_tag_len <= column as usize
4481 } else {
4482 false
4483 }
4484 };
4485
4486 let cursor_is_after_delimiter = {
4487 let delimiter_trim = delimiter.trim_end();
4488 let delimiter_line = snapshot
4489 .chars_for_range(range.clone())
4490 .skip(num_of_whitespaces)
4491 .take(delimiter_trim.len())
4492 .collect::<String>();
4493 if delimiter_line.starts_with(delimiter_trim) {
4494 num_of_whitespaces + delimiter_trim.len() <= column as usize
4495 } else {
4496 false
4497 }
4498 };
4499
4500 let cursor_is_before_end_tag_if_exists = {
4501 let mut char_position = 0u32;
4502 let mut end_tag_offset = None;
4503
4504 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4505 if let Some(byte_pos) = chunk.find(&**end_tag) {
4506 let chars_before_match =
4507 chunk[..byte_pos].chars().count() as u32;
4508 end_tag_offset =
4509 Some(char_position + chars_before_match);
4510 break 'outer;
4511 }
4512 char_position += chunk.chars().count() as u32;
4513 }
4514
4515 if let Some(end_tag_offset) = end_tag_offset {
4516 let cursor_is_before_end_tag = column <= end_tag_offset;
4517 if cursor_is_after_start_tag {
4518 if cursor_is_before_end_tag {
4519 insert_extra_newline = true;
4520 }
4521 let cursor_is_at_start_of_end_tag =
4522 column == end_tag_offset;
4523 if cursor_is_at_start_of_end_tag {
4524 indent_on_extra_newline.len = *len;
4525 }
4526 }
4527 cursor_is_before_end_tag
4528 } else {
4529 true
4530 }
4531 };
4532
4533 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4534 && cursor_is_before_end_tag_if_exists
4535 {
4536 if cursor_is_after_start_tag {
4537 indent_on_newline.len = *len;
4538 }
4539 Some(delimiter.clone())
4540 } else {
4541 None
4542 }
4543 });
4544
4545 (
4546 comment_delimiter,
4547 doc_delimiter,
4548 insert_extra_newline,
4549 indent_on_newline,
4550 indent_on_extra_newline,
4551 )
4552 } else {
4553 (
4554 None,
4555 None,
4556 false,
4557 IndentSize::default(),
4558 IndentSize::default(),
4559 )
4560 };
4561
4562 let prevent_auto_indent = doc_delimiter.is_some();
4563 let delimiter = comment_delimiter.or(doc_delimiter);
4564
4565 let capacity_for_delimiter =
4566 delimiter.as_deref().map(str::len).unwrap_or_default();
4567 let mut new_text = String::with_capacity(
4568 1 + capacity_for_delimiter
4569 + existing_indent.len as usize
4570 + indent_on_newline.len as usize
4571 + indent_on_extra_newline.len as usize,
4572 );
4573 new_text.push('\n');
4574 new_text.extend(existing_indent.chars());
4575 new_text.extend(indent_on_newline.chars());
4576
4577 if let Some(delimiter) = &delimiter {
4578 new_text.push_str(delimiter);
4579 }
4580
4581 if insert_extra_newline {
4582 new_text.push('\n');
4583 new_text.extend(existing_indent.chars());
4584 new_text.extend(indent_on_extra_newline.chars());
4585 }
4586
4587 let anchor = buffer.anchor_after(end);
4588 let new_selection = selection.map(|_| anchor);
4589 (
4590 ((start..end, new_text), prevent_auto_indent),
4591 (insert_extra_newline, new_selection),
4592 )
4593 })
4594 .unzip()
4595 };
4596
4597 let mut auto_indent_edits = Vec::new();
4598 let mut edits = Vec::new();
4599 for (edit, prevent_auto_indent) in edits_with_flags {
4600 if prevent_auto_indent {
4601 edits.push(edit);
4602 } else {
4603 auto_indent_edits.push(edit);
4604 }
4605 }
4606 if !edits.is_empty() {
4607 this.edit(edits, cx);
4608 }
4609 if !auto_indent_edits.is_empty() {
4610 this.edit_with_autoindent(auto_indent_edits, cx);
4611 }
4612
4613 let buffer = this.buffer.read(cx).snapshot(cx);
4614 let new_selections = selection_info
4615 .into_iter()
4616 .map(|(extra_newline_inserted, new_selection)| {
4617 let mut cursor = new_selection.end.to_point(&buffer);
4618 if extra_newline_inserted {
4619 cursor.row -= 1;
4620 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4621 }
4622 new_selection.map(|_| cursor)
4623 })
4624 .collect();
4625
4626 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4627 this.refresh_edit_prediction(true, false, window, cx);
4628 });
4629 }
4630
4631 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4632 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4633
4634 let buffer = self.buffer.read(cx);
4635 let snapshot = buffer.snapshot(cx);
4636
4637 let mut edits = Vec::new();
4638 let mut rows = Vec::new();
4639
4640 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4641 let cursor = selection.head();
4642 let row = cursor.row;
4643
4644 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4645
4646 let newline = "\n".to_string();
4647 edits.push((start_of_line..start_of_line, newline));
4648
4649 rows.push(row + rows_inserted as u32);
4650 }
4651
4652 self.transact(window, cx, |editor, window, cx| {
4653 editor.edit(edits, cx);
4654
4655 editor.change_selections(Default::default(), window, cx, |s| {
4656 let mut index = 0;
4657 s.move_cursors_with(|map, _, _| {
4658 let row = rows[index];
4659 index += 1;
4660
4661 let point = Point::new(row, 0);
4662 let boundary = map.next_line_boundary(point).1;
4663 let clipped = map.clip_point(boundary, Bias::Left);
4664
4665 (clipped, SelectionGoal::None)
4666 });
4667 });
4668
4669 let mut indent_edits = Vec::new();
4670 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4671 for row in rows {
4672 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4673 for (row, indent) in indents {
4674 if indent.len == 0 {
4675 continue;
4676 }
4677
4678 let text = match indent.kind {
4679 IndentKind::Space => " ".repeat(indent.len as usize),
4680 IndentKind::Tab => "\t".repeat(indent.len as usize),
4681 };
4682 let point = Point::new(row.0, 0);
4683 indent_edits.push((point..point, text));
4684 }
4685 }
4686 editor.edit(indent_edits, cx);
4687 });
4688 }
4689
4690 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4691 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4692
4693 let buffer = self.buffer.read(cx);
4694 let snapshot = buffer.snapshot(cx);
4695
4696 let mut edits = Vec::new();
4697 let mut rows = Vec::new();
4698 let mut rows_inserted = 0;
4699
4700 for selection in self.selections.all_adjusted(cx) {
4701 let cursor = selection.head();
4702 let row = cursor.row;
4703
4704 let point = Point::new(row + 1, 0);
4705 let start_of_line = snapshot.clip_point(point, Bias::Left);
4706
4707 let newline = "\n".to_string();
4708 edits.push((start_of_line..start_of_line, newline));
4709
4710 rows_inserted += 1;
4711 rows.push(row + rows_inserted);
4712 }
4713
4714 self.transact(window, cx, |editor, window, cx| {
4715 editor.edit(edits, cx);
4716
4717 editor.change_selections(Default::default(), window, cx, |s| {
4718 let mut index = 0;
4719 s.move_cursors_with(|map, _, _| {
4720 let row = rows[index];
4721 index += 1;
4722
4723 let point = Point::new(row, 0);
4724 let boundary = map.next_line_boundary(point).1;
4725 let clipped = map.clip_point(boundary, Bias::Left);
4726
4727 (clipped, SelectionGoal::None)
4728 });
4729 });
4730
4731 let mut indent_edits = Vec::new();
4732 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4733 for row in rows {
4734 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4735 for (row, indent) in indents {
4736 if indent.len == 0 {
4737 continue;
4738 }
4739
4740 let text = match indent.kind {
4741 IndentKind::Space => " ".repeat(indent.len as usize),
4742 IndentKind::Tab => "\t".repeat(indent.len as usize),
4743 };
4744 let point = Point::new(row.0, 0);
4745 indent_edits.push((point..point, text));
4746 }
4747 }
4748 editor.edit(indent_edits, cx);
4749 });
4750 }
4751
4752 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4753 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4754 original_indent_columns: Vec::new(),
4755 });
4756 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4757 }
4758
4759 fn insert_with_autoindent_mode(
4760 &mut self,
4761 text: &str,
4762 autoindent_mode: Option<AutoindentMode>,
4763 window: &mut Window,
4764 cx: &mut Context<Self>,
4765 ) {
4766 if self.read_only(cx) {
4767 return;
4768 }
4769
4770 let text: Arc<str> = text.into();
4771 self.transact(window, cx, |this, window, cx| {
4772 let old_selections = this.selections.all_adjusted(cx);
4773 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4774 let anchors = {
4775 let snapshot = buffer.read(cx);
4776 old_selections
4777 .iter()
4778 .map(|s| {
4779 let anchor = snapshot.anchor_after(s.head());
4780 s.map(|_| anchor)
4781 })
4782 .collect::<Vec<_>>()
4783 };
4784 buffer.edit(
4785 old_selections
4786 .iter()
4787 .map(|s| (s.start..s.end, text.clone())),
4788 autoindent_mode,
4789 cx,
4790 );
4791 anchors
4792 });
4793
4794 this.change_selections(Default::default(), window, cx, |s| {
4795 s.select_anchors(selection_anchors);
4796 });
4797
4798 cx.notify();
4799 });
4800 }
4801
4802 fn trigger_completion_on_input(
4803 &mut self,
4804 text: &str,
4805 trigger_in_words: bool,
4806 window: &mut Window,
4807 cx: &mut Context<Self>,
4808 ) {
4809 let completions_source = self
4810 .context_menu
4811 .borrow()
4812 .as_ref()
4813 .and_then(|menu| match menu {
4814 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4815 CodeContextMenu::CodeActions(_) => None,
4816 });
4817
4818 match completions_source {
4819 Some(CompletionsMenuSource::Words) => {
4820 self.show_word_completions(&ShowWordCompletions, window, cx)
4821 }
4822 Some(CompletionsMenuSource::Normal)
4823 | Some(CompletionsMenuSource::SnippetChoices)
4824 | None
4825 if self.is_completion_trigger(
4826 text,
4827 trigger_in_words,
4828 completions_source.is_some(),
4829 cx,
4830 ) =>
4831 {
4832 self.show_completions(
4833 &ShowCompletions {
4834 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4835 },
4836 window,
4837 cx,
4838 )
4839 }
4840 _ => {
4841 self.hide_context_menu(window, cx);
4842 }
4843 }
4844 }
4845
4846 fn is_completion_trigger(
4847 &self,
4848 text: &str,
4849 trigger_in_words: bool,
4850 menu_is_open: bool,
4851 cx: &mut Context<Self>,
4852 ) -> bool {
4853 let position = self.selections.newest_anchor().head();
4854 let multibuffer = self.buffer.read(cx);
4855 let Some(buffer) = position
4856 .buffer_id
4857 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4858 else {
4859 return false;
4860 };
4861
4862 if let Some(completion_provider) = &self.completion_provider {
4863 completion_provider.is_completion_trigger(
4864 &buffer,
4865 position.text_anchor,
4866 text,
4867 trigger_in_words,
4868 menu_is_open,
4869 cx,
4870 )
4871 } else {
4872 false
4873 }
4874 }
4875
4876 /// If any empty selections is touching the start of its innermost containing autoclose
4877 /// region, expand it to select the brackets.
4878 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4879 let selections = self.selections.all::<usize>(cx);
4880 let buffer = self.buffer.read(cx).read(cx);
4881 let new_selections = self
4882 .selections_with_autoclose_regions(selections, &buffer)
4883 .map(|(mut selection, region)| {
4884 if !selection.is_empty() {
4885 return selection;
4886 }
4887
4888 if let Some(region) = region {
4889 let mut range = region.range.to_offset(&buffer);
4890 if selection.start == range.start && range.start >= region.pair.start.len() {
4891 range.start -= region.pair.start.len();
4892 if buffer.contains_str_at(range.start, ®ion.pair.start)
4893 && buffer.contains_str_at(range.end, ®ion.pair.end)
4894 {
4895 range.end += region.pair.end.len();
4896 selection.start = range.start;
4897 selection.end = range.end;
4898
4899 return selection;
4900 }
4901 }
4902 }
4903
4904 let always_treat_brackets_as_autoclosed = buffer
4905 .language_settings_at(selection.start, cx)
4906 .always_treat_brackets_as_autoclosed;
4907
4908 if !always_treat_brackets_as_autoclosed {
4909 return selection;
4910 }
4911
4912 if let Some(scope) = buffer.language_scope_at(selection.start) {
4913 for (pair, enabled) in scope.brackets() {
4914 if !enabled || !pair.close {
4915 continue;
4916 }
4917
4918 if buffer.contains_str_at(selection.start, &pair.end) {
4919 let pair_start_len = pair.start.len();
4920 if buffer.contains_str_at(
4921 selection.start.saturating_sub(pair_start_len),
4922 &pair.start,
4923 ) {
4924 selection.start -= pair_start_len;
4925 selection.end += pair.end.len();
4926
4927 return selection;
4928 }
4929 }
4930 }
4931 }
4932
4933 selection
4934 })
4935 .collect();
4936
4937 drop(buffer);
4938 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4939 selections.select(new_selections)
4940 });
4941 }
4942
4943 /// Iterate the given selections, and for each one, find the smallest surrounding
4944 /// autoclose region. This uses the ordering of the selections and the autoclose
4945 /// regions to avoid repeated comparisons.
4946 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4947 &'a self,
4948 selections: impl IntoIterator<Item = Selection<D>>,
4949 buffer: &'a MultiBufferSnapshot,
4950 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4951 let mut i = 0;
4952 let mut regions = self.autoclose_regions.as_slice();
4953 selections.into_iter().map(move |selection| {
4954 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4955
4956 let mut enclosing = None;
4957 while let Some(pair_state) = regions.get(i) {
4958 if pair_state.range.end.to_offset(buffer) < range.start {
4959 regions = ®ions[i + 1..];
4960 i = 0;
4961 } else if pair_state.range.start.to_offset(buffer) > range.end {
4962 break;
4963 } else {
4964 if pair_state.selection_id == selection.id {
4965 enclosing = Some(pair_state);
4966 }
4967 i += 1;
4968 }
4969 }
4970
4971 (selection, enclosing)
4972 })
4973 }
4974
4975 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
4976 fn invalidate_autoclose_regions(
4977 &mut self,
4978 mut selections: &[Selection<Anchor>],
4979 buffer: &MultiBufferSnapshot,
4980 ) {
4981 self.autoclose_regions.retain(|state| {
4982 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
4983 return false;
4984 }
4985
4986 let mut i = 0;
4987 while let Some(selection) = selections.get(i) {
4988 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4989 selections = &selections[1..];
4990 continue;
4991 }
4992 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4993 break;
4994 }
4995 if selection.id == state.selection_id {
4996 return true;
4997 } else {
4998 i += 1;
4999 }
5000 }
5001 false
5002 });
5003 }
5004
5005 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5006 let offset = position.to_offset(buffer);
5007 let (word_range, kind) = buffer.surrounding_word(offset, true);
5008 if offset > word_range.start && kind == Some(CharKind::Word) {
5009 Some(
5010 buffer
5011 .text_for_range(word_range.start..offset)
5012 .collect::<String>(),
5013 )
5014 } else {
5015 None
5016 }
5017 }
5018
5019 pub fn toggle_inline_values(
5020 &mut self,
5021 _: &ToggleInlineValues,
5022 _: &mut Window,
5023 cx: &mut Context<Self>,
5024 ) {
5025 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5026
5027 self.refresh_inline_values(cx);
5028 }
5029
5030 pub fn toggle_inlay_hints(
5031 &mut self,
5032 _: &ToggleInlayHints,
5033 _: &mut Window,
5034 cx: &mut Context<Self>,
5035 ) {
5036 self.refresh_inlay_hints(
5037 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5038 cx,
5039 );
5040 }
5041
5042 pub fn inlay_hints_enabled(&self) -> bool {
5043 self.inlay_hint_cache.enabled
5044 }
5045
5046 pub fn inline_values_enabled(&self) -> bool {
5047 self.inline_value_cache.enabled
5048 }
5049
5050 #[cfg(any(test, feature = "test-support"))]
5051 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5052 self.display_map
5053 .read(cx)
5054 .current_inlays()
5055 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5056 .cloned()
5057 .collect()
5058 }
5059
5060 #[cfg(any(test, feature = "test-support"))]
5061 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5062 self.display_map
5063 .read(cx)
5064 .current_inlays()
5065 .cloned()
5066 .collect()
5067 }
5068
5069 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5070 if self.semantics_provider.is_none() || !self.mode.is_full() {
5071 return;
5072 }
5073
5074 let reason_description = reason.description();
5075 let ignore_debounce = matches!(
5076 reason,
5077 InlayHintRefreshReason::SettingsChange(_)
5078 | InlayHintRefreshReason::Toggle(_)
5079 | InlayHintRefreshReason::ExcerptsRemoved(_)
5080 | InlayHintRefreshReason::ModifiersChanged(_)
5081 );
5082 let (invalidate_cache, required_languages) = match reason {
5083 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5084 match self.inlay_hint_cache.modifiers_override(enabled) {
5085 Some(enabled) => {
5086 if enabled {
5087 (InvalidationStrategy::RefreshRequested, None)
5088 } else {
5089 self.splice_inlays(
5090 &self
5091 .visible_inlay_hints(cx)
5092 .iter()
5093 .map(|inlay| inlay.id)
5094 .collect::<Vec<InlayId>>(),
5095 Vec::new(),
5096 cx,
5097 );
5098 return;
5099 }
5100 }
5101 None => return,
5102 }
5103 }
5104 InlayHintRefreshReason::Toggle(enabled) => {
5105 if self.inlay_hint_cache.toggle(enabled) {
5106 if enabled {
5107 (InvalidationStrategy::RefreshRequested, None)
5108 } else {
5109 self.splice_inlays(
5110 &self
5111 .visible_inlay_hints(cx)
5112 .iter()
5113 .map(|inlay| inlay.id)
5114 .collect::<Vec<InlayId>>(),
5115 Vec::new(),
5116 cx,
5117 );
5118 return;
5119 }
5120 } else {
5121 return;
5122 }
5123 }
5124 InlayHintRefreshReason::SettingsChange(new_settings) => {
5125 match self.inlay_hint_cache.update_settings(
5126 &self.buffer,
5127 new_settings,
5128 self.visible_inlay_hints(cx),
5129 cx,
5130 ) {
5131 ControlFlow::Break(Some(InlaySplice {
5132 to_remove,
5133 to_insert,
5134 })) => {
5135 self.splice_inlays(&to_remove, to_insert, cx);
5136 return;
5137 }
5138 ControlFlow::Break(None) => return,
5139 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5140 }
5141 }
5142 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5143 if let Some(InlaySplice {
5144 to_remove,
5145 to_insert,
5146 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5147 {
5148 self.splice_inlays(&to_remove, to_insert, cx);
5149 }
5150 self.display_map.update(cx, |display_map, _| {
5151 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5152 });
5153 return;
5154 }
5155 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5156 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5157 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5158 }
5159 InlayHintRefreshReason::RefreshRequested => {
5160 (InvalidationStrategy::RefreshRequested, None)
5161 }
5162 };
5163
5164 if let Some(InlaySplice {
5165 to_remove,
5166 to_insert,
5167 }) = self.inlay_hint_cache.spawn_hint_refresh(
5168 reason_description,
5169 self.visible_excerpts(required_languages.as_ref(), cx),
5170 invalidate_cache,
5171 ignore_debounce,
5172 cx,
5173 ) {
5174 self.splice_inlays(&to_remove, to_insert, cx);
5175 }
5176 }
5177
5178 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5179 self.display_map
5180 .read(cx)
5181 .current_inlays()
5182 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5183 .cloned()
5184 .collect()
5185 }
5186
5187 pub fn visible_excerpts(
5188 &self,
5189 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5190 cx: &mut Context<Editor>,
5191 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5192 let Some(project) = self.project.as_ref() else {
5193 return HashMap::default();
5194 };
5195 let project = project.read(cx);
5196 let multi_buffer = self.buffer().read(cx);
5197 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5198 let multi_buffer_visible_start = self
5199 .scroll_manager
5200 .anchor()
5201 .anchor
5202 .to_point(&multi_buffer_snapshot);
5203 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5204 multi_buffer_visible_start
5205 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5206 Bias::Left,
5207 );
5208 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5209 multi_buffer_snapshot
5210 .range_to_buffer_ranges(multi_buffer_visible_range)
5211 .into_iter()
5212 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5213 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5214 let buffer_file = project::File::from_dyn(buffer.file())?;
5215 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5216 let worktree_entry = buffer_worktree
5217 .read(cx)
5218 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5219 if worktree_entry.is_ignored {
5220 return None;
5221 }
5222
5223 let language = buffer.language()?;
5224 if let Some(restrict_to_languages) = restrict_to_languages {
5225 if !restrict_to_languages.contains(language) {
5226 return None;
5227 }
5228 }
5229 Some((
5230 excerpt_id,
5231 (
5232 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5233 buffer.version().clone(),
5234 excerpt_visible_range,
5235 ),
5236 ))
5237 })
5238 .collect()
5239 }
5240
5241 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5242 TextLayoutDetails {
5243 text_system: window.text_system().clone(),
5244 editor_style: self.style.clone().unwrap(),
5245 rem_size: window.rem_size(),
5246 scroll_anchor: self.scroll_manager.anchor(),
5247 visible_rows: self.visible_line_count(),
5248 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5249 }
5250 }
5251
5252 pub fn splice_inlays(
5253 &self,
5254 to_remove: &[InlayId],
5255 to_insert: Vec<Inlay>,
5256 cx: &mut Context<Self>,
5257 ) {
5258 self.display_map.update(cx, |display_map, cx| {
5259 display_map.splice_inlays(to_remove, to_insert, cx)
5260 });
5261 cx.notify();
5262 }
5263
5264 fn trigger_on_type_formatting(
5265 &self,
5266 input: String,
5267 window: &mut Window,
5268 cx: &mut Context<Self>,
5269 ) -> Option<Task<Result<()>>> {
5270 if input.len() != 1 {
5271 return None;
5272 }
5273
5274 let project = self.project.as_ref()?;
5275 let position = self.selections.newest_anchor().head();
5276 let (buffer, buffer_position) = self
5277 .buffer
5278 .read(cx)
5279 .text_anchor_for_position(position, cx)?;
5280
5281 let settings = language_settings::language_settings(
5282 buffer
5283 .read(cx)
5284 .language_at(buffer_position)
5285 .map(|l| l.name()),
5286 buffer.read(cx).file(),
5287 cx,
5288 );
5289 if !settings.use_on_type_format {
5290 return None;
5291 }
5292
5293 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5294 // hence we do LSP request & edit on host side only — add formats to host's history.
5295 let push_to_lsp_host_history = true;
5296 // If this is not the host, append its history with new edits.
5297 let push_to_client_history = project.read(cx).is_via_collab();
5298
5299 let on_type_formatting = project.update(cx, |project, cx| {
5300 project.on_type_format(
5301 buffer.clone(),
5302 buffer_position,
5303 input,
5304 push_to_lsp_host_history,
5305 cx,
5306 )
5307 });
5308 Some(cx.spawn_in(window, async move |editor, cx| {
5309 if let Some(transaction) = on_type_formatting.await? {
5310 if push_to_client_history {
5311 buffer
5312 .update(cx, |buffer, _| {
5313 buffer.push_transaction(transaction, Instant::now());
5314 buffer.finalize_last_transaction();
5315 })
5316 .ok();
5317 }
5318 editor.update(cx, |editor, cx| {
5319 editor.refresh_document_highlights(cx);
5320 })?;
5321 }
5322 Ok(())
5323 }))
5324 }
5325
5326 pub fn show_word_completions(
5327 &mut self,
5328 _: &ShowWordCompletions,
5329 window: &mut Window,
5330 cx: &mut Context<Self>,
5331 ) {
5332 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5333 }
5334
5335 pub fn show_completions(
5336 &mut self,
5337 options: &ShowCompletions,
5338 window: &mut Window,
5339 cx: &mut Context<Self>,
5340 ) {
5341 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5342 }
5343
5344 fn open_or_update_completions_menu(
5345 &mut self,
5346 requested_source: Option<CompletionsMenuSource>,
5347 trigger: Option<&str>,
5348 window: &mut Window,
5349 cx: &mut Context<Self>,
5350 ) {
5351 if self.pending_rename.is_some() {
5352 return;
5353 }
5354
5355 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5356
5357 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5358 // inserted and selected. To handle that case, the start of the selection is used so that
5359 // the menu starts with all choices.
5360 let position = self
5361 .selections
5362 .newest_anchor()
5363 .start
5364 .bias_right(&multibuffer_snapshot);
5365 if position.diff_base_anchor.is_some() {
5366 return;
5367 }
5368 let (buffer, buffer_position) =
5369 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5370 output
5371 } else {
5372 return;
5373 };
5374 let buffer_snapshot = buffer.read(cx).snapshot();
5375
5376 let query: Option<Arc<String>> =
5377 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5378
5379 drop(multibuffer_snapshot);
5380
5381 let provider = match requested_source {
5382 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5383 Some(CompletionsMenuSource::Words) => None,
5384 Some(CompletionsMenuSource::SnippetChoices) => {
5385 log::error!("bug: SnippetChoices requested_source is not handled");
5386 None
5387 }
5388 };
5389
5390 let sort_completions = provider
5391 .as_ref()
5392 .map_or(false, |provider| provider.sort_completions());
5393
5394 let filter_completions = provider
5395 .as_ref()
5396 .map_or(true, |provider| provider.filter_completions());
5397
5398 let trigger_kind = match trigger {
5399 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5400 CompletionTriggerKind::TRIGGER_CHARACTER
5401 }
5402 _ => CompletionTriggerKind::INVOKED,
5403 };
5404 let completion_context = CompletionContext {
5405 trigger_character: trigger.and_then(|trigger| {
5406 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5407 Some(String::from(trigger))
5408 } else {
5409 None
5410 }
5411 }),
5412 trigger_kind,
5413 };
5414
5415 // Hide the current completions menu when a trigger char is typed. Without this, cached
5416 // completions from before the trigger char may be reused (#32774). Snippet choices could
5417 // involve trigger chars, so this is skipped in that case.
5418 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5419 {
5420 let menu_is_open = matches!(
5421 self.context_menu.borrow().as_ref(),
5422 Some(CodeContextMenu::Completions(_))
5423 );
5424 if menu_is_open {
5425 self.hide_context_menu(window, cx);
5426 }
5427 }
5428
5429 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5430 if filter_completions {
5431 menu.filter(query.clone(), provider.clone(), window, cx);
5432 }
5433 // When `is_incomplete` is false, no need to re-query completions when the current query
5434 // is a suffix of the initial query.
5435 if !menu.is_incomplete {
5436 // If the new query is a suffix of the old query (typing more characters) and
5437 // the previous result was complete, the existing completions can be filtered.
5438 //
5439 // Note that this is always true for snippet completions.
5440 let query_matches = match (&menu.initial_query, &query) {
5441 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5442 (None, _) => true,
5443 _ => false,
5444 };
5445 if query_matches {
5446 let position_matches = if menu.initial_position == position {
5447 true
5448 } else {
5449 let snapshot = self.buffer.read(cx).read(cx);
5450 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5451 };
5452 if position_matches {
5453 return;
5454 }
5455 }
5456 }
5457 };
5458
5459 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5460 buffer_snapshot.surrounding_word(buffer_position, false)
5461 {
5462 let word_to_exclude = buffer_snapshot
5463 .text_for_range(word_range.clone())
5464 .collect::<String>();
5465 (
5466 buffer_snapshot.anchor_before(word_range.start)
5467 ..buffer_snapshot.anchor_after(buffer_position),
5468 Some(word_to_exclude),
5469 )
5470 } else {
5471 (buffer_position..buffer_position, None)
5472 };
5473
5474 let language = buffer_snapshot
5475 .language_at(buffer_position)
5476 .map(|language| language.name());
5477
5478 let completion_settings =
5479 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5480
5481 let show_completion_documentation = buffer_snapshot
5482 .settings_at(buffer_position, cx)
5483 .show_completion_documentation;
5484
5485 // The document can be large, so stay in reasonable bounds when searching for words,
5486 // otherwise completion pop-up might be slow to appear.
5487 const WORD_LOOKUP_ROWS: u32 = 5_000;
5488 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5489 let min_word_search = buffer_snapshot.clip_point(
5490 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5491 Bias::Left,
5492 );
5493 let max_word_search = buffer_snapshot.clip_point(
5494 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5495 Bias::Right,
5496 );
5497 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5498 ..buffer_snapshot.point_to_offset(max_word_search);
5499
5500 let skip_digits = query
5501 .as_ref()
5502 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5503
5504 let (mut words, provider_responses) = match &provider {
5505 Some(provider) => {
5506 let provider_responses = provider.completions(
5507 position.excerpt_id,
5508 &buffer,
5509 buffer_position,
5510 completion_context,
5511 window,
5512 cx,
5513 );
5514
5515 let words = match completion_settings.words {
5516 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5517 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5518 .background_spawn(async move {
5519 buffer_snapshot.words_in_range(WordsQuery {
5520 fuzzy_contents: None,
5521 range: word_search_range,
5522 skip_digits,
5523 })
5524 }),
5525 };
5526
5527 (words, provider_responses)
5528 }
5529 None => (
5530 cx.background_spawn(async move {
5531 buffer_snapshot.words_in_range(WordsQuery {
5532 fuzzy_contents: None,
5533 range: word_search_range,
5534 skip_digits,
5535 })
5536 }),
5537 Task::ready(Ok(Vec::new())),
5538 ),
5539 };
5540
5541 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5542
5543 let id = post_inc(&mut self.next_completion_id);
5544 let task = cx.spawn_in(window, async move |editor, cx| {
5545 let Ok(()) = editor.update(cx, |this, _| {
5546 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5547 }) else {
5548 return;
5549 };
5550
5551 // TODO: Ideally completions from different sources would be selectively re-queried, so
5552 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5553 let mut completions = Vec::new();
5554 let mut is_incomplete = false;
5555 if let Some(provider_responses) = provider_responses.await.log_err() {
5556 if !provider_responses.is_empty() {
5557 for response in provider_responses {
5558 completions.extend(response.completions);
5559 is_incomplete = is_incomplete || response.is_incomplete;
5560 }
5561 if completion_settings.words == WordsCompletionMode::Fallback {
5562 words = Task::ready(BTreeMap::default());
5563 }
5564 }
5565 }
5566
5567 let mut words = words.await;
5568 if let Some(word_to_exclude) = &word_to_exclude {
5569 words.remove(word_to_exclude);
5570 }
5571 for lsp_completion in &completions {
5572 words.remove(&lsp_completion.new_text);
5573 }
5574 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5575 replace_range: word_replace_range.clone(),
5576 new_text: word.clone(),
5577 label: CodeLabel::plain(word, None),
5578 icon_path: None,
5579 documentation: None,
5580 source: CompletionSource::BufferWord {
5581 word_range,
5582 resolved: false,
5583 },
5584 insert_text_mode: Some(InsertTextMode::AS_IS),
5585 confirm: None,
5586 }));
5587
5588 let menu = if completions.is_empty() {
5589 None
5590 } else {
5591 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5592 let languages = editor
5593 .workspace
5594 .as_ref()
5595 .and_then(|(workspace, _)| workspace.upgrade())
5596 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5597 let menu = CompletionsMenu::new(
5598 id,
5599 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5600 sort_completions,
5601 show_completion_documentation,
5602 position,
5603 query.clone(),
5604 is_incomplete,
5605 buffer.clone(),
5606 completions.into(),
5607 snippet_sort_order,
5608 languages,
5609 language,
5610 cx,
5611 );
5612
5613 let query = if filter_completions { query } else { None };
5614 let matches_task = if let Some(query) = query {
5615 menu.do_async_filtering(query, cx)
5616 } else {
5617 Task::ready(menu.unfiltered_matches())
5618 };
5619 (menu, matches_task)
5620 }) else {
5621 return;
5622 };
5623
5624 let matches = matches_task.await;
5625
5626 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5627 // Newer menu already set, so exit.
5628 match editor.context_menu.borrow().as_ref() {
5629 Some(CodeContextMenu::Completions(prev_menu)) => {
5630 if prev_menu.id > id {
5631 return;
5632 }
5633 }
5634 _ => {}
5635 };
5636
5637 // Only valid to take prev_menu because it the new menu is immediately set
5638 // below, or the menu is hidden.
5639 match editor.context_menu.borrow_mut().take() {
5640 Some(CodeContextMenu::Completions(prev_menu)) => {
5641 let position_matches =
5642 if prev_menu.initial_position == menu.initial_position {
5643 true
5644 } else {
5645 let snapshot = editor.buffer.read(cx).read(cx);
5646 prev_menu.initial_position.to_offset(&snapshot)
5647 == menu.initial_position.to_offset(&snapshot)
5648 };
5649 if position_matches {
5650 // Preserve markdown cache before `set_filter_results` because it will
5651 // try to populate the documentation cache.
5652 menu.preserve_markdown_cache(prev_menu);
5653 }
5654 }
5655 _ => {}
5656 };
5657
5658 menu.set_filter_results(matches, provider, window, cx);
5659 }) else {
5660 return;
5661 };
5662
5663 menu.visible().then_some(menu)
5664 };
5665
5666 editor
5667 .update_in(cx, |editor, window, cx| {
5668 if editor.focus_handle.is_focused(window) {
5669 if let Some(menu) = menu {
5670 *editor.context_menu.borrow_mut() =
5671 Some(CodeContextMenu::Completions(menu));
5672
5673 crate::hover_popover::hide_hover(editor, cx);
5674 if editor.show_edit_predictions_in_menu() {
5675 editor.update_visible_edit_prediction(window, cx);
5676 } else {
5677 editor.discard_edit_prediction(false, cx);
5678 }
5679
5680 cx.notify();
5681 return;
5682 }
5683 }
5684
5685 if editor.completion_tasks.len() <= 1 {
5686 // If there are no more completion tasks and the last menu was empty, we should hide it.
5687 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5688 // If it was already hidden and we don't show edit predictions in the menu,
5689 // we should also show the edit prediction when available.
5690 if was_hidden && editor.show_edit_predictions_in_menu() {
5691 editor.update_visible_edit_prediction(window, cx);
5692 }
5693 }
5694 })
5695 .ok();
5696 });
5697
5698 self.completion_tasks.push((id, task));
5699 }
5700
5701 #[cfg(feature = "test-support")]
5702 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5703 let menu = self.context_menu.borrow();
5704 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5705 let completions = menu.completions.borrow();
5706 Some(completions.to_vec())
5707 } else {
5708 None
5709 }
5710 }
5711
5712 pub fn with_completions_menu_matching_id<R>(
5713 &self,
5714 id: CompletionId,
5715 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5716 ) -> R {
5717 let mut context_menu = self.context_menu.borrow_mut();
5718 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5719 return f(None);
5720 };
5721 if completions_menu.id != id {
5722 return f(None);
5723 }
5724 f(Some(completions_menu))
5725 }
5726
5727 pub fn confirm_completion(
5728 &mut self,
5729 action: &ConfirmCompletion,
5730 window: &mut Window,
5731 cx: &mut Context<Self>,
5732 ) -> Option<Task<Result<()>>> {
5733 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5734 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5735 }
5736
5737 pub fn confirm_completion_insert(
5738 &mut self,
5739 _: &ConfirmCompletionInsert,
5740 window: &mut Window,
5741 cx: &mut Context<Self>,
5742 ) -> Option<Task<Result<()>>> {
5743 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5744 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5745 }
5746
5747 pub fn confirm_completion_replace(
5748 &mut self,
5749 _: &ConfirmCompletionReplace,
5750 window: &mut Window,
5751 cx: &mut Context<Self>,
5752 ) -> Option<Task<Result<()>>> {
5753 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5754 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5755 }
5756
5757 pub fn compose_completion(
5758 &mut self,
5759 action: &ComposeCompletion,
5760 window: &mut Window,
5761 cx: &mut Context<Self>,
5762 ) -> Option<Task<Result<()>>> {
5763 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5764 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5765 }
5766
5767 fn do_completion(
5768 &mut self,
5769 item_ix: Option<usize>,
5770 intent: CompletionIntent,
5771 window: &mut Window,
5772 cx: &mut Context<Editor>,
5773 ) -> Option<Task<Result<()>>> {
5774 use language::ToOffset as _;
5775
5776 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5777 else {
5778 return None;
5779 };
5780
5781 let candidate_id = {
5782 let entries = completions_menu.entries.borrow();
5783 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5784 if self.show_edit_predictions_in_menu() {
5785 self.discard_edit_prediction(true, cx);
5786 }
5787 mat.candidate_id
5788 };
5789
5790 let completion = completions_menu
5791 .completions
5792 .borrow()
5793 .get(candidate_id)?
5794 .clone();
5795 cx.stop_propagation();
5796
5797 let buffer_handle = completions_menu.buffer.clone();
5798
5799 let CompletionEdit {
5800 new_text,
5801 snippet,
5802 replace_range,
5803 } = process_completion_for_edit(
5804 &completion,
5805 intent,
5806 &buffer_handle,
5807 &completions_menu.initial_position.text_anchor,
5808 cx,
5809 );
5810
5811 let buffer = buffer_handle.read(cx);
5812 let snapshot = self.buffer.read(cx).snapshot(cx);
5813 let newest_anchor = self.selections.newest_anchor();
5814 let replace_range_multibuffer = {
5815 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5816 let multibuffer_anchor = snapshot
5817 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5818 .unwrap()
5819 ..snapshot
5820 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5821 .unwrap();
5822 multibuffer_anchor.start.to_offset(&snapshot)
5823 ..multibuffer_anchor.end.to_offset(&snapshot)
5824 };
5825 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5826 return None;
5827 }
5828
5829 let old_text = buffer
5830 .text_for_range(replace_range.clone())
5831 .collect::<String>();
5832 let lookbehind = newest_anchor
5833 .start
5834 .text_anchor
5835 .to_offset(buffer)
5836 .saturating_sub(replace_range.start);
5837 let lookahead = replace_range
5838 .end
5839 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5840 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5841 let suffix = &old_text[lookbehind.min(old_text.len())..];
5842
5843 let selections = self.selections.all::<usize>(cx);
5844 let mut ranges = Vec::new();
5845 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5846
5847 for selection in &selections {
5848 let range = if selection.id == newest_anchor.id {
5849 replace_range_multibuffer.clone()
5850 } else {
5851 let mut range = selection.range();
5852
5853 // if prefix is present, don't duplicate it
5854 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5855 range.start = range.start.saturating_sub(lookbehind);
5856
5857 // if suffix is also present, mimic the newest cursor and replace it
5858 if selection.id != newest_anchor.id
5859 && snapshot.contains_str_at(range.end, suffix)
5860 {
5861 range.end += lookahead;
5862 }
5863 }
5864 range
5865 };
5866
5867 ranges.push(range.clone());
5868
5869 if !self.linked_edit_ranges.is_empty() {
5870 let start_anchor = snapshot.anchor_before(range.start);
5871 let end_anchor = snapshot.anchor_after(range.end);
5872 if let Some(ranges) = self
5873 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5874 {
5875 for (buffer, edits) in ranges {
5876 linked_edits
5877 .entry(buffer.clone())
5878 .or_default()
5879 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5880 }
5881 }
5882 }
5883 }
5884
5885 let common_prefix_len = old_text
5886 .chars()
5887 .zip(new_text.chars())
5888 .take_while(|(a, b)| a == b)
5889 .map(|(a, _)| a.len_utf8())
5890 .sum::<usize>();
5891
5892 cx.emit(EditorEvent::InputHandled {
5893 utf16_range_to_replace: None,
5894 text: new_text[common_prefix_len..].into(),
5895 });
5896
5897 self.transact(window, cx, |editor, window, cx| {
5898 if let Some(mut snippet) = snippet {
5899 snippet.text = new_text.to_string();
5900 editor
5901 .insert_snippet(&ranges, snippet, window, cx)
5902 .log_err();
5903 } else {
5904 editor.buffer.update(cx, |multi_buffer, cx| {
5905 let auto_indent = match completion.insert_text_mode {
5906 Some(InsertTextMode::AS_IS) => None,
5907 _ => editor.autoindent_mode.clone(),
5908 };
5909 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5910 multi_buffer.edit(edits, auto_indent, cx);
5911 });
5912 }
5913 for (buffer, edits) in linked_edits {
5914 buffer.update(cx, |buffer, cx| {
5915 let snapshot = buffer.snapshot();
5916 let edits = edits
5917 .into_iter()
5918 .map(|(range, text)| {
5919 use text::ToPoint as TP;
5920 let end_point = TP::to_point(&range.end, &snapshot);
5921 let start_point = TP::to_point(&range.start, &snapshot);
5922 (start_point..end_point, text)
5923 })
5924 .sorted_by_key(|(range, _)| range.start);
5925 buffer.edit(edits, None, cx);
5926 })
5927 }
5928
5929 editor.refresh_edit_prediction(true, false, window, cx);
5930 });
5931 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5932
5933 let show_new_completions_on_confirm = completion
5934 .confirm
5935 .as_ref()
5936 .map_or(false, |confirm| confirm(intent, window, cx));
5937 if show_new_completions_on_confirm {
5938 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5939 }
5940
5941 let provider = self.completion_provider.as_ref()?;
5942 drop(completion);
5943 let apply_edits = provider.apply_additional_edits_for_completion(
5944 buffer_handle,
5945 completions_menu.completions.clone(),
5946 candidate_id,
5947 true,
5948 cx,
5949 );
5950
5951 let editor_settings = EditorSettings::get_global(cx);
5952 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5953 // After the code completion is finished, users often want to know what signatures are needed.
5954 // so we should automatically call signature_help
5955 self.show_signature_help(&ShowSignatureHelp, window, cx);
5956 }
5957
5958 Some(cx.foreground_executor().spawn(async move {
5959 apply_edits.await?;
5960 Ok(())
5961 }))
5962 }
5963
5964 pub fn toggle_code_actions(
5965 &mut self,
5966 action: &ToggleCodeActions,
5967 window: &mut Window,
5968 cx: &mut Context<Self>,
5969 ) {
5970 let quick_launch = action.quick_launch;
5971 let mut context_menu = self.context_menu.borrow_mut();
5972 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5973 if code_actions.deployed_from == action.deployed_from {
5974 // Toggle if we're selecting the same one
5975 *context_menu = None;
5976 cx.notify();
5977 return;
5978 } else {
5979 // Otherwise, clear it and start a new one
5980 *context_menu = None;
5981 cx.notify();
5982 }
5983 }
5984 drop(context_menu);
5985 let snapshot = self.snapshot(window, cx);
5986 let deployed_from = action.deployed_from.clone();
5987 let action = action.clone();
5988 self.completion_tasks.clear();
5989 self.discard_edit_prediction(false, cx);
5990
5991 let multibuffer_point = match &action.deployed_from {
5992 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5993 DisplayPoint::new(*row, 0).to_point(&snapshot)
5994 }
5995 _ => self.selections.newest::<Point>(cx).head(),
5996 };
5997 let Some((buffer, buffer_row)) = snapshot
5998 .buffer_snapshot
5999 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6000 .and_then(|(buffer_snapshot, range)| {
6001 self.buffer()
6002 .read(cx)
6003 .buffer(buffer_snapshot.remote_id())
6004 .map(|buffer| (buffer, range.start.row))
6005 })
6006 else {
6007 return;
6008 };
6009 let buffer_id = buffer.read(cx).remote_id();
6010 let tasks = self
6011 .tasks
6012 .get(&(buffer_id, buffer_row))
6013 .map(|t| Arc::new(t.to_owned()));
6014
6015 if !self.focus_handle.is_focused(window) {
6016 return;
6017 }
6018 let project = self.project.clone();
6019
6020 let code_actions_task = match deployed_from {
6021 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6022 _ => self.code_actions(buffer_row, window, cx),
6023 };
6024
6025 let runnable_task = match deployed_from {
6026 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6027 _ => {
6028 let mut task_context_task = Task::ready(None);
6029 if let Some(tasks) = &tasks {
6030 if let Some(project) = project {
6031 task_context_task =
6032 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6033 }
6034 }
6035
6036 cx.spawn_in(window, {
6037 let buffer = buffer.clone();
6038 async move |editor, cx| {
6039 let task_context = task_context_task.await;
6040
6041 let resolved_tasks =
6042 tasks
6043 .zip(task_context.clone())
6044 .map(|(tasks, task_context)| ResolvedTasks {
6045 templates: tasks.resolve(&task_context).collect(),
6046 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6047 multibuffer_point.row,
6048 tasks.column,
6049 )),
6050 });
6051 let debug_scenarios = editor
6052 .update(cx, |editor, cx| {
6053 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6054 })?
6055 .await;
6056 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6057 }
6058 })
6059 }
6060 };
6061
6062 cx.spawn_in(window, async move |editor, cx| {
6063 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6064 let code_actions = code_actions_task.await;
6065 let spawn_straight_away = quick_launch
6066 && resolved_tasks
6067 .as_ref()
6068 .map_or(false, |tasks| tasks.templates.len() == 1)
6069 && code_actions
6070 .as_ref()
6071 .map_or(true, |actions| actions.is_empty())
6072 && debug_scenarios.is_empty();
6073
6074 editor.update_in(cx, |editor, window, cx| {
6075 crate::hover_popover::hide_hover(editor, cx);
6076 let actions = CodeActionContents::new(
6077 resolved_tasks,
6078 code_actions,
6079 debug_scenarios,
6080 task_context.unwrap_or_default(),
6081 );
6082
6083 // Don't show the menu if there are no actions available
6084 if actions.is_empty() {
6085 cx.notify();
6086 return Task::ready(Ok(()));
6087 }
6088
6089 *editor.context_menu.borrow_mut() =
6090 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6091 buffer,
6092 actions,
6093 selected_item: Default::default(),
6094 scroll_handle: UniformListScrollHandle::default(),
6095 deployed_from,
6096 }));
6097 cx.notify();
6098 if spawn_straight_away {
6099 if let Some(task) = editor.confirm_code_action(
6100 &ConfirmCodeAction { item_ix: Some(0) },
6101 window,
6102 cx,
6103 ) {
6104 return task;
6105 }
6106 }
6107
6108 Task::ready(Ok(()))
6109 })
6110 })
6111 .detach_and_log_err(cx);
6112 }
6113
6114 fn debug_scenarios(
6115 &mut self,
6116 resolved_tasks: &Option<ResolvedTasks>,
6117 buffer: &Entity<Buffer>,
6118 cx: &mut App,
6119 ) -> Task<Vec<task::DebugScenario>> {
6120 maybe!({
6121 let project = self.project.as_ref()?;
6122 let dap_store = project.read(cx).dap_store();
6123 let mut scenarios = vec![];
6124 let resolved_tasks = resolved_tasks.as_ref()?;
6125 let buffer = buffer.read(cx);
6126 let language = buffer.language()?;
6127 let file = buffer.file();
6128 let debug_adapter = language_settings(language.name().into(), file, cx)
6129 .debuggers
6130 .first()
6131 .map(SharedString::from)
6132 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6133
6134 dap_store.update(cx, |dap_store, cx| {
6135 for (_, task) in &resolved_tasks.templates {
6136 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6137 task.original_task().clone(),
6138 debug_adapter.clone().into(),
6139 task.display_label().to_owned().into(),
6140 cx,
6141 );
6142 scenarios.push(maybe_scenario);
6143 }
6144 });
6145 Some(cx.background_spawn(async move {
6146 let scenarios = futures::future::join_all(scenarios)
6147 .await
6148 .into_iter()
6149 .flatten()
6150 .collect::<Vec<_>>();
6151 scenarios
6152 }))
6153 })
6154 .unwrap_or_else(|| Task::ready(vec![]))
6155 }
6156
6157 fn code_actions(
6158 &mut self,
6159 buffer_row: u32,
6160 window: &mut Window,
6161 cx: &mut Context<Self>,
6162 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6163 let mut task = self.code_actions_task.take();
6164 cx.spawn_in(window, async move |editor, cx| {
6165 while let Some(prev_task) = task {
6166 prev_task.await.log_err();
6167 task = editor
6168 .update(cx, |this, _| this.code_actions_task.take())
6169 .ok()?;
6170 }
6171
6172 editor
6173 .update(cx, |editor, cx| {
6174 editor
6175 .available_code_actions
6176 .clone()
6177 .and_then(|(location, code_actions)| {
6178 let snapshot = location.buffer.read(cx).snapshot();
6179 let point_range = location.range.to_point(&snapshot);
6180 let point_range = point_range.start.row..=point_range.end.row;
6181 if point_range.contains(&buffer_row) {
6182 Some(code_actions)
6183 } else {
6184 None
6185 }
6186 })
6187 })
6188 .ok()
6189 .flatten()
6190 })
6191 }
6192
6193 pub fn confirm_code_action(
6194 &mut self,
6195 action: &ConfirmCodeAction,
6196 window: &mut Window,
6197 cx: &mut Context<Self>,
6198 ) -> Option<Task<Result<()>>> {
6199 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6200
6201 let actions_menu =
6202 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6203 menu
6204 } else {
6205 return None;
6206 };
6207
6208 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6209 let action = actions_menu.actions.get(action_ix)?;
6210 let title = action.label();
6211 let buffer = actions_menu.buffer;
6212 let workspace = self.workspace()?;
6213
6214 match action {
6215 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6216 workspace.update(cx, |workspace, cx| {
6217 workspace.schedule_resolved_task(
6218 task_source_kind,
6219 resolved_task,
6220 false,
6221 window,
6222 cx,
6223 );
6224
6225 Some(Task::ready(Ok(())))
6226 })
6227 }
6228 CodeActionsItem::CodeAction {
6229 excerpt_id,
6230 action,
6231 provider,
6232 } => {
6233 let apply_code_action =
6234 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6235 let workspace = workspace.downgrade();
6236 Some(cx.spawn_in(window, async move |editor, cx| {
6237 let project_transaction = apply_code_action.await?;
6238 Self::open_project_transaction(
6239 &editor,
6240 workspace,
6241 project_transaction,
6242 title,
6243 cx,
6244 )
6245 .await
6246 }))
6247 }
6248 CodeActionsItem::DebugScenario(scenario) => {
6249 let context = actions_menu.actions.context.clone();
6250
6251 workspace.update(cx, |workspace, cx| {
6252 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6253 workspace.start_debug_session(
6254 scenario,
6255 context,
6256 Some(buffer),
6257 None,
6258 window,
6259 cx,
6260 );
6261 });
6262 Some(Task::ready(Ok(())))
6263 }
6264 }
6265 }
6266
6267 pub async fn open_project_transaction(
6268 this: &WeakEntity<Editor>,
6269 workspace: WeakEntity<Workspace>,
6270 transaction: ProjectTransaction,
6271 title: String,
6272 cx: &mut AsyncWindowContext,
6273 ) -> Result<()> {
6274 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6275 cx.update(|_, cx| {
6276 entries.sort_unstable_by_key(|(buffer, _)| {
6277 buffer.read(cx).file().map(|f| f.path().clone())
6278 });
6279 })?;
6280
6281 // If the project transaction's edits are all contained within this editor, then
6282 // avoid opening a new editor to display them.
6283
6284 if let Some((buffer, transaction)) = entries.first() {
6285 if entries.len() == 1 {
6286 let excerpt = this.update(cx, |editor, cx| {
6287 editor
6288 .buffer()
6289 .read(cx)
6290 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6291 })?;
6292 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6293 if excerpted_buffer == *buffer {
6294 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6295 let excerpt_range = excerpt_range.to_offset(buffer);
6296 buffer
6297 .edited_ranges_for_transaction::<usize>(transaction)
6298 .all(|range| {
6299 excerpt_range.start <= range.start
6300 && excerpt_range.end >= range.end
6301 })
6302 })?;
6303
6304 if all_edits_within_excerpt {
6305 return Ok(());
6306 }
6307 }
6308 }
6309 }
6310 } else {
6311 return Ok(());
6312 }
6313
6314 let mut ranges_to_highlight = Vec::new();
6315 let excerpt_buffer = cx.new(|cx| {
6316 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6317 for (buffer_handle, transaction) in &entries {
6318 let edited_ranges = buffer_handle
6319 .read(cx)
6320 .edited_ranges_for_transaction::<Point>(transaction)
6321 .collect::<Vec<_>>();
6322 let (ranges, _) = multibuffer.set_excerpts_for_path(
6323 PathKey::for_buffer(buffer_handle, cx),
6324 buffer_handle.clone(),
6325 edited_ranges,
6326 DEFAULT_MULTIBUFFER_CONTEXT,
6327 cx,
6328 );
6329
6330 ranges_to_highlight.extend(ranges);
6331 }
6332 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6333 multibuffer
6334 })?;
6335
6336 workspace.update_in(cx, |workspace, window, cx| {
6337 let project = workspace.project().clone();
6338 let editor =
6339 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6340 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6341 editor.update(cx, |editor, cx| {
6342 editor.highlight_background::<Self>(
6343 &ranges_to_highlight,
6344 |theme| theme.colors().editor_highlighted_line_background,
6345 cx,
6346 );
6347 });
6348 })?;
6349
6350 Ok(())
6351 }
6352
6353 pub fn clear_code_action_providers(&mut self) {
6354 self.code_action_providers.clear();
6355 self.available_code_actions.take();
6356 }
6357
6358 pub fn add_code_action_provider(
6359 &mut self,
6360 provider: Rc<dyn CodeActionProvider>,
6361 window: &mut Window,
6362 cx: &mut Context<Self>,
6363 ) {
6364 if self
6365 .code_action_providers
6366 .iter()
6367 .any(|existing_provider| existing_provider.id() == provider.id())
6368 {
6369 return;
6370 }
6371
6372 self.code_action_providers.push(provider);
6373 self.refresh_code_actions(window, cx);
6374 }
6375
6376 pub fn remove_code_action_provider(
6377 &mut self,
6378 id: Arc<str>,
6379 window: &mut Window,
6380 cx: &mut Context<Self>,
6381 ) {
6382 self.code_action_providers
6383 .retain(|provider| provider.id() != id);
6384 self.refresh_code_actions(window, cx);
6385 }
6386
6387 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6388 !self.code_action_providers.is_empty()
6389 && EditorSettings::get_global(cx).toolbar.code_actions
6390 }
6391
6392 pub fn has_available_code_actions(&self) -> bool {
6393 self.available_code_actions
6394 .as_ref()
6395 .is_some_and(|(_, actions)| !actions.is_empty())
6396 }
6397
6398 fn render_inline_code_actions(
6399 &self,
6400 icon_size: ui::IconSize,
6401 display_row: DisplayRow,
6402 is_active: bool,
6403 cx: &mut Context<Self>,
6404 ) -> AnyElement {
6405 let show_tooltip = !self.context_menu_visible();
6406 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6407 .icon_size(icon_size)
6408 .shape(ui::IconButtonShape::Square)
6409 .icon_color(ui::Color::Hidden)
6410 .toggle_state(is_active)
6411 .when(show_tooltip, |this| {
6412 this.tooltip({
6413 let focus_handle = self.focus_handle.clone();
6414 move |window, cx| {
6415 Tooltip::for_action_in(
6416 "Toggle Code Actions",
6417 &ToggleCodeActions {
6418 deployed_from: None,
6419 quick_launch: false,
6420 },
6421 &focus_handle,
6422 window,
6423 cx,
6424 )
6425 }
6426 })
6427 })
6428 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6429 window.focus(&editor.focus_handle(cx));
6430 editor.toggle_code_actions(
6431 &crate::actions::ToggleCodeActions {
6432 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6433 display_row,
6434 )),
6435 quick_launch: false,
6436 },
6437 window,
6438 cx,
6439 );
6440 }))
6441 .into_any_element()
6442 }
6443
6444 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6445 &self.context_menu
6446 }
6447
6448 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6449 let newest_selection = self.selections.newest_anchor().clone();
6450 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6451 let buffer = self.buffer.read(cx);
6452 if newest_selection.head().diff_base_anchor.is_some() {
6453 return None;
6454 }
6455 let (start_buffer, start) =
6456 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6457 let (end_buffer, end) =
6458 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6459 if start_buffer != end_buffer {
6460 return None;
6461 }
6462
6463 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6464 cx.background_executor()
6465 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6466 .await;
6467
6468 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6469 let providers = this.code_action_providers.clone();
6470 let tasks = this
6471 .code_action_providers
6472 .iter()
6473 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6474 .collect::<Vec<_>>();
6475 (providers, tasks)
6476 })?;
6477
6478 let mut actions = Vec::new();
6479 for (provider, provider_actions) in
6480 providers.into_iter().zip(future::join_all(tasks).await)
6481 {
6482 if let Some(provider_actions) = provider_actions.log_err() {
6483 actions.extend(provider_actions.into_iter().map(|action| {
6484 AvailableCodeAction {
6485 excerpt_id: newest_selection.start.excerpt_id,
6486 action,
6487 provider: provider.clone(),
6488 }
6489 }));
6490 }
6491 }
6492
6493 this.update(cx, |this, cx| {
6494 this.available_code_actions = if actions.is_empty() {
6495 None
6496 } else {
6497 Some((
6498 Location {
6499 buffer: start_buffer,
6500 range: start..end,
6501 },
6502 actions.into(),
6503 ))
6504 };
6505 cx.notify();
6506 })
6507 }));
6508 None
6509 }
6510
6511 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6512 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6513 self.show_git_blame_inline = false;
6514
6515 self.show_git_blame_inline_delay_task =
6516 Some(cx.spawn_in(window, async move |this, cx| {
6517 cx.background_executor().timer(delay).await;
6518
6519 this.update(cx, |this, cx| {
6520 this.show_git_blame_inline = true;
6521 cx.notify();
6522 })
6523 .log_err();
6524 }));
6525 }
6526 }
6527
6528 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6529 let snapshot = self.snapshot(window, cx);
6530 let cursor = self.selections.newest::<Point>(cx).head();
6531 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6532 else {
6533 return;
6534 };
6535
6536 let Some(blame) = self.blame.as_ref() else {
6537 return;
6538 };
6539
6540 let row_info = RowInfo {
6541 buffer_id: Some(buffer.remote_id()),
6542 buffer_row: Some(point.row),
6543 ..Default::default()
6544 };
6545 let Some(blame_entry) = blame
6546 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6547 .flatten()
6548 else {
6549 return;
6550 };
6551
6552 let anchor = self.selections.newest_anchor().head();
6553 let position = self.to_pixel_point(anchor, &snapshot, window);
6554 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6555 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6556 };
6557 }
6558
6559 fn show_blame_popover(
6560 &mut self,
6561 blame_entry: &BlameEntry,
6562 position: gpui::Point<Pixels>,
6563 ignore_timeout: bool,
6564 cx: &mut Context<Self>,
6565 ) {
6566 if let Some(state) = &mut self.inline_blame_popover {
6567 state.hide_task.take();
6568 } else {
6569 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6570 let blame_entry = blame_entry.clone();
6571 let show_task = cx.spawn(async move |editor, cx| {
6572 if !ignore_timeout {
6573 cx.background_executor()
6574 .timer(std::time::Duration::from_millis(blame_popover_delay))
6575 .await;
6576 }
6577 editor
6578 .update(cx, |editor, cx| {
6579 editor.inline_blame_popover_show_task.take();
6580 let Some(blame) = editor.blame.as_ref() else {
6581 return;
6582 };
6583 let blame = blame.read(cx);
6584 let details = blame.details_for_entry(&blame_entry);
6585 let markdown = cx.new(|cx| {
6586 Markdown::new(
6587 details
6588 .as_ref()
6589 .map(|message| message.message.clone())
6590 .unwrap_or_default(),
6591 None,
6592 None,
6593 cx,
6594 )
6595 });
6596 editor.inline_blame_popover = Some(InlineBlamePopover {
6597 position,
6598 hide_task: None,
6599 popover_bounds: None,
6600 popover_state: InlineBlamePopoverState {
6601 scroll_handle: ScrollHandle::new(),
6602 commit_message: details,
6603 markdown,
6604 },
6605 keyboard_grace: ignore_timeout,
6606 });
6607 cx.notify();
6608 })
6609 .ok();
6610 });
6611 self.inline_blame_popover_show_task = Some(show_task);
6612 }
6613 }
6614
6615 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6616 self.inline_blame_popover_show_task.take();
6617 if let Some(state) = &mut self.inline_blame_popover {
6618 let hide_task = cx.spawn(async move |editor, cx| {
6619 cx.background_executor()
6620 .timer(std::time::Duration::from_millis(100))
6621 .await;
6622 editor
6623 .update(cx, |editor, cx| {
6624 editor.inline_blame_popover.take();
6625 cx.notify();
6626 })
6627 .ok();
6628 });
6629 state.hide_task = Some(hide_task);
6630 }
6631 }
6632
6633 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6634 if self.pending_rename.is_some() {
6635 return None;
6636 }
6637
6638 let provider = self.semantics_provider.clone()?;
6639 let buffer = self.buffer.read(cx);
6640 let newest_selection = self.selections.newest_anchor().clone();
6641 let cursor_position = newest_selection.head();
6642 let (cursor_buffer, cursor_buffer_position) =
6643 buffer.text_anchor_for_position(cursor_position, cx)?;
6644 let (tail_buffer, tail_buffer_position) =
6645 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6646 if cursor_buffer != tail_buffer {
6647 return None;
6648 }
6649
6650 let snapshot = cursor_buffer.read(cx).snapshot();
6651 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6652 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6653 if start_word_range != end_word_range {
6654 self.document_highlights_task.take();
6655 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6656 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6657 return None;
6658 }
6659
6660 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6661 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6662 cx.background_executor()
6663 .timer(Duration::from_millis(debounce))
6664 .await;
6665
6666 let highlights = if let Some(highlights) = cx
6667 .update(|cx| {
6668 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6669 })
6670 .ok()
6671 .flatten()
6672 {
6673 highlights.await.log_err()
6674 } else {
6675 None
6676 };
6677
6678 if let Some(highlights) = highlights {
6679 this.update(cx, |this, cx| {
6680 if this.pending_rename.is_some() {
6681 return;
6682 }
6683
6684 let buffer_id = cursor_position.buffer_id;
6685 let buffer = this.buffer.read(cx);
6686 if !buffer
6687 .text_anchor_for_position(cursor_position, cx)
6688 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6689 {
6690 return;
6691 }
6692
6693 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6694 let mut write_ranges = Vec::new();
6695 let mut read_ranges = Vec::new();
6696 for highlight in highlights {
6697 for (excerpt_id, excerpt_range) in
6698 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6699 {
6700 let start = highlight
6701 .range
6702 .start
6703 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6704 let end = highlight
6705 .range
6706 .end
6707 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6708 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6709 continue;
6710 }
6711
6712 let range = Anchor {
6713 buffer_id,
6714 excerpt_id,
6715 text_anchor: start,
6716 diff_base_anchor: None,
6717 }..Anchor {
6718 buffer_id,
6719 excerpt_id,
6720 text_anchor: end,
6721 diff_base_anchor: None,
6722 };
6723 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6724 write_ranges.push(range);
6725 } else {
6726 read_ranges.push(range);
6727 }
6728 }
6729 }
6730
6731 this.highlight_background::<DocumentHighlightRead>(
6732 &read_ranges,
6733 |theme| theme.colors().editor_document_highlight_read_background,
6734 cx,
6735 );
6736 this.highlight_background::<DocumentHighlightWrite>(
6737 &write_ranges,
6738 |theme| theme.colors().editor_document_highlight_write_background,
6739 cx,
6740 );
6741 cx.notify();
6742 })
6743 .log_err();
6744 }
6745 }));
6746 None
6747 }
6748
6749 fn prepare_highlight_query_from_selection(
6750 &mut self,
6751 cx: &mut Context<Editor>,
6752 ) -> Option<(String, Range<Anchor>)> {
6753 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6754 return None;
6755 }
6756 if !EditorSettings::get_global(cx).selection_highlight {
6757 return None;
6758 }
6759 if self.selections.count() != 1 || self.selections.line_mode {
6760 return None;
6761 }
6762 let selection = self.selections.newest::<Point>(cx);
6763 if selection.is_empty() || selection.start.row != selection.end.row {
6764 return None;
6765 }
6766 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6767 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6768 let query = multi_buffer_snapshot
6769 .text_for_range(selection_anchor_range.clone())
6770 .collect::<String>();
6771 if query.trim().is_empty() {
6772 return None;
6773 }
6774 Some((query, selection_anchor_range))
6775 }
6776
6777 fn update_selection_occurrence_highlights(
6778 &mut self,
6779 query_text: String,
6780 query_range: Range<Anchor>,
6781 multi_buffer_range_to_query: Range<Point>,
6782 use_debounce: bool,
6783 window: &mut Window,
6784 cx: &mut Context<Editor>,
6785 ) -> Task<()> {
6786 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6787 cx.spawn_in(window, async move |editor, cx| {
6788 if use_debounce {
6789 cx.background_executor()
6790 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6791 .await;
6792 }
6793 let match_task = cx.background_spawn(async move {
6794 let buffer_ranges = multi_buffer_snapshot
6795 .range_to_buffer_ranges(multi_buffer_range_to_query)
6796 .into_iter()
6797 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6798 let mut match_ranges = Vec::new();
6799 let Ok(regex) = project::search::SearchQuery::text(
6800 query_text.clone(),
6801 false,
6802 false,
6803 false,
6804 Default::default(),
6805 Default::default(),
6806 false,
6807 None,
6808 ) else {
6809 return Vec::default();
6810 };
6811 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6812 match_ranges.extend(
6813 regex
6814 .search(&buffer_snapshot, Some(search_range.clone()))
6815 .await
6816 .into_iter()
6817 .filter_map(|match_range| {
6818 let match_start = buffer_snapshot
6819 .anchor_after(search_range.start + match_range.start);
6820 let match_end = buffer_snapshot
6821 .anchor_before(search_range.start + match_range.end);
6822 let match_anchor_range = Anchor::range_in_buffer(
6823 excerpt_id,
6824 buffer_snapshot.remote_id(),
6825 match_start..match_end,
6826 );
6827 (match_anchor_range != query_range).then_some(match_anchor_range)
6828 }),
6829 );
6830 }
6831 match_ranges
6832 });
6833 let match_ranges = match_task.await;
6834 editor
6835 .update_in(cx, |editor, _, cx| {
6836 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6837 if !match_ranges.is_empty() {
6838 editor.highlight_background::<SelectedTextHighlight>(
6839 &match_ranges,
6840 |theme| theme.colors().editor_document_highlight_bracket_background,
6841 cx,
6842 )
6843 }
6844 })
6845 .log_err();
6846 })
6847 }
6848
6849 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6850 struct NewlineFold;
6851 let type_id = std::any::TypeId::of::<NewlineFold>();
6852 if !self.mode.is_single_line() {
6853 return;
6854 }
6855 let snapshot = self.snapshot(window, cx);
6856 if snapshot.buffer_snapshot.max_point().row == 0 {
6857 return;
6858 }
6859 let task = cx.background_spawn(async move {
6860 let new_newlines = snapshot
6861 .buffer_chars_at(0)
6862 .filter_map(|(c, i)| {
6863 if c == '\n' {
6864 Some(
6865 snapshot.buffer_snapshot.anchor_after(i)
6866 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6867 )
6868 } else {
6869 None
6870 }
6871 })
6872 .collect::<Vec<_>>();
6873 let existing_newlines = snapshot
6874 .folds_in_range(0..snapshot.buffer_snapshot.len())
6875 .filter_map(|fold| {
6876 if fold.placeholder.type_tag == Some(type_id) {
6877 Some(fold.range.start..fold.range.end)
6878 } else {
6879 None
6880 }
6881 })
6882 .collect::<Vec<_>>();
6883
6884 (new_newlines, existing_newlines)
6885 });
6886 self.folding_newlines = cx.spawn(async move |this, cx| {
6887 let (new_newlines, existing_newlines) = task.await;
6888 if new_newlines == existing_newlines {
6889 return;
6890 }
6891 let placeholder = FoldPlaceholder {
6892 render: Arc::new(move |_, _, cx| {
6893 div()
6894 .bg(cx.theme().status().hint_background)
6895 .border_b_1()
6896 .size_full()
6897 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6898 .border_color(cx.theme().status().hint)
6899 .child("\\n")
6900 .into_any()
6901 }),
6902 constrain_width: false,
6903 merge_adjacent: false,
6904 type_tag: Some(type_id),
6905 };
6906 let creases = new_newlines
6907 .into_iter()
6908 .map(|range| Crease::simple(range, placeholder.clone()))
6909 .collect();
6910 this.update(cx, |this, cx| {
6911 this.display_map.update(cx, |display_map, cx| {
6912 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6913 display_map.fold(creases, cx);
6914 });
6915 })
6916 .ok();
6917 });
6918 }
6919
6920 fn refresh_selected_text_highlights(
6921 &mut self,
6922 on_buffer_edit: bool,
6923 window: &mut Window,
6924 cx: &mut Context<Editor>,
6925 ) {
6926 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6927 else {
6928 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6929 self.quick_selection_highlight_task.take();
6930 self.debounced_selection_highlight_task.take();
6931 return;
6932 };
6933 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6934 if on_buffer_edit
6935 || self
6936 .quick_selection_highlight_task
6937 .as_ref()
6938 .map_or(true, |(prev_anchor_range, _)| {
6939 prev_anchor_range != &query_range
6940 })
6941 {
6942 let multi_buffer_visible_start = self
6943 .scroll_manager
6944 .anchor()
6945 .anchor
6946 .to_point(&multi_buffer_snapshot);
6947 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6948 multi_buffer_visible_start
6949 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6950 Bias::Left,
6951 );
6952 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6953 self.quick_selection_highlight_task = Some((
6954 query_range.clone(),
6955 self.update_selection_occurrence_highlights(
6956 query_text.clone(),
6957 query_range.clone(),
6958 multi_buffer_visible_range,
6959 false,
6960 window,
6961 cx,
6962 ),
6963 ));
6964 }
6965 if on_buffer_edit
6966 || self
6967 .debounced_selection_highlight_task
6968 .as_ref()
6969 .map_or(true, |(prev_anchor_range, _)| {
6970 prev_anchor_range != &query_range
6971 })
6972 {
6973 let multi_buffer_start = multi_buffer_snapshot
6974 .anchor_before(0)
6975 .to_point(&multi_buffer_snapshot);
6976 let multi_buffer_end = multi_buffer_snapshot
6977 .anchor_after(multi_buffer_snapshot.len())
6978 .to_point(&multi_buffer_snapshot);
6979 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6980 self.debounced_selection_highlight_task = Some((
6981 query_range.clone(),
6982 self.update_selection_occurrence_highlights(
6983 query_text,
6984 query_range,
6985 multi_buffer_full_range,
6986 true,
6987 window,
6988 cx,
6989 ),
6990 ));
6991 }
6992 }
6993
6994 pub fn refresh_edit_prediction(
6995 &mut self,
6996 debounce: bool,
6997 user_requested: bool,
6998 window: &mut Window,
6999 cx: &mut Context<Self>,
7000 ) -> Option<()> {
7001 if DisableAiSettings::get_global(cx).disable_ai {
7002 return None;
7003 }
7004
7005 let provider = self.edit_prediction_provider()?;
7006 let cursor = self.selections.newest_anchor().head();
7007 let (buffer, cursor_buffer_position) =
7008 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7009
7010 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7011 self.discard_edit_prediction(false, cx);
7012 return None;
7013 }
7014
7015 if !user_requested
7016 && (!self.should_show_edit_predictions()
7017 || !self.is_focused(window)
7018 || buffer.read(cx).is_empty())
7019 {
7020 self.discard_edit_prediction(false, cx);
7021 return None;
7022 }
7023
7024 self.update_visible_edit_prediction(window, cx);
7025 provider.refresh(
7026 self.project.clone(),
7027 buffer,
7028 cursor_buffer_position,
7029 debounce,
7030 cx,
7031 );
7032 Some(())
7033 }
7034
7035 fn show_edit_predictions_in_menu(&self) -> bool {
7036 match self.edit_prediction_settings {
7037 EditPredictionSettings::Disabled => false,
7038 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7039 }
7040 }
7041
7042 pub fn edit_predictions_enabled(&self) -> bool {
7043 match self.edit_prediction_settings {
7044 EditPredictionSettings::Disabled => false,
7045 EditPredictionSettings::Enabled { .. } => true,
7046 }
7047 }
7048
7049 fn edit_prediction_requires_modifier(&self) -> bool {
7050 match self.edit_prediction_settings {
7051 EditPredictionSettings::Disabled => false,
7052 EditPredictionSettings::Enabled {
7053 preview_requires_modifier,
7054 ..
7055 } => preview_requires_modifier,
7056 }
7057 }
7058
7059 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7060 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7061 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7062 self.discard_edit_prediction(false, cx);
7063 } else {
7064 let selection = self.selections.newest_anchor();
7065 let cursor = selection.head();
7066
7067 if let Some((buffer, cursor_buffer_position)) =
7068 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7069 {
7070 self.edit_prediction_settings =
7071 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7072 }
7073 }
7074 }
7075
7076 fn edit_prediction_settings_at_position(
7077 &self,
7078 buffer: &Entity<Buffer>,
7079 buffer_position: language::Anchor,
7080 cx: &App,
7081 ) -> EditPredictionSettings {
7082 if !self.mode.is_full()
7083 || !self.show_edit_predictions_override.unwrap_or(true)
7084 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7085 {
7086 return EditPredictionSettings::Disabled;
7087 }
7088
7089 let buffer = buffer.read(cx);
7090
7091 let file = buffer.file();
7092
7093 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7094 return EditPredictionSettings::Disabled;
7095 };
7096
7097 let by_provider = matches!(
7098 self.menu_edit_predictions_policy,
7099 MenuEditPredictionsPolicy::ByProvider
7100 );
7101
7102 let show_in_menu = by_provider
7103 && self
7104 .edit_prediction_provider
7105 .as_ref()
7106 .map_or(false, |provider| {
7107 provider.provider.show_completions_in_menu()
7108 });
7109
7110 let preview_requires_modifier =
7111 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7112
7113 EditPredictionSettings::Enabled {
7114 show_in_menu,
7115 preview_requires_modifier,
7116 }
7117 }
7118
7119 fn should_show_edit_predictions(&self) -> bool {
7120 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7121 }
7122
7123 pub fn edit_prediction_preview_is_active(&self) -> bool {
7124 matches!(
7125 self.edit_prediction_preview,
7126 EditPredictionPreview::Active { .. }
7127 )
7128 }
7129
7130 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7131 let cursor = self.selections.newest_anchor().head();
7132 if let Some((buffer, cursor_position)) =
7133 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7134 {
7135 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7136 } else {
7137 false
7138 }
7139 }
7140
7141 pub fn supports_minimap(&self, cx: &App) -> bool {
7142 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7143 }
7144
7145 fn edit_predictions_enabled_in_buffer(
7146 &self,
7147 buffer: &Entity<Buffer>,
7148 buffer_position: language::Anchor,
7149 cx: &App,
7150 ) -> bool {
7151 maybe!({
7152 if self.read_only(cx) {
7153 return Some(false);
7154 }
7155 let provider = self.edit_prediction_provider()?;
7156 if !provider.is_enabled(&buffer, buffer_position, cx) {
7157 return Some(false);
7158 }
7159 let buffer = buffer.read(cx);
7160 let Some(file) = buffer.file() else {
7161 return Some(true);
7162 };
7163 let settings = all_language_settings(Some(file), cx);
7164 Some(settings.edit_predictions_enabled_for_file(file, cx))
7165 })
7166 .unwrap_or(false)
7167 }
7168
7169 fn cycle_edit_prediction(
7170 &mut self,
7171 direction: Direction,
7172 window: &mut Window,
7173 cx: &mut Context<Self>,
7174 ) -> Option<()> {
7175 let provider = self.edit_prediction_provider()?;
7176 let cursor = self.selections.newest_anchor().head();
7177 let (buffer, cursor_buffer_position) =
7178 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7179 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7180 return None;
7181 }
7182
7183 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7184 self.update_visible_edit_prediction(window, cx);
7185
7186 Some(())
7187 }
7188
7189 pub fn show_edit_prediction(
7190 &mut self,
7191 _: &ShowEditPrediction,
7192 window: &mut Window,
7193 cx: &mut Context<Self>,
7194 ) {
7195 if !self.has_active_edit_prediction() {
7196 self.refresh_edit_prediction(false, true, window, cx);
7197 return;
7198 }
7199
7200 self.update_visible_edit_prediction(window, cx);
7201 }
7202
7203 pub fn display_cursor_names(
7204 &mut self,
7205 _: &DisplayCursorNames,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) {
7209 self.show_cursor_names(window, cx);
7210 }
7211
7212 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7213 self.show_cursor_names = true;
7214 cx.notify();
7215 cx.spawn_in(window, async move |this, cx| {
7216 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7217 this.update(cx, |this, cx| {
7218 this.show_cursor_names = false;
7219 cx.notify()
7220 })
7221 .ok()
7222 })
7223 .detach();
7224 }
7225
7226 pub fn next_edit_prediction(
7227 &mut self,
7228 _: &NextEditPrediction,
7229 window: &mut Window,
7230 cx: &mut Context<Self>,
7231 ) {
7232 if self.has_active_edit_prediction() {
7233 self.cycle_edit_prediction(Direction::Next, window, cx);
7234 } else {
7235 let is_copilot_disabled = self
7236 .refresh_edit_prediction(false, true, window, cx)
7237 .is_none();
7238 if is_copilot_disabled {
7239 cx.propagate();
7240 }
7241 }
7242 }
7243
7244 pub fn previous_edit_prediction(
7245 &mut self,
7246 _: &PreviousEditPrediction,
7247 window: &mut Window,
7248 cx: &mut Context<Self>,
7249 ) {
7250 if self.has_active_edit_prediction() {
7251 self.cycle_edit_prediction(Direction::Prev, window, cx);
7252 } else {
7253 let is_copilot_disabled = self
7254 .refresh_edit_prediction(false, true, window, cx)
7255 .is_none();
7256 if is_copilot_disabled {
7257 cx.propagate();
7258 }
7259 }
7260 }
7261
7262 pub fn accept_edit_prediction(
7263 &mut self,
7264 _: &AcceptEditPrediction,
7265 window: &mut Window,
7266 cx: &mut Context<Self>,
7267 ) {
7268 if self.show_edit_predictions_in_menu() {
7269 self.hide_context_menu(window, cx);
7270 }
7271
7272 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7273 return;
7274 };
7275
7276 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7277
7278 match &active_edit_prediction.completion {
7279 EditPrediction::Move { target, .. } => {
7280 let target = *target;
7281
7282 if let Some(position_map) = &self.last_position_map {
7283 if position_map
7284 .visible_row_range
7285 .contains(&target.to_display_point(&position_map.snapshot).row())
7286 || !self.edit_prediction_requires_modifier()
7287 {
7288 self.unfold_ranges(&[target..target], true, false, cx);
7289 // Note that this is also done in vim's handler of the Tab action.
7290 self.change_selections(
7291 SelectionEffects::scroll(Autoscroll::newest()),
7292 window,
7293 cx,
7294 |selections| {
7295 selections.select_anchor_ranges([target..target]);
7296 },
7297 );
7298 self.clear_row_highlights::<EditPredictionPreview>();
7299
7300 self.edit_prediction_preview
7301 .set_previous_scroll_position(None);
7302 } else {
7303 self.edit_prediction_preview
7304 .set_previous_scroll_position(Some(
7305 position_map.snapshot.scroll_anchor,
7306 ));
7307
7308 self.highlight_rows::<EditPredictionPreview>(
7309 target..target,
7310 cx.theme().colors().editor_highlighted_line_background,
7311 RowHighlightOptions {
7312 autoscroll: true,
7313 ..Default::default()
7314 },
7315 cx,
7316 );
7317 self.request_autoscroll(Autoscroll::fit(), cx);
7318 }
7319 }
7320 }
7321 EditPrediction::Edit { edits, .. } => {
7322 if let Some(provider) = self.edit_prediction_provider() {
7323 provider.accept(cx);
7324 }
7325
7326 // Store the transaction ID and selections before applying the edit
7327 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7328
7329 let snapshot = self.buffer.read(cx).snapshot(cx);
7330 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7331
7332 self.buffer.update(cx, |buffer, cx| {
7333 buffer.edit(edits.iter().cloned(), None, cx)
7334 });
7335
7336 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7337 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7338 });
7339
7340 let selections = self.selections.disjoint_anchors();
7341 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7342 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7343 if has_new_transaction {
7344 self.selection_history
7345 .insert_transaction(transaction_id_now, selections);
7346 }
7347 }
7348
7349 self.update_visible_edit_prediction(window, cx);
7350 if self.active_edit_prediction.is_none() {
7351 self.refresh_edit_prediction(true, true, window, cx);
7352 }
7353
7354 cx.notify();
7355 }
7356 }
7357
7358 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7359 }
7360
7361 pub fn accept_partial_edit_prediction(
7362 &mut self,
7363 _: &AcceptPartialEditPrediction,
7364 window: &mut Window,
7365 cx: &mut Context<Self>,
7366 ) {
7367 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7368 return;
7369 };
7370 if self.selections.count() != 1 {
7371 return;
7372 }
7373
7374 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7375
7376 match &active_edit_prediction.completion {
7377 EditPrediction::Move { target, .. } => {
7378 let target = *target;
7379 self.change_selections(
7380 SelectionEffects::scroll(Autoscroll::newest()),
7381 window,
7382 cx,
7383 |selections| {
7384 selections.select_anchor_ranges([target..target]);
7385 },
7386 );
7387 }
7388 EditPrediction::Edit { edits, .. } => {
7389 // Find an insertion that starts at the cursor position.
7390 let snapshot = self.buffer.read(cx).snapshot(cx);
7391 let cursor_offset = self.selections.newest::<usize>(cx).head();
7392 let insertion = edits.iter().find_map(|(range, text)| {
7393 let range = range.to_offset(&snapshot);
7394 if range.is_empty() && range.start == cursor_offset {
7395 Some(text)
7396 } else {
7397 None
7398 }
7399 });
7400
7401 if let Some(text) = insertion {
7402 let mut partial_completion = text
7403 .chars()
7404 .by_ref()
7405 .take_while(|c| c.is_alphabetic())
7406 .collect::<String>();
7407 if partial_completion.is_empty() {
7408 partial_completion = text
7409 .chars()
7410 .by_ref()
7411 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7412 .collect::<String>();
7413 }
7414
7415 cx.emit(EditorEvent::InputHandled {
7416 utf16_range_to_replace: None,
7417 text: partial_completion.clone().into(),
7418 });
7419
7420 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7421
7422 self.refresh_edit_prediction(true, true, window, cx);
7423 cx.notify();
7424 } else {
7425 self.accept_edit_prediction(&Default::default(), window, cx);
7426 }
7427 }
7428 }
7429 }
7430
7431 fn discard_edit_prediction(
7432 &mut self,
7433 should_report_edit_prediction_event: bool,
7434 cx: &mut Context<Self>,
7435 ) -> bool {
7436 if should_report_edit_prediction_event {
7437 let completion_id = self
7438 .active_edit_prediction
7439 .as_ref()
7440 .and_then(|active_completion| active_completion.completion_id.clone());
7441
7442 self.report_edit_prediction_event(completion_id, false, cx);
7443 }
7444
7445 if let Some(provider) = self.edit_prediction_provider() {
7446 provider.discard(cx);
7447 }
7448
7449 self.take_active_edit_prediction(cx)
7450 }
7451
7452 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7453 let Some(provider) = self.edit_prediction_provider() else {
7454 return;
7455 };
7456
7457 let Some((_, buffer, _)) = self
7458 .buffer
7459 .read(cx)
7460 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7461 else {
7462 return;
7463 };
7464
7465 let extension = buffer
7466 .read(cx)
7467 .file()
7468 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7469
7470 let event_type = match accepted {
7471 true => "Edit Prediction Accepted",
7472 false => "Edit Prediction Discarded",
7473 };
7474 telemetry::event!(
7475 event_type,
7476 provider = provider.name(),
7477 prediction_id = id,
7478 suggestion_accepted = accepted,
7479 file_extension = extension,
7480 );
7481 }
7482
7483 pub fn has_active_edit_prediction(&self) -> bool {
7484 self.active_edit_prediction.is_some()
7485 }
7486
7487 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7488 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7489 return false;
7490 };
7491
7492 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7493 self.clear_highlights::<EditPredictionHighlight>(cx);
7494 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7495 true
7496 }
7497
7498 /// Returns true when we're displaying the edit prediction popover below the cursor
7499 /// like we are not previewing and the LSP autocomplete menu is visible
7500 /// or we are in `when_holding_modifier` mode.
7501 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7502 if self.edit_prediction_preview_is_active()
7503 || !self.show_edit_predictions_in_menu()
7504 || !self.edit_predictions_enabled()
7505 {
7506 return false;
7507 }
7508
7509 if self.has_visible_completions_menu() {
7510 return true;
7511 }
7512
7513 has_completion && self.edit_prediction_requires_modifier()
7514 }
7515
7516 fn handle_modifiers_changed(
7517 &mut self,
7518 modifiers: Modifiers,
7519 position_map: &PositionMap,
7520 window: &mut Window,
7521 cx: &mut Context<Self>,
7522 ) {
7523 if self.show_edit_predictions_in_menu() {
7524 self.update_edit_prediction_preview(&modifiers, window, cx);
7525 }
7526
7527 self.update_selection_mode(&modifiers, position_map, window, cx);
7528
7529 let mouse_position = window.mouse_position();
7530 if !position_map.text_hitbox.is_hovered(window) {
7531 return;
7532 }
7533
7534 self.update_hovered_link(
7535 position_map.point_for_position(mouse_position),
7536 &position_map.snapshot,
7537 modifiers,
7538 window,
7539 cx,
7540 )
7541 }
7542
7543 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7544 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7545 if invert {
7546 match multi_cursor_setting {
7547 MultiCursorModifier::Alt => modifiers.alt,
7548 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7549 }
7550 } else {
7551 match multi_cursor_setting {
7552 MultiCursorModifier::Alt => modifiers.secondary(),
7553 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7554 }
7555 }
7556 }
7557
7558 fn columnar_selection_mode(
7559 modifiers: &Modifiers,
7560 cx: &mut Context<Self>,
7561 ) -> Option<ColumnarMode> {
7562 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7563 if Self::multi_cursor_modifier(false, modifiers, cx) {
7564 Some(ColumnarMode::FromMouse)
7565 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7566 Some(ColumnarMode::FromSelection)
7567 } else {
7568 None
7569 }
7570 } else {
7571 None
7572 }
7573 }
7574
7575 fn update_selection_mode(
7576 &mut self,
7577 modifiers: &Modifiers,
7578 position_map: &PositionMap,
7579 window: &mut Window,
7580 cx: &mut Context<Self>,
7581 ) {
7582 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7583 return;
7584 };
7585 if self.selections.pending.is_none() {
7586 return;
7587 }
7588
7589 let mouse_position = window.mouse_position();
7590 let point_for_position = position_map.point_for_position(mouse_position);
7591 let position = point_for_position.previous_valid;
7592
7593 self.select(
7594 SelectPhase::BeginColumnar {
7595 position,
7596 reset: false,
7597 mode,
7598 goal_column: point_for_position.exact_unclipped.column(),
7599 },
7600 window,
7601 cx,
7602 );
7603 }
7604
7605 fn update_edit_prediction_preview(
7606 &mut self,
7607 modifiers: &Modifiers,
7608 window: &mut Window,
7609 cx: &mut Context<Self>,
7610 ) {
7611 let mut modifiers_held = false;
7612 if let Some(accept_keystroke) = self
7613 .accept_edit_prediction_keybind(false, window, cx)
7614 .keystroke()
7615 {
7616 modifiers_held = modifiers_held
7617 || (&accept_keystroke.modifiers == modifiers
7618 && accept_keystroke.modifiers.modified());
7619 };
7620 if let Some(accept_partial_keystroke) = self
7621 .accept_edit_prediction_keybind(true, window, cx)
7622 .keystroke()
7623 {
7624 modifiers_held = modifiers_held
7625 || (&accept_partial_keystroke.modifiers == modifiers
7626 && accept_partial_keystroke.modifiers.modified());
7627 }
7628
7629 if modifiers_held {
7630 if matches!(
7631 self.edit_prediction_preview,
7632 EditPredictionPreview::Inactive { .. }
7633 ) {
7634 self.edit_prediction_preview = EditPredictionPreview::Active {
7635 previous_scroll_position: None,
7636 since: Instant::now(),
7637 };
7638
7639 self.update_visible_edit_prediction(window, cx);
7640 cx.notify();
7641 }
7642 } else if let EditPredictionPreview::Active {
7643 previous_scroll_position,
7644 since,
7645 } = self.edit_prediction_preview
7646 {
7647 if let (Some(previous_scroll_position), Some(position_map)) =
7648 (previous_scroll_position, self.last_position_map.as_ref())
7649 {
7650 self.set_scroll_position(
7651 previous_scroll_position
7652 .scroll_position(&position_map.snapshot.display_snapshot),
7653 window,
7654 cx,
7655 );
7656 }
7657
7658 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7659 released_too_fast: since.elapsed() < Duration::from_millis(200),
7660 };
7661 self.clear_row_highlights::<EditPredictionPreview>();
7662 self.update_visible_edit_prediction(window, cx);
7663 cx.notify();
7664 }
7665 }
7666
7667 fn update_visible_edit_prediction(
7668 &mut self,
7669 _window: &mut Window,
7670 cx: &mut Context<Self>,
7671 ) -> Option<()> {
7672 if DisableAiSettings::get_global(cx).disable_ai {
7673 return None;
7674 }
7675
7676 let selection = self.selections.newest_anchor();
7677 let cursor = selection.head();
7678 let multibuffer = self.buffer.read(cx).snapshot(cx);
7679 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7680 let excerpt_id = cursor.excerpt_id;
7681
7682 let show_in_menu = self.show_edit_predictions_in_menu();
7683 let completions_menu_has_precedence = !show_in_menu
7684 && (self.context_menu.borrow().is_some()
7685 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7686
7687 if completions_menu_has_precedence
7688 || !offset_selection.is_empty()
7689 || self
7690 .active_edit_prediction
7691 .as_ref()
7692 .map_or(false, |completion| {
7693 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7694 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7695 !invalidation_range.contains(&offset_selection.head())
7696 })
7697 {
7698 self.discard_edit_prediction(false, cx);
7699 return None;
7700 }
7701
7702 self.take_active_edit_prediction(cx);
7703 let Some(provider) = self.edit_prediction_provider() else {
7704 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7705 return None;
7706 };
7707
7708 let (buffer, cursor_buffer_position) =
7709 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7710
7711 self.edit_prediction_settings =
7712 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7713
7714 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7715
7716 if self.edit_prediction_indent_conflict {
7717 let cursor_point = cursor.to_point(&multibuffer);
7718
7719 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7720
7721 if let Some((_, indent)) = indents.iter().next() {
7722 if indent.len == cursor_point.column {
7723 self.edit_prediction_indent_conflict = false;
7724 }
7725 }
7726 }
7727
7728 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7729 let edits = edit_prediction
7730 .edits
7731 .into_iter()
7732 .flat_map(|(range, new_text)| {
7733 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7734 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7735 Some((start..end, new_text))
7736 })
7737 .collect::<Vec<_>>();
7738 if edits.is_empty() {
7739 return None;
7740 }
7741
7742 let first_edit_start = edits.first().unwrap().0.start;
7743 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7744 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7745
7746 let last_edit_end = edits.last().unwrap().0.end;
7747 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7748 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7749
7750 let cursor_row = cursor.to_point(&multibuffer).row;
7751
7752 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7753
7754 let mut inlay_ids = Vec::new();
7755 let invalidation_row_range;
7756 let move_invalidation_row_range = if cursor_row < edit_start_row {
7757 Some(cursor_row..edit_end_row)
7758 } else if cursor_row > edit_end_row {
7759 Some(edit_start_row..cursor_row)
7760 } else {
7761 None
7762 };
7763 let is_move =
7764 move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode;
7765 let completion = if is_move {
7766 invalidation_row_range =
7767 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7768 let target = first_edit_start;
7769 EditPrediction::Move { target, snapshot }
7770 } else {
7771 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7772 && !self.edit_predictions_hidden_for_vim_mode;
7773
7774 if show_completions_in_buffer {
7775 if edits
7776 .iter()
7777 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7778 {
7779 let mut inlays = Vec::new();
7780 for (range, new_text) in &edits {
7781 let inlay = Inlay::edit_prediction(
7782 post_inc(&mut self.next_inlay_id),
7783 range.start,
7784 new_text.as_str(),
7785 );
7786 inlay_ids.push(inlay.id);
7787 inlays.push(inlay);
7788 }
7789
7790 self.splice_inlays(&[], inlays, cx);
7791 } else {
7792 let background_color = cx.theme().status().deleted_background;
7793 self.highlight_text::<EditPredictionHighlight>(
7794 edits.iter().map(|(range, _)| range.clone()).collect(),
7795 HighlightStyle {
7796 background_color: Some(background_color),
7797 ..Default::default()
7798 },
7799 cx,
7800 );
7801 }
7802 }
7803
7804 invalidation_row_range = edit_start_row..edit_end_row;
7805
7806 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7807 if provider.show_tab_accept_marker() {
7808 EditDisplayMode::TabAccept
7809 } else {
7810 EditDisplayMode::Inline
7811 }
7812 } else {
7813 EditDisplayMode::DiffPopover
7814 };
7815
7816 EditPrediction::Edit {
7817 edits,
7818 edit_preview: edit_prediction.edit_preview,
7819 display_mode,
7820 snapshot,
7821 }
7822 };
7823
7824 let invalidation_range = multibuffer
7825 .anchor_before(Point::new(invalidation_row_range.start, 0))
7826 ..multibuffer.anchor_after(Point::new(
7827 invalidation_row_range.end,
7828 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7829 ));
7830
7831 self.stale_edit_prediction_in_menu = None;
7832 self.active_edit_prediction = Some(EditPredictionState {
7833 inlay_ids,
7834 completion,
7835 completion_id: edit_prediction.id,
7836 invalidation_range,
7837 });
7838
7839 cx.notify();
7840
7841 Some(())
7842 }
7843
7844 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7845 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7846 }
7847
7848 fn clear_tasks(&mut self) {
7849 self.tasks.clear()
7850 }
7851
7852 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7853 if self.tasks.insert(key, value).is_some() {
7854 // This case should hopefully be rare, but just in case...
7855 log::error!(
7856 "multiple different run targets found on a single line, only the last target will be rendered"
7857 )
7858 }
7859 }
7860
7861 /// Get all display points of breakpoints that will be rendered within editor
7862 ///
7863 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7864 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7865 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7866 fn active_breakpoints(
7867 &self,
7868 range: Range<DisplayRow>,
7869 window: &mut Window,
7870 cx: &mut Context<Self>,
7871 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7872 let mut breakpoint_display_points = HashMap::default();
7873
7874 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7875 return breakpoint_display_points;
7876 };
7877
7878 let snapshot = self.snapshot(window, cx);
7879
7880 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7881 let Some(project) = self.project.as_ref() else {
7882 return breakpoint_display_points;
7883 };
7884
7885 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7886 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7887
7888 for (buffer_snapshot, range, excerpt_id) in
7889 multi_buffer_snapshot.range_to_buffer_ranges(range)
7890 {
7891 let Some(buffer) = project
7892 .read(cx)
7893 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7894 else {
7895 continue;
7896 };
7897 let breakpoints = breakpoint_store.read(cx).breakpoints(
7898 &buffer,
7899 Some(
7900 buffer_snapshot.anchor_before(range.start)
7901 ..buffer_snapshot.anchor_after(range.end),
7902 ),
7903 buffer_snapshot,
7904 cx,
7905 );
7906 for (breakpoint, state) in breakpoints {
7907 let multi_buffer_anchor =
7908 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7909 let position = multi_buffer_anchor
7910 .to_point(&multi_buffer_snapshot)
7911 .to_display_point(&snapshot);
7912
7913 breakpoint_display_points.insert(
7914 position.row(),
7915 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7916 );
7917 }
7918 }
7919
7920 breakpoint_display_points
7921 }
7922
7923 fn breakpoint_context_menu(
7924 &self,
7925 anchor: Anchor,
7926 window: &mut Window,
7927 cx: &mut Context<Self>,
7928 ) -> Entity<ui::ContextMenu> {
7929 let weak_editor = cx.weak_entity();
7930 let focus_handle = self.focus_handle(cx);
7931
7932 let row = self
7933 .buffer
7934 .read(cx)
7935 .snapshot(cx)
7936 .summary_for_anchor::<Point>(&anchor)
7937 .row;
7938
7939 let breakpoint = self
7940 .breakpoint_at_row(row, window, cx)
7941 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7942
7943 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7944 "Edit Log Breakpoint"
7945 } else {
7946 "Set Log Breakpoint"
7947 };
7948
7949 let condition_breakpoint_msg = if breakpoint
7950 .as_ref()
7951 .is_some_and(|bp| bp.1.condition.is_some())
7952 {
7953 "Edit Condition Breakpoint"
7954 } else {
7955 "Set Condition Breakpoint"
7956 };
7957
7958 let hit_condition_breakpoint_msg = if breakpoint
7959 .as_ref()
7960 .is_some_and(|bp| bp.1.hit_condition.is_some())
7961 {
7962 "Edit Hit Condition Breakpoint"
7963 } else {
7964 "Set Hit Condition Breakpoint"
7965 };
7966
7967 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7968 "Unset Breakpoint"
7969 } else {
7970 "Set Breakpoint"
7971 };
7972
7973 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7974
7975 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7976 BreakpointState::Enabled => Some("Disable"),
7977 BreakpointState::Disabled => Some("Enable"),
7978 });
7979
7980 let (anchor, breakpoint) =
7981 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7982
7983 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7984 menu.on_blur_subscription(Subscription::new(|| {}))
7985 .context(focus_handle)
7986 .when(run_to_cursor, |this| {
7987 let weak_editor = weak_editor.clone();
7988 this.entry("Run to cursor", None, move |window, cx| {
7989 weak_editor
7990 .update(cx, |editor, cx| {
7991 editor.change_selections(
7992 SelectionEffects::no_scroll(),
7993 window,
7994 cx,
7995 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7996 );
7997 })
7998 .ok();
7999
8000 window.dispatch_action(Box::new(RunToCursor), cx);
8001 })
8002 .separator()
8003 })
8004 .when_some(toggle_state_msg, |this, msg| {
8005 this.entry(msg, None, {
8006 let weak_editor = weak_editor.clone();
8007 let breakpoint = breakpoint.clone();
8008 move |_window, cx| {
8009 weak_editor
8010 .update(cx, |this, cx| {
8011 this.edit_breakpoint_at_anchor(
8012 anchor,
8013 breakpoint.as_ref().clone(),
8014 BreakpointEditAction::InvertState,
8015 cx,
8016 );
8017 })
8018 .log_err();
8019 }
8020 })
8021 })
8022 .entry(set_breakpoint_msg, None, {
8023 let weak_editor = weak_editor.clone();
8024 let breakpoint = breakpoint.clone();
8025 move |_window, cx| {
8026 weak_editor
8027 .update(cx, |this, cx| {
8028 this.edit_breakpoint_at_anchor(
8029 anchor,
8030 breakpoint.as_ref().clone(),
8031 BreakpointEditAction::Toggle,
8032 cx,
8033 );
8034 })
8035 .log_err();
8036 }
8037 })
8038 .entry(log_breakpoint_msg, None, {
8039 let breakpoint = breakpoint.clone();
8040 let weak_editor = weak_editor.clone();
8041 move |window, cx| {
8042 weak_editor
8043 .update(cx, |this, cx| {
8044 this.add_edit_breakpoint_block(
8045 anchor,
8046 breakpoint.as_ref(),
8047 BreakpointPromptEditAction::Log,
8048 window,
8049 cx,
8050 );
8051 })
8052 .log_err();
8053 }
8054 })
8055 .entry(condition_breakpoint_msg, None, {
8056 let breakpoint = breakpoint.clone();
8057 let weak_editor = weak_editor.clone();
8058 move |window, cx| {
8059 weak_editor
8060 .update(cx, |this, cx| {
8061 this.add_edit_breakpoint_block(
8062 anchor,
8063 breakpoint.as_ref(),
8064 BreakpointPromptEditAction::Condition,
8065 window,
8066 cx,
8067 );
8068 })
8069 .log_err();
8070 }
8071 })
8072 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8073 weak_editor
8074 .update(cx, |this, cx| {
8075 this.add_edit_breakpoint_block(
8076 anchor,
8077 breakpoint.as_ref(),
8078 BreakpointPromptEditAction::HitCondition,
8079 window,
8080 cx,
8081 );
8082 })
8083 .log_err();
8084 })
8085 })
8086 }
8087
8088 fn render_breakpoint(
8089 &self,
8090 position: Anchor,
8091 row: DisplayRow,
8092 breakpoint: &Breakpoint,
8093 state: Option<BreakpointSessionState>,
8094 cx: &mut Context<Self>,
8095 ) -> IconButton {
8096 let is_rejected = state.is_some_and(|s| !s.verified);
8097 // Is it a breakpoint that shows up when hovering over gutter?
8098 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8099 (false, false),
8100 |PhantomBreakpointIndicator {
8101 is_active,
8102 display_row,
8103 collides_with_existing_breakpoint,
8104 }| {
8105 (
8106 is_active && display_row == row,
8107 collides_with_existing_breakpoint,
8108 )
8109 },
8110 );
8111
8112 let (color, icon) = {
8113 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8114 (false, false) => ui::IconName::DebugBreakpoint,
8115 (true, false) => ui::IconName::DebugLogBreakpoint,
8116 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8117 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8118 };
8119
8120 let color = if is_phantom {
8121 Color::Hint
8122 } else if is_rejected {
8123 Color::Disabled
8124 } else {
8125 Color::Debugger
8126 };
8127
8128 (color, icon)
8129 };
8130
8131 let breakpoint = Arc::from(breakpoint.clone());
8132
8133 let alt_as_text = gpui::Keystroke {
8134 modifiers: Modifiers::secondary_key(),
8135 ..Default::default()
8136 };
8137 let primary_action_text = if breakpoint.is_disabled() {
8138 "Enable breakpoint"
8139 } else if is_phantom && !collides_with_existing {
8140 "Set breakpoint"
8141 } else {
8142 "Unset breakpoint"
8143 };
8144 let focus_handle = self.focus_handle.clone();
8145
8146 let meta = if is_rejected {
8147 SharedString::from("No executable code is associated with this line.")
8148 } else if collides_with_existing && !breakpoint.is_disabled() {
8149 SharedString::from(format!(
8150 "{alt_as_text}-click to disable,\nright-click for more options."
8151 ))
8152 } else {
8153 SharedString::from("Right-click for more options.")
8154 };
8155 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8156 .icon_size(IconSize::XSmall)
8157 .size(ui::ButtonSize::None)
8158 .when(is_rejected, |this| {
8159 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8160 })
8161 .icon_color(color)
8162 .style(ButtonStyle::Transparent)
8163 .on_click(cx.listener({
8164 let breakpoint = breakpoint.clone();
8165
8166 move |editor, event: &ClickEvent, window, cx| {
8167 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8168 BreakpointEditAction::InvertState
8169 } else {
8170 BreakpointEditAction::Toggle
8171 };
8172
8173 window.focus(&editor.focus_handle(cx));
8174 editor.edit_breakpoint_at_anchor(
8175 position,
8176 breakpoint.as_ref().clone(),
8177 edit_action,
8178 cx,
8179 );
8180 }
8181 }))
8182 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8183 editor.set_breakpoint_context_menu(
8184 row,
8185 Some(position),
8186 event.position(),
8187 window,
8188 cx,
8189 );
8190 }))
8191 .tooltip(move |window, cx| {
8192 Tooltip::with_meta_in(
8193 primary_action_text,
8194 Some(&ToggleBreakpoint),
8195 meta.clone(),
8196 &focus_handle,
8197 window,
8198 cx,
8199 )
8200 })
8201 }
8202
8203 fn build_tasks_context(
8204 project: &Entity<Project>,
8205 buffer: &Entity<Buffer>,
8206 buffer_row: u32,
8207 tasks: &Arc<RunnableTasks>,
8208 cx: &mut Context<Self>,
8209 ) -> Task<Option<task::TaskContext>> {
8210 let position = Point::new(buffer_row, tasks.column);
8211 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8212 let location = Location {
8213 buffer: buffer.clone(),
8214 range: range_start..range_start,
8215 };
8216 // Fill in the environmental variables from the tree-sitter captures
8217 let mut captured_task_variables = TaskVariables::default();
8218 for (capture_name, value) in tasks.extra_variables.clone() {
8219 captured_task_variables.insert(
8220 task::VariableName::Custom(capture_name.into()),
8221 value.clone(),
8222 );
8223 }
8224 project.update(cx, |project, cx| {
8225 project.task_store().update(cx, |task_store, cx| {
8226 task_store.task_context_for_location(captured_task_variables, location, cx)
8227 })
8228 })
8229 }
8230
8231 pub fn spawn_nearest_task(
8232 &mut self,
8233 action: &SpawnNearestTask,
8234 window: &mut Window,
8235 cx: &mut Context<Self>,
8236 ) {
8237 let Some((workspace, _)) = self.workspace.clone() else {
8238 return;
8239 };
8240 let Some(project) = self.project.clone() else {
8241 return;
8242 };
8243
8244 // Try to find a closest, enclosing node using tree-sitter that has a task
8245 let Some((buffer, buffer_row, tasks)) = self
8246 .find_enclosing_node_task(cx)
8247 // Or find the task that's closest in row-distance.
8248 .or_else(|| self.find_closest_task(cx))
8249 else {
8250 return;
8251 };
8252
8253 let reveal_strategy = action.reveal;
8254 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8255 cx.spawn_in(window, async move |_, cx| {
8256 let context = task_context.await?;
8257 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8258
8259 let resolved = &mut resolved_task.resolved;
8260 resolved.reveal = reveal_strategy;
8261
8262 workspace
8263 .update_in(cx, |workspace, window, cx| {
8264 workspace.schedule_resolved_task(
8265 task_source_kind,
8266 resolved_task,
8267 false,
8268 window,
8269 cx,
8270 );
8271 })
8272 .ok()
8273 })
8274 .detach();
8275 }
8276
8277 fn find_closest_task(
8278 &mut self,
8279 cx: &mut Context<Self>,
8280 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8281 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8282
8283 let ((buffer_id, row), tasks) = self
8284 .tasks
8285 .iter()
8286 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8287
8288 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8289 let tasks = Arc::new(tasks.to_owned());
8290 Some((buffer, *row, tasks))
8291 }
8292
8293 fn find_enclosing_node_task(
8294 &mut self,
8295 cx: &mut Context<Self>,
8296 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8297 let snapshot = self.buffer.read(cx).snapshot(cx);
8298 let offset = self.selections.newest::<usize>(cx).head();
8299 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8300 let buffer_id = excerpt.buffer().remote_id();
8301
8302 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8303 let mut cursor = layer.node().walk();
8304
8305 while cursor.goto_first_child_for_byte(offset).is_some() {
8306 if cursor.node().end_byte() == offset {
8307 cursor.goto_next_sibling();
8308 }
8309 }
8310
8311 // Ascend to the smallest ancestor that contains the range and has a task.
8312 loop {
8313 let node = cursor.node();
8314 let node_range = node.byte_range();
8315 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8316
8317 // Check if this node contains our offset
8318 if node_range.start <= offset && node_range.end >= offset {
8319 // If it contains offset, check for task
8320 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8321 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8322 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8323 }
8324 }
8325
8326 if !cursor.goto_parent() {
8327 break;
8328 }
8329 }
8330 None
8331 }
8332
8333 fn render_run_indicator(
8334 &self,
8335 _style: &EditorStyle,
8336 is_active: bool,
8337 row: DisplayRow,
8338 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8339 cx: &mut Context<Self>,
8340 ) -> IconButton {
8341 let color = Color::Muted;
8342 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8343
8344 IconButton::new(
8345 ("run_indicator", row.0 as usize),
8346 ui::IconName::PlayOutlined,
8347 )
8348 .shape(ui::IconButtonShape::Square)
8349 .icon_size(IconSize::XSmall)
8350 .icon_color(color)
8351 .toggle_state(is_active)
8352 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8353 let quick_launch = match e {
8354 ClickEvent::Keyboard(_) => true,
8355 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8356 };
8357
8358 window.focus(&editor.focus_handle(cx));
8359 editor.toggle_code_actions(
8360 &ToggleCodeActions {
8361 deployed_from: Some(CodeActionSource::RunMenu(row)),
8362 quick_launch,
8363 },
8364 window,
8365 cx,
8366 );
8367 }))
8368 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8369 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8370 }))
8371 }
8372
8373 pub fn context_menu_visible(&self) -> bool {
8374 !self.edit_prediction_preview_is_active()
8375 && self
8376 .context_menu
8377 .borrow()
8378 .as_ref()
8379 .map_or(false, |menu| menu.visible())
8380 }
8381
8382 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8383 self.context_menu
8384 .borrow()
8385 .as_ref()
8386 .map(|menu| menu.origin())
8387 }
8388
8389 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8390 self.context_menu_options = Some(options);
8391 }
8392
8393 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8394 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8395
8396 fn render_edit_prediction_popover(
8397 &mut self,
8398 text_bounds: &Bounds<Pixels>,
8399 content_origin: gpui::Point<Pixels>,
8400 right_margin: Pixels,
8401 editor_snapshot: &EditorSnapshot,
8402 visible_row_range: Range<DisplayRow>,
8403 scroll_top: f32,
8404 scroll_bottom: f32,
8405 line_layouts: &[LineWithInvisibles],
8406 line_height: Pixels,
8407 scroll_pixel_position: gpui::Point<Pixels>,
8408 newest_selection_head: Option<DisplayPoint>,
8409 editor_width: Pixels,
8410 style: &EditorStyle,
8411 window: &mut Window,
8412 cx: &mut App,
8413 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8414 if self.mode().is_minimap() {
8415 return None;
8416 }
8417 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8418
8419 if self.edit_prediction_visible_in_cursor_popover(true) {
8420 return None;
8421 }
8422
8423 match &active_edit_prediction.completion {
8424 EditPrediction::Move { target, .. } => {
8425 let target_display_point = target.to_display_point(editor_snapshot);
8426
8427 if self.edit_prediction_requires_modifier() {
8428 if !self.edit_prediction_preview_is_active() {
8429 return None;
8430 }
8431
8432 self.render_edit_prediction_modifier_jump_popover(
8433 text_bounds,
8434 content_origin,
8435 visible_row_range,
8436 line_layouts,
8437 line_height,
8438 scroll_pixel_position,
8439 newest_selection_head,
8440 target_display_point,
8441 window,
8442 cx,
8443 )
8444 } else {
8445 self.render_edit_prediction_eager_jump_popover(
8446 text_bounds,
8447 content_origin,
8448 editor_snapshot,
8449 visible_row_range,
8450 scroll_top,
8451 scroll_bottom,
8452 line_height,
8453 scroll_pixel_position,
8454 target_display_point,
8455 editor_width,
8456 window,
8457 cx,
8458 )
8459 }
8460 }
8461 EditPrediction::Edit {
8462 display_mode: EditDisplayMode::Inline,
8463 ..
8464 } => None,
8465 EditPrediction::Edit {
8466 display_mode: EditDisplayMode::TabAccept,
8467 edits,
8468 ..
8469 } => {
8470 let range = &edits.first()?.0;
8471 let target_display_point = range.end.to_display_point(editor_snapshot);
8472
8473 self.render_edit_prediction_end_of_line_popover(
8474 "Accept",
8475 editor_snapshot,
8476 visible_row_range,
8477 target_display_point,
8478 line_height,
8479 scroll_pixel_position,
8480 content_origin,
8481 editor_width,
8482 window,
8483 cx,
8484 )
8485 }
8486 EditPrediction::Edit {
8487 edits,
8488 edit_preview,
8489 display_mode: EditDisplayMode::DiffPopover,
8490 snapshot,
8491 } => self.render_edit_prediction_diff_popover(
8492 text_bounds,
8493 content_origin,
8494 right_margin,
8495 editor_snapshot,
8496 visible_row_range,
8497 line_layouts,
8498 line_height,
8499 scroll_pixel_position,
8500 newest_selection_head,
8501 editor_width,
8502 style,
8503 edits,
8504 edit_preview,
8505 snapshot,
8506 window,
8507 cx,
8508 ),
8509 }
8510 }
8511
8512 fn render_edit_prediction_modifier_jump_popover(
8513 &mut self,
8514 text_bounds: &Bounds<Pixels>,
8515 content_origin: gpui::Point<Pixels>,
8516 visible_row_range: Range<DisplayRow>,
8517 line_layouts: &[LineWithInvisibles],
8518 line_height: Pixels,
8519 scroll_pixel_position: gpui::Point<Pixels>,
8520 newest_selection_head: Option<DisplayPoint>,
8521 target_display_point: DisplayPoint,
8522 window: &mut Window,
8523 cx: &mut App,
8524 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8525 let scrolled_content_origin =
8526 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8527
8528 const SCROLL_PADDING_Y: Pixels = px(12.);
8529
8530 if target_display_point.row() < visible_row_range.start {
8531 return self.render_edit_prediction_scroll_popover(
8532 |_| SCROLL_PADDING_Y,
8533 IconName::ArrowUp,
8534 visible_row_range,
8535 line_layouts,
8536 newest_selection_head,
8537 scrolled_content_origin,
8538 window,
8539 cx,
8540 );
8541 } else if target_display_point.row() >= visible_row_range.end {
8542 return self.render_edit_prediction_scroll_popover(
8543 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8544 IconName::ArrowDown,
8545 visible_row_range,
8546 line_layouts,
8547 newest_selection_head,
8548 scrolled_content_origin,
8549 window,
8550 cx,
8551 );
8552 }
8553
8554 const POLE_WIDTH: Pixels = px(2.);
8555
8556 let line_layout =
8557 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8558 let target_column = target_display_point.column() as usize;
8559
8560 let target_x = line_layout.x_for_index(target_column);
8561 let target_y =
8562 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8563
8564 let flag_on_right = target_x < text_bounds.size.width / 2.;
8565
8566 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8567 border_color.l += 0.001;
8568
8569 let mut element = v_flex()
8570 .items_end()
8571 .when(flag_on_right, |el| el.items_start())
8572 .child(if flag_on_right {
8573 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8574 .rounded_bl(px(0.))
8575 .rounded_tl(px(0.))
8576 .border_l_2()
8577 .border_color(border_color)
8578 } else {
8579 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8580 .rounded_br(px(0.))
8581 .rounded_tr(px(0.))
8582 .border_r_2()
8583 .border_color(border_color)
8584 })
8585 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8586 .into_any();
8587
8588 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8589
8590 let mut origin = scrolled_content_origin + point(target_x, target_y)
8591 - point(
8592 if flag_on_right {
8593 POLE_WIDTH
8594 } else {
8595 size.width - POLE_WIDTH
8596 },
8597 size.height - line_height,
8598 );
8599
8600 origin.x = origin.x.max(content_origin.x);
8601
8602 element.prepaint_at(origin, window, cx);
8603
8604 Some((element, origin))
8605 }
8606
8607 fn render_edit_prediction_scroll_popover(
8608 &mut self,
8609 to_y: impl Fn(Size<Pixels>) -> Pixels,
8610 scroll_icon: IconName,
8611 visible_row_range: Range<DisplayRow>,
8612 line_layouts: &[LineWithInvisibles],
8613 newest_selection_head: Option<DisplayPoint>,
8614 scrolled_content_origin: gpui::Point<Pixels>,
8615 window: &mut Window,
8616 cx: &mut App,
8617 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8618 let mut element = self
8619 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8620 .into_any();
8621
8622 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8623
8624 let cursor = newest_selection_head?;
8625 let cursor_row_layout =
8626 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8627 let cursor_column = cursor.column() as usize;
8628
8629 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8630
8631 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8632
8633 element.prepaint_at(origin, window, cx);
8634 Some((element, origin))
8635 }
8636
8637 fn render_edit_prediction_eager_jump_popover(
8638 &mut self,
8639 text_bounds: &Bounds<Pixels>,
8640 content_origin: gpui::Point<Pixels>,
8641 editor_snapshot: &EditorSnapshot,
8642 visible_row_range: Range<DisplayRow>,
8643 scroll_top: f32,
8644 scroll_bottom: f32,
8645 line_height: Pixels,
8646 scroll_pixel_position: gpui::Point<Pixels>,
8647 target_display_point: DisplayPoint,
8648 editor_width: Pixels,
8649 window: &mut Window,
8650 cx: &mut App,
8651 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8652 if target_display_point.row().as_f32() < scroll_top {
8653 let mut element = self
8654 .render_edit_prediction_line_popover(
8655 "Jump to Edit",
8656 Some(IconName::ArrowUp),
8657 window,
8658 cx,
8659 )?
8660 .into_any();
8661
8662 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8663 let offset = point(
8664 (text_bounds.size.width - size.width) / 2.,
8665 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8666 );
8667
8668 let origin = text_bounds.origin + offset;
8669 element.prepaint_at(origin, window, cx);
8670 Some((element, origin))
8671 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8672 let mut element = self
8673 .render_edit_prediction_line_popover(
8674 "Jump to Edit",
8675 Some(IconName::ArrowDown),
8676 window,
8677 cx,
8678 )?
8679 .into_any();
8680
8681 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8682 let offset = point(
8683 (text_bounds.size.width - size.width) / 2.,
8684 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8685 );
8686
8687 let origin = text_bounds.origin + offset;
8688 element.prepaint_at(origin, window, cx);
8689 Some((element, origin))
8690 } else {
8691 self.render_edit_prediction_end_of_line_popover(
8692 "Jump to Edit",
8693 editor_snapshot,
8694 visible_row_range,
8695 target_display_point,
8696 line_height,
8697 scroll_pixel_position,
8698 content_origin,
8699 editor_width,
8700 window,
8701 cx,
8702 )
8703 }
8704 }
8705
8706 fn render_edit_prediction_end_of_line_popover(
8707 self: &mut Editor,
8708 label: &'static str,
8709 editor_snapshot: &EditorSnapshot,
8710 visible_row_range: Range<DisplayRow>,
8711 target_display_point: DisplayPoint,
8712 line_height: Pixels,
8713 scroll_pixel_position: gpui::Point<Pixels>,
8714 content_origin: gpui::Point<Pixels>,
8715 editor_width: Pixels,
8716 window: &mut Window,
8717 cx: &mut App,
8718 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8719 let target_line_end = DisplayPoint::new(
8720 target_display_point.row(),
8721 editor_snapshot.line_len(target_display_point.row()),
8722 );
8723
8724 let mut element = self
8725 .render_edit_prediction_line_popover(label, None, window, cx)?
8726 .into_any();
8727
8728 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8729
8730 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8731
8732 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8733 let mut origin = start_point
8734 + line_origin
8735 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8736 origin.x = origin.x.max(content_origin.x);
8737
8738 let max_x = content_origin.x + editor_width - size.width;
8739
8740 if origin.x > max_x {
8741 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8742
8743 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8744 origin.y += offset;
8745 IconName::ArrowUp
8746 } else {
8747 origin.y -= offset;
8748 IconName::ArrowDown
8749 };
8750
8751 element = self
8752 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8753 .into_any();
8754
8755 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8756
8757 origin.x = content_origin.x + editor_width - size.width - px(2.);
8758 }
8759
8760 element.prepaint_at(origin, window, cx);
8761 Some((element, origin))
8762 }
8763
8764 fn render_edit_prediction_diff_popover(
8765 self: &Editor,
8766 text_bounds: &Bounds<Pixels>,
8767 content_origin: gpui::Point<Pixels>,
8768 right_margin: Pixels,
8769 editor_snapshot: &EditorSnapshot,
8770 visible_row_range: Range<DisplayRow>,
8771 line_layouts: &[LineWithInvisibles],
8772 line_height: Pixels,
8773 scroll_pixel_position: gpui::Point<Pixels>,
8774 newest_selection_head: Option<DisplayPoint>,
8775 editor_width: Pixels,
8776 style: &EditorStyle,
8777 edits: &Vec<(Range<Anchor>, String)>,
8778 edit_preview: &Option<language::EditPreview>,
8779 snapshot: &language::BufferSnapshot,
8780 window: &mut Window,
8781 cx: &mut App,
8782 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8783 let edit_start = edits
8784 .first()
8785 .unwrap()
8786 .0
8787 .start
8788 .to_display_point(editor_snapshot);
8789 let edit_end = edits
8790 .last()
8791 .unwrap()
8792 .0
8793 .end
8794 .to_display_point(editor_snapshot);
8795
8796 let is_visible = visible_row_range.contains(&edit_start.row())
8797 || visible_row_range.contains(&edit_end.row());
8798 if !is_visible {
8799 return None;
8800 }
8801
8802 let highlighted_edits =
8803 crate::edit_prediction_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8804
8805 let styled_text = highlighted_edits.to_styled_text(&style.text);
8806 let line_count = highlighted_edits.text.lines().count();
8807
8808 const BORDER_WIDTH: Pixels = px(1.);
8809
8810 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8811 let has_keybind = keybind.is_some();
8812
8813 let mut element = h_flex()
8814 .items_start()
8815 .child(
8816 h_flex()
8817 .bg(cx.theme().colors().editor_background)
8818 .border(BORDER_WIDTH)
8819 .shadow_xs()
8820 .border_color(cx.theme().colors().border)
8821 .rounded_l_lg()
8822 .when(line_count > 1, |el| el.rounded_br_lg())
8823 .pr_1()
8824 .child(styled_text),
8825 )
8826 .child(
8827 h_flex()
8828 .h(line_height + BORDER_WIDTH * 2.)
8829 .px_1p5()
8830 .gap_1()
8831 // Workaround: For some reason, there's a gap if we don't do this
8832 .ml(-BORDER_WIDTH)
8833 .shadow(vec![gpui::BoxShadow {
8834 color: gpui::black().opacity(0.05),
8835 offset: point(px(1.), px(1.)),
8836 blur_radius: px(2.),
8837 spread_radius: px(0.),
8838 }])
8839 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8840 .border(BORDER_WIDTH)
8841 .border_color(cx.theme().colors().border)
8842 .rounded_r_lg()
8843 .id("edit_prediction_diff_popover_keybind")
8844 .when(!has_keybind, |el| {
8845 let status_colors = cx.theme().status();
8846
8847 el.bg(status_colors.error_background)
8848 .border_color(status_colors.error.opacity(0.6))
8849 .child(Icon::new(IconName::Info).color(Color::Error))
8850 .cursor_default()
8851 .hoverable_tooltip(move |_window, cx| {
8852 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8853 })
8854 })
8855 .children(keybind),
8856 )
8857 .into_any();
8858
8859 let longest_row =
8860 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8861 let longest_line_width = if visible_row_range.contains(&longest_row) {
8862 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8863 } else {
8864 layout_line(
8865 longest_row,
8866 editor_snapshot,
8867 style,
8868 editor_width,
8869 |_| false,
8870 window,
8871 cx,
8872 )
8873 .width
8874 };
8875
8876 let viewport_bounds =
8877 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8878 right: -right_margin,
8879 ..Default::default()
8880 });
8881
8882 let x_after_longest =
8883 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8884 - scroll_pixel_position.x;
8885
8886 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8887
8888 // Fully visible if it can be displayed within the window (allow overlapping other
8889 // panes). However, this is only allowed if the popover starts within text_bounds.
8890 let can_position_to_the_right = x_after_longest < text_bounds.right()
8891 && x_after_longest + element_bounds.width < viewport_bounds.right();
8892
8893 let mut origin = if can_position_to_the_right {
8894 point(
8895 x_after_longest,
8896 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8897 - scroll_pixel_position.y,
8898 )
8899 } else {
8900 let cursor_row = newest_selection_head.map(|head| head.row());
8901 let above_edit = edit_start
8902 .row()
8903 .0
8904 .checked_sub(line_count as u32)
8905 .map(DisplayRow);
8906 let below_edit = Some(edit_end.row() + 1);
8907 let above_cursor =
8908 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8909 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8910
8911 // Place the edit popover adjacent to the edit if there is a location
8912 // available that is onscreen and does not obscure the cursor. Otherwise,
8913 // place it adjacent to the cursor.
8914 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8915 .into_iter()
8916 .flatten()
8917 .find(|&start_row| {
8918 let end_row = start_row + line_count as u32;
8919 visible_row_range.contains(&start_row)
8920 && visible_row_range.contains(&end_row)
8921 && cursor_row.map_or(true, |cursor_row| {
8922 !((start_row..end_row).contains(&cursor_row))
8923 })
8924 })?;
8925
8926 content_origin
8927 + point(
8928 -scroll_pixel_position.x,
8929 row_target.as_f32() * line_height - scroll_pixel_position.y,
8930 )
8931 };
8932
8933 origin.x -= BORDER_WIDTH;
8934
8935 window.defer_draw(element, origin, 1);
8936
8937 // Do not return an element, since it will already be drawn due to defer_draw.
8938 None
8939 }
8940
8941 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8942 px(30.)
8943 }
8944
8945 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8946 if self.read_only(cx) {
8947 cx.theme().players().read_only()
8948 } else {
8949 self.style.as_ref().unwrap().local_player
8950 }
8951 }
8952
8953 fn render_edit_prediction_accept_keybind(
8954 &self,
8955 window: &mut Window,
8956 cx: &App,
8957 ) -> Option<AnyElement> {
8958 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8959 let accept_keystroke = accept_binding.keystroke()?;
8960
8961 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8962
8963 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8964 Color::Accent
8965 } else {
8966 Color::Muted
8967 };
8968
8969 h_flex()
8970 .px_0p5()
8971 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8972 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8973 .text_size(TextSize::XSmall.rems(cx))
8974 .child(h_flex().children(ui::render_modifiers(
8975 &accept_keystroke.modifiers,
8976 PlatformStyle::platform(),
8977 Some(modifiers_color),
8978 Some(IconSize::XSmall.rems().into()),
8979 true,
8980 )))
8981 .when(is_platform_style_mac, |parent| {
8982 parent.child(accept_keystroke.key.clone())
8983 })
8984 .when(!is_platform_style_mac, |parent| {
8985 parent.child(
8986 Key::new(
8987 util::capitalize(&accept_keystroke.key),
8988 Some(Color::Default),
8989 )
8990 .size(Some(IconSize::XSmall.rems().into())),
8991 )
8992 })
8993 .into_any()
8994 .into()
8995 }
8996
8997 fn render_edit_prediction_line_popover(
8998 &self,
8999 label: impl Into<SharedString>,
9000 icon: Option<IconName>,
9001 window: &mut Window,
9002 cx: &App,
9003 ) -> Option<Stateful<Div>> {
9004 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9005
9006 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9007 let has_keybind = keybind.is_some();
9008
9009 let result = h_flex()
9010 .id("ep-line-popover")
9011 .py_0p5()
9012 .pl_1()
9013 .pr(padding_right)
9014 .gap_1()
9015 .rounded_md()
9016 .border_1()
9017 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9018 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9019 .shadow_xs()
9020 .when(!has_keybind, |el| {
9021 let status_colors = cx.theme().status();
9022
9023 el.bg(status_colors.error_background)
9024 .border_color(status_colors.error.opacity(0.6))
9025 .pl_2()
9026 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9027 .cursor_default()
9028 .hoverable_tooltip(move |_window, cx| {
9029 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9030 })
9031 })
9032 .children(keybind)
9033 .child(
9034 Label::new(label)
9035 .size(LabelSize::Small)
9036 .when(!has_keybind, |el| {
9037 el.color(cx.theme().status().error.into()).strikethrough()
9038 }),
9039 )
9040 .when(!has_keybind, |el| {
9041 el.child(
9042 h_flex().ml_1().child(
9043 Icon::new(IconName::Info)
9044 .size(IconSize::Small)
9045 .color(cx.theme().status().error.into()),
9046 ),
9047 )
9048 })
9049 .when_some(icon, |element, icon| {
9050 element.child(
9051 div()
9052 .mt(px(1.5))
9053 .child(Icon::new(icon).size(IconSize::Small)),
9054 )
9055 });
9056
9057 Some(result)
9058 }
9059
9060 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9061 let accent_color = cx.theme().colors().text_accent;
9062 let editor_bg_color = cx.theme().colors().editor_background;
9063 editor_bg_color.blend(accent_color.opacity(0.1))
9064 }
9065
9066 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9067 let accent_color = cx.theme().colors().text_accent;
9068 let editor_bg_color = cx.theme().colors().editor_background;
9069 editor_bg_color.blend(accent_color.opacity(0.6))
9070 }
9071
9072 fn render_edit_prediction_cursor_popover(
9073 &self,
9074 min_width: Pixels,
9075 max_width: Pixels,
9076 cursor_point: Point,
9077 style: &EditorStyle,
9078 accept_keystroke: Option<&gpui::Keystroke>,
9079 _window: &Window,
9080 cx: &mut Context<Editor>,
9081 ) -> Option<AnyElement> {
9082 let provider = self.edit_prediction_provider.as_ref()?;
9083
9084 if provider.provider.needs_terms_acceptance(cx) {
9085 return Some(
9086 h_flex()
9087 .min_w(min_width)
9088 .flex_1()
9089 .px_2()
9090 .py_1()
9091 .gap_3()
9092 .elevation_2(cx)
9093 .hover(|style| style.bg(cx.theme().colors().element_hover))
9094 .id("accept-terms")
9095 .cursor_pointer()
9096 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9097 .on_click(cx.listener(|this, _event, window, cx| {
9098 cx.stop_propagation();
9099 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9100 window.dispatch_action(
9101 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9102 cx,
9103 );
9104 }))
9105 .child(
9106 h_flex()
9107 .flex_1()
9108 .gap_2()
9109 .child(Icon::new(IconName::ZedPredict))
9110 .child(Label::new("Accept Terms of Service"))
9111 .child(div().w_full())
9112 .child(
9113 Icon::new(IconName::ArrowUpRight)
9114 .color(Color::Muted)
9115 .size(IconSize::Small),
9116 )
9117 .into_any_element(),
9118 )
9119 .into_any(),
9120 );
9121 }
9122
9123 let is_refreshing = provider.provider.is_refreshing(cx);
9124
9125 fn pending_completion_container() -> Div {
9126 h_flex()
9127 .h_full()
9128 .flex_1()
9129 .gap_2()
9130 .child(Icon::new(IconName::ZedPredict))
9131 }
9132
9133 let completion = match &self.active_edit_prediction {
9134 Some(prediction) => {
9135 if !self.has_visible_completions_menu() {
9136 const RADIUS: Pixels = px(6.);
9137 const BORDER_WIDTH: Pixels = px(1.);
9138
9139 return Some(
9140 h_flex()
9141 .elevation_2(cx)
9142 .border(BORDER_WIDTH)
9143 .border_color(cx.theme().colors().border)
9144 .when(accept_keystroke.is_none(), |el| {
9145 el.border_color(cx.theme().status().error)
9146 })
9147 .rounded(RADIUS)
9148 .rounded_tl(px(0.))
9149 .overflow_hidden()
9150 .child(div().px_1p5().child(match &prediction.completion {
9151 EditPrediction::Move { target, snapshot } => {
9152 use text::ToPoint as _;
9153 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9154 {
9155 Icon::new(IconName::ZedPredictDown)
9156 } else {
9157 Icon::new(IconName::ZedPredictUp)
9158 }
9159 }
9160 EditPrediction::Edit { .. } => Icon::new(IconName::ZedPredict),
9161 }))
9162 .child(
9163 h_flex()
9164 .gap_1()
9165 .py_1()
9166 .px_2()
9167 .rounded_r(RADIUS - BORDER_WIDTH)
9168 .border_l_1()
9169 .border_color(cx.theme().colors().border)
9170 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9171 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9172 el.child(
9173 Label::new("Hold")
9174 .size(LabelSize::Small)
9175 .when(accept_keystroke.is_none(), |el| {
9176 el.strikethrough()
9177 })
9178 .line_height_style(LineHeightStyle::UiLabel),
9179 )
9180 })
9181 .id("edit_prediction_cursor_popover_keybind")
9182 .when(accept_keystroke.is_none(), |el| {
9183 let status_colors = cx.theme().status();
9184
9185 el.bg(status_colors.error_background)
9186 .border_color(status_colors.error.opacity(0.6))
9187 .child(Icon::new(IconName::Info).color(Color::Error))
9188 .cursor_default()
9189 .hoverable_tooltip(move |_window, cx| {
9190 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9191 .into()
9192 })
9193 })
9194 .when_some(
9195 accept_keystroke.as_ref(),
9196 |el, accept_keystroke| {
9197 el.child(h_flex().children(ui::render_modifiers(
9198 &accept_keystroke.modifiers,
9199 PlatformStyle::platform(),
9200 Some(Color::Default),
9201 Some(IconSize::XSmall.rems().into()),
9202 false,
9203 )))
9204 },
9205 ),
9206 )
9207 .into_any(),
9208 );
9209 }
9210
9211 self.render_edit_prediction_cursor_popover_preview(
9212 prediction,
9213 cursor_point,
9214 style,
9215 cx,
9216 )?
9217 }
9218
9219 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9220 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9221 stale_completion,
9222 cursor_point,
9223 style,
9224 cx,
9225 )?,
9226
9227 None => {
9228 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9229 }
9230 },
9231
9232 None => pending_completion_container().child(Label::new("No Prediction")),
9233 };
9234
9235 let completion = if is_refreshing {
9236 completion
9237 .with_animation(
9238 "loading-completion",
9239 Animation::new(Duration::from_secs(2))
9240 .repeat()
9241 .with_easing(pulsating_between(0.4, 0.8)),
9242 |label, delta| label.opacity(delta),
9243 )
9244 .into_any_element()
9245 } else {
9246 completion.into_any_element()
9247 };
9248
9249 let has_completion = self.active_edit_prediction.is_some();
9250
9251 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9252 Some(
9253 h_flex()
9254 .min_w(min_width)
9255 .max_w(max_width)
9256 .flex_1()
9257 .elevation_2(cx)
9258 .border_color(cx.theme().colors().border)
9259 .child(
9260 div()
9261 .flex_1()
9262 .py_1()
9263 .px_2()
9264 .overflow_hidden()
9265 .child(completion),
9266 )
9267 .when_some(accept_keystroke, |el, accept_keystroke| {
9268 if !accept_keystroke.modifiers.modified() {
9269 return el;
9270 }
9271
9272 el.child(
9273 h_flex()
9274 .h_full()
9275 .border_l_1()
9276 .rounded_r_lg()
9277 .border_color(cx.theme().colors().border)
9278 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9279 .gap_1()
9280 .py_1()
9281 .px_2()
9282 .child(
9283 h_flex()
9284 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9285 .when(is_platform_style_mac, |parent| parent.gap_1())
9286 .child(h_flex().children(ui::render_modifiers(
9287 &accept_keystroke.modifiers,
9288 PlatformStyle::platform(),
9289 Some(if !has_completion {
9290 Color::Muted
9291 } else {
9292 Color::Default
9293 }),
9294 None,
9295 false,
9296 ))),
9297 )
9298 .child(Label::new("Preview").into_any_element())
9299 .opacity(if has_completion { 1.0 } else { 0.4 }),
9300 )
9301 })
9302 .into_any(),
9303 )
9304 }
9305
9306 fn render_edit_prediction_cursor_popover_preview(
9307 &self,
9308 completion: &EditPredictionState,
9309 cursor_point: Point,
9310 style: &EditorStyle,
9311 cx: &mut Context<Editor>,
9312 ) -> Option<Div> {
9313 use text::ToPoint as _;
9314
9315 fn render_relative_row_jump(
9316 prefix: impl Into<String>,
9317 current_row: u32,
9318 target_row: u32,
9319 ) -> Div {
9320 let (row_diff, arrow) = if target_row < current_row {
9321 (current_row - target_row, IconName::ArrowUp)
9322 } else {
9323 (target_row - current_row, IconName::ArrowDown)
9324 };
9325
9326 h_flex()
9327 .child(
9328 Label::new(format!("{}{}", prefix.into(), row_diff))
9329 .color(Color::Muted)
9330 .size(LabelSize::Small),
9331 )
9332 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9333 }
9334
9335 match &completion.completion {
9336 EditPrediction::Move {
9337 target, snapshot, ..
9338 } => Some(
9339 h_flex()
9340 .px_2()
9341 .gap_2()
9342 .flex_1()
9343 .child(
9344 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9345 Icon::new(IconName::ZedPredictDown)
9346 } else {
9347 Icon::new(IconName::ZedPredictUp)
9348 },
9349 )
9350 .child(Label::new("Jump to Edit")),
9351 ),
9352
9353 EditPrediction::Edit {
9354 edits,
9355 edit_preview,
9356 snapshot,
9357 display_mode: _,
9358 } => {
9359 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9360
9361 let (highlighted_edits, has_more_lines) = crate::edit_prediction_edit_text(
9362 &snapshot,
9363 &edits,
9364 edit_preview.as_ref()?,
9365 true,
9366 cx,
9367 )
9368 .first_line_preview();
9369
9370 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9371 .with_default_highlights(&style.text, highlighted_edits.highlights);
9372
9373 let preview = h_flex()
9374 .gap_1()
9375 .min_w_16()
9376 .child(styled_text)
9377 .when(has_more_lines, |parent| parent.child("…"));
9378
9379 let left = if first_edit_row != cursor_point.row {
9380 render_relative_row_jump("", cursor_point.row, first_edit_row)
9381 .into_any_element()
9382 } else {
9383 Icon::new(IconName::ZedPredict).into_any_element()
9384 };
9385
9386 Some(
9387 h_flex()
9388 .h_full()
9389 .flex_1()
9390 .gap_2()
9391 .pr_1()
9392 .overflow_x_hidden()
9393 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9394 .child(left)
9395 .child(preview),
9396 )
9397 }
9398 }
9399 }
9400
9401 pub fn render_context_menu(
9402 &self,
9403 style: &EditorStyle,
9404 max_height_in_lines: u32,
9405 window: &mut Window,
9406 cx: &mut Context<Editor>,
9407 ) -> Option<AnyElement> {
9408 let menu = self.context_menu.borrow();
9409 let menu = menu.as_ref()?;
9410 if !menu.visible() {
9411 return None;
9412 };
9413 Some(menu.render(style, max_height_in_lines, window, cx))
9414 }
9415
9416 fn render_context_menu_aside(
9417 &mut self,
9418 max_size: Size<Pixels>,
9419 window: &mut Window,
9420 cx: &mut Context<Editor>,
9421 ) -> Option<AnyElement> {
9422 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9423 if menu.visible() {
9424 menu.render_aside(max_size, window, cx)
9425 } else {
9426 None
9427 }
9428 })
9429 }
9430
9431 fn hide_context_menu(
9432 &mut self,
9433 window: &mut Window,
9434 cx: &mut Context<Self>,
9435 ) -> Option<CodeContextMenu> {
9436 cx.notify();
9437 self.completion_tasks.clear();
9438 let context_menu = self.context_menu.borrow_mut().take();
9439 self.stale_edit_prediction_in_menu.take();
9440 self.update_visible_edit_prediction(window, cx);
9441 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9442 if let Some(completion_provider) = &self.completion_provider {
9443 completion_provider.selection_changed(None, window, cx);
9444 }
9445 }
9446 context_menu
9447 }
9448
9449 fn show_snippet_choices(
9450 &mut self,
9451 choices: &Vec<String>,
9452 selection: Range<Anchor>,
9453 cx: &mut Context<Self>,
9454 ) {
9455 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9456 (Some(a), Some(b)) if a == b => a,
9457 _ => {
9458 log::error!("expected anchor range to have matching buffer IDs");
9459 return;
9460 }
9461 };
9462 let multi_buffer = self.buffer().read(cx);
9463 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9464 return;
9465 };
9466
9467 let id = post_inc(&mut self.next_completion_id);
9468 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9469 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9470 CompletionsMenu::new_snippet_choices(
9471 id,
9472 true,
9473 choices,
9474 selection,
9475 buffer,
9476 snippet_sort_order,
9477 ),
9478 ));
9479 }
9480
9481 pub fn insert_snippet(
9482 &mut self,
9483 insertion_ranges: &[Range<usize>],
9484 snippet: Snippet,
9485 window: &mut Window,
9486 cx: &mut Context<Self>,
9487 ) -> Result<()> {
9488 struct Tabstop<T> {
9489 is_end_tabstop: bool,
9490 ranges: Vec<Range<T>>,
9491 choices: Option<Vec<String>>,
9492 }
9493
9494 let tabstops = self.buffer.update(cx, |buffer, cx| {
9495 let snippet_text: Arc<str> = snippet.text.clone().into();
9496 let edits = insertion_ranges
9497 .iter()
9498 .cloned()
9499 .map(|range| (range, snippet_text.clone()));
9500 let autoindent_mode = AutoindentMode::Block {
9501 original_indent_columns: Vec::new(),
9502 };
9503 buffer.edit(edits, Some(autoindent_mode), cx);
9504
9505 let snapshot = &*buffer.read(cx);
9506 let snippet = &snippet;
9507 snippet
9508 .tabstops
9509 .iter()
9510 .map(|tabstop| {
9511 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9512 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9513 });
9514 let mut tabstop_ranges = tabstop
9515 .ranges
9516 .iter()
9517 .flat_map(|tabstop_range| {
9518 let mut delta = 0_isize;
9519 insertion_ranges.iter().map(move |insertion_range| {
9520 let insertion_start = insertion_range.start as isize + delta;
9521 delta +=
9522 snippet.text.len() as isize - insertion_range.len() as isize;
9523
9524 let start = ((insertion_start + tabstop_range.start) as usize)
9525 .min(snapshot.len());
9526 let end = ((insertion_start + tabstop_range.end) as usize)
9527 .min(snapshot.len());
9528 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9529 })
9530 })
9531 .collect::<Vec<_>>();
9532 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9533
9534 Tabstop {
9535 is_end_tabstop,
9536 ranges: tabstop_ranges,
9537 choices: tabstop.choices.clone(),
9538 }
9539 })
9540 .collect::<Vec<_>>()
9541 });
9542 if let Some(tabstop) = tabstops.first() {
9543 self.change_selections(Default::default(), window, cx, |s| {
9544 // Reverse order so that the first range is the newest created selection.
9545 // Completions will use it and autoscroll will prioritize it.
9546 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9547 });
9548
9549 if let Some(choices) = &tabstop.choices {
9550 if let Some(selection) = tabstop.ranges.first() {
9551 self.show_snippet_choices(choices, selection.clone(), cx)
9552 }
9553 }
9554
9555 // If we're already at the last tabstop and it's at the end of the snippet,
9556 // we're done, we don't need to keep the state around.
9557 if !tabstop.is_end_tabstop {
9558 let choices = tabstops
9559 .iter()
9560 .map(|tabstop| tabstop.choices.clone())
9561 .collect();
9562
9563 let ranges = tabstops
9564 .into_iter()
9565 .map(|tabstop| tabstop.ranges)
9566 .collect::<Vec<_>>();
9567
9568 self.snippet_stack.push(SnippetState {
9569 active_index: 0,
9570 ranges,
9571 choices,
9572 });
9573 }
9574
9575 // Check whether the just-entered snippet ends with an auto-closable bracket.
9576 if self.autoclose_regions.is_empty() {
9577 let snapshot = self.buffer.read(cx).snapshot(cx);
9578 let mut all_selections = self.selections.all::<Point>(cx);
9579 for selection in &mut all_selections {
9580 let selection_head = selection.head();
9581 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9582 continue;
9583 };
9584
9585 let mut bracket_pair = None;
9586 let max_lookup_length = scope
9587 .brackets()
9588 .map(|(pair, _)| {
9589 pair.start
9590 .as_str()
9591 .chars()
9592 .count()
9593 .max(pair.end.as_str().chars().count())
9594 })
9595 .max();
9596 if let Some(max_lookup_length) = max_lookup_length {
9597 let next_text = snapshot
9598 .chars_at(selection_head)
9599 .take(max_lookup_length)
9600 .collect::<String>();
9601 let prev_text = snapshot
9602 .reversed_chars_at(selection_head)
9603 .take(max_lookup_length)
9604 .collect::<String>();
9605
9606 for (pair, enabled) in scope.brackets() {
9607 if enabled
9608 && pair.close
9609 && prev_text.starts_with(pair.start.as_str())
9610 && next_text.starts_with(pair.end.as_str())
9611 {
9612 bracket_pair = Some(pair.clone());
9613 break;
9614 }
9615 }
9616 }
9617
9618 if let Some(pair) = bracket_pair {
9619 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9620 let autoclose_enabled =
9621 self.use_autoclose && snapshot_settings.use_autoclose;
9622 if autoclose_enabled {
9623 let start = snapshot.anchor_after(selection_head);
9624 let end = snapshot.anchor_after(selection_head);
9625 self.autoclose_regions.push(AutocloseRegion {
9626 selection_id: selection.id,
9627 range: start..end,
9628 pair,
9629 });
9630 }
9631 }
9632 }
9633 }
9634 }
9635 Ok(())
9636 }
9637
9638 pub fn move_to_next_snippet_tabstop(
9639 &mut self,
9640 window: &mut Window,
9641 cx: &mut Context<Self>,
9642 ) -> bool {
9643 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9644 }
9645
9646 pub fn move_to_prev_snippet_tabstop(
9647 &mut self,
9648 window: &mut Window,
9649 cx: &mut Context<Self>,
9650 ) -> bool {
9651 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9652 }
9653
9654 pub fn move_to_snippet_tabstop(
9655 &mut self,
9656 bias: Bias,
9657 window: &mut Window,
9658 cx: &mut Context<Self>,
9659 ) -> bool {
9660 if let Some(mut snippet) = self.snippet_stack.pop() {
9661 match bias {
9662 Bias::Left => {
9663 if snippet.active_index > 0 {
9664 snippet.active_index -= 1;
9665 } else {
9666 self.snippet_stack.push(snippet);
9667 return false;
9668 }
9669 }
9670 Bias::Right => {
9671 if snippet.active_index + 1 < snippet.ranges.len() {
9672 snippet.active_index += 1;
9673 } else {
9674 self.snippet_stack.push(snippet);
9675 return false;
9676 }
9677 }
9678 }
9679 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9680 self.change_selections(Default::default(), window, cx, |s| {
9681 // Reverse order so that the first range is the newest created selection.
9682 // Completions will use it and autoscroll will prioritize it.
9683 s.select_ranges(current_ranges.iter().rev().cloned())
9684 });
9685
9686 if let Some(choices) = &snippet.choices[snippet.active_index] {
9687 if let Some(selection) = current_ranges.first() {
9688 self.show_snippet_choices(&choices, selection.clone(), cx);
9689 }
9690 }
9691
9692 // If snippet state is not at the last tabstop, push it back on the stack
9693 if snippet.active_index + 1 < snippet.ranges.len() {
9694 self.snippet_stack.push(snippet);
9695 }
9696 return true;
9697 }
9698 }
9699
9700 false
9701 }
9702
9703 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9704 self.transact(window, cx, |this, window, cx| {
9705 this.select_all(&SelectAll, window, cx);
9706 this.insert("", window, cx);
9707 });
9708 }
9709
9710 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9712 self.transact(window, cx, |this, window, cx| {
9713 this.select_autoclose_pair(window, cx);
9714 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9715 if !this.linked_edit_ranges.is_empty() {
9716 let selections = this.selections.all::<MultiBufferPoint>(cx);
9717 let snapshot = this.buffer.read(cx).snapshot(cx);
9718
9719 for selection in selections.iter() {
9720 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9721 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9722 if selection_start.buffer_id != selection_end.buffer_id {
9723 continue;
9724 }
9725 if let Some(ranges) =
9726 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9727 {
9728 for (buffer, entries) in ranges {
9729 linked_ranges.entry(buffer).or_default().extend(entries);
9730 }
9731 }
9732 }
9733 }
9734
9735 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9736 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9737 for selection in &mut selections {
9738 if selection.is_empty() {
9739 let old_head = selection.head();
9740 let mut new_head =
9741 movement::left(&display_map, old_head.to_display_point(&display_map))
9742 .to_point(&display_map);
9743 if let Some((buffer, line_buffer_range)) = display_map
9744 .buffer_snapshot
9745 .buffer_line_for_row(MultiBufferRow(old_head.row))
9746 {
9747 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9748 let indent_len = match indent_size.kind {
9749 IndentKind::Space => {
9750 buffer.settings_at(line_buffer_range.start, cx).tab_size
9751 }
9752 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9753 };
9754 if old_head.column <= indent_size.len && old_head.column > 0 {
9755 let indent_len = indent_len.get();
9756 new_head = cmp::min(
9757 new_head,
9758 MultiBufferPoint::new(
9759 old_head.row,
9760 ((old_head.column - 1) / indent_len) * indent_len,
9761 ),
9762 );
9763 }
9764 }
9765
9766 selection.set_head(new_head, SelectionGoal::None);
9767 }
9768 }
9769
9770 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9771 this.insert("", window, cx);
9772 let empty_str: Arc<str> = Arc::from("");
9773 for (buffer, edits) in linked_ranges {
9774 let snapshot = buffer.read(cx).snapshot();
9775 use text::ToPoint as TP;
9776
9777 let edits = edits
9778 .into_iter()
9779 .map(|range| {
9780 let end_point = TP::to_point(&range.end, &snapshot);
9781 let mut start_point = TP::to_point(&range.start, &snapshot);
9782
9783 if end_point == start_point {
9784 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9785 .saturating_sub(1);
9786 start_point =
9787 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9788 };
9789
9790 (start_point..end_point, empty_str.clone())
9791 })
9792 .sorted_by_key(|(range, _)| range.start)
9793 .collect::<Vec<_>>();
9794 buffer.update(cx, |this, cx| {
9795 this.edit(edits, None, cx);
9796 })
9797 }
9798 this.refresh_edit_prediction(true, false, window, cx);
9799 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9800 });
9801 }
9802
9803 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9804 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9805 self.transact(window, cx, |this, window, cx| {
9806 this.change_selections(Default::default(), window, cx, |s| {
9807 s.move_with(|map, selection| {
9808 if selection.is_empty() {
9809 let cursor = movement::right(map, selection.head());
9810 selection.end = cursor;
9811 selection.reversed = true;
9812 selection.goal = SelectionGoal::None;
9813 }
9814 })
9815 });
9816 this.insert("", window, cx);
9817 this.refresh_edit_prediction(true, false, window, cx);
9818 });
9819 }
9820
9821 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9822 if self.mode.is_single_line() {
9823 cx.propagate();
9824 return;
9825 }
9826
9827 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9828 if self.move_to_prev_snippet_tabstop(window, cx) {
9829 return;
9830 }
9831 self.outdent(&Outdent, window, cx);
9832 }
9833
9834 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9835 if self.mode.is_single_line() {
9836 cx.propagate();
9837 return;
9838 }
9839
9840 if self.move_to_next_snippet_tabstop(window, cx) {
9841 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9842 return;
9843 }
9844 if self.read_only(cx) {
9845 return;
9846 }
9847 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9848 let mut selections = self.selections.all_adjusted(cx);
9849 let buffer = self.buffer.read(cx);
9850 let snapshot = buffer.snapshot(cx);
9851 let rows_iter = selections.iter().map(|s| s.head().row);
9852 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9853
9854 let has_some_cursor_in_whitespace = selections
9855 .iter()
9856 .filter(|selection| selection.is_empty())
9857 .any(|selection| {
9858 let cursor = selection.head();
9859 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9860 cursor.column < current_indent.len
9861 });
9862
9863 let mut edits = Vec::new();
9864 let mut prev_edited_row = 0;
9865 let mut row_delta = 0;
9866 for selection in &mut selections {
9867 if selection.start.row != prev_edited_row {
9868 row_delta = 0;
9869 }
9870 prev_edited_row = selection.end.row;
9871
9872 // If the selection is non-empty, then increase the indentation of the selected lines.
9873 if !selection.is_empty() {
9874 row_delta =
9875 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9876 continue;
9877 }
9878
9879 let cursor = selection.head();
9880 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9881 if let Some(suggested_indent) =
9882 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9883 {
9884 // Don't do anything if already at suggested indent
9885 // and there is any other cursor which is not
9886 if has_some_cursor_in_whitespace
9887 && cursor.column == current_indent.len
9888 && current_indent.len == suggested_indent.len
9889 {
9890 continue;
9891 }
9892
9893 // Adjust line and move cursor to suggested indent
9894 // if cursor is not at suggested indent
9895 if cursor.column < suggested_indent.len
9896 && cursor.column <= current_indent.len
9897 && current_indent.len <= suggested_indent.len
9898 {
9899 selection.start = Point::new(cursor.row, suggested_indent.len);
9900 selection.end = selection.start;
9901 if row_delta == 0 {
9902 edits.extend(Buffer::edit_for_indent_size_adjustment(
9903 cursor.row,
9904 current_indent,
9905 suggested_indent,
9906 ));
9907 row_delta = suggested_indent.len - current_indent.len;
9908 }
9909 continue;
9910 }
9911
9912 // If current indent is more than suggested indent
9913 // only move cursor to current indent and skip indent
9914 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9915 selection.start = Point::new(cursor.row, current_indent.len);
9916 selection.end = selection.start;
9917 continue;
9918 }
9919 }
9920
9921 // Otherwise, insert a hard or soft tab.
9922 let settings = buffer.language_settings_at(cursor, cx);
9923 let tab_size = if settings.hard_tabs {
9924 IndentSize::tab()
9925 } else {
9926 let tab_size = settings.tab_size.get();
9927 let indent_remainder = snapshot
9928 .text_for_range(Point::new(cursor.row, 0)..cursor)
9929 .flat_map(str::chars)
9930 .fold(row_delta % tab_size, |counter: u32, c| {
9931 if c == '\t' {
9932 0
9933 } else {
9934 (counter + 1) % tab_size
9935 }
9936 });
9937
9938 let chars_to_next_tab_stop = tab_size - indent_remainder;
9939 IndentSize::spaces(chars_to_next_tab_stop)
9940 };
9941 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9942 selection.end = selection.start;
9943 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9944 row_delta += tab_size.len;
9945 }
9946
9947 self.transact(window, cx, |this, window, cx| {
9948 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9949 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9950 this.refresh_edit_prediction(true, false, window, cx);
9951 });
9952 }
9953
9954 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9955 if self.read_only(cx) {
9956 return;
9957 }
9958 if self.mode.is_single_line() {
9959 cx.propagate();
9960 return;
9961 }
9962
9963 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9964 let mut selections = self.selections.all::<Point>(cx);
9965 let mut prev_edited_row = 0;
9966 let mut row_delta = 0;
9967 let mut edits = Vec::new();
9968 let buffer = self.buffer.read(cx);
9969 let snapshot = buffer.snapshot(cx);
9970 for selection in &mut selections {
9971 if selection.start.row != prev_edited_row {
9972 row_delta = 0;
9973 }
9974 prev_edited_row = selection.end.row;
9975
9976 row_delta =
9977 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9978 }
9979
9980 self.transact(window, cx, |this, window, cx| {
9981 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9982 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9983 });
9984 }
9985
9986 fn indent_selection(
9987 buffer: &MultiBuffer,
9988 snapshot: &MultiBufferSnapshot,
9989 selection: &mut Selection<Point>,
9990 edits: &mut Vec<(Range<Point>, String)>,
9991 delta_for_start_row: u32,
9992 cx: &App,
9993 ) -> u32 {
9994 let settings = buffer.language_settings_at(selection.start, cx);
9995 let tab_size = settings.tab_size.get();
9996 let indent_kind = if settings.hard_tabs {
9997 IndentKind::Tab
9998 } else {
9999 IndentKind::Space
10000 };
10001 let mut start_row = selection.start.row;
10002 let mut end_row = selection.end.row + 1;
10003
10004 // If a selection ends at the beginning of a line, don't indent
10005 // that last line.
10006 if selection.end.column == 0 && selection.end.row > selection.start.row {
10007 end_row -= 1;
10008 }
10009
10010 // Avoid re-indenting a row that has already been indented by a
10011 // previous selection, but still update this selection's column
10012 // to reflect that indentation.
10013 if delta_for_start_row > 0 {
10014 start_row += 1;
10015 selection.start.column += delta_for_start_row;
10016 if selection.end.row == selection.start.row {
10017 selection.end.column += delta_for_start_row;
10018 }
10019 }
10020
10021 let mut delta_for_end_row = 0;
10022 let has_multiple_rows = start_row + 1 != end_row;
10023 for row in start_row..end_row {
10024 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10025 let indent_delta = match (current_indent.kind, indent_kind) {
10026 (IndentKind::Space, IndentKind::Space) => {
10027 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10028 IndentSize::spaces(columns_to_next_tab_stop)
10029 }
10030 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10031 (_, IndentKind::Tab) => IndentSize::tab(),
10032 };
10033
10034 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10035 0
10036 } else {
10037 selection.start.column
10038 };
10039 let row_start = Point::new(row, start);
10040 edits.push((
10041 row_start..row_start,
10042 indent_delta.chars().collect::<String>(),
10043 ));
10044
10045 // Update this selection's endpoints to reflect the indentation.
10046 if row == selection.start.row {
10047 selection.start.column += indent_delta.len;
10048 }
10049 if row == selection.end.row {
10050 selection.end.column += indent_delta.len;
10051 delta_for_end_row = indent_delta.len;
10052 }
10053 }
10054
10055 if selection.start.row == selection.end.row {
10056 delta_for_start_row + delta_for_end_row
10057 } else {
10058 delta_for_end_row
10059 }
10060 }
10061
10062 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10063 if self.read_only(cx) {
10064 return;
10065 }
10066 if self.mode.is_single_line() {
10067 cx.propagate();
10068 return;
10069 }
10070
10071 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10072 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10073 let selections = self.selections.all::<Point>(cx);
10074 let mut deletion_ranges = Vec::new();
10075 let mut last_outdent = None;
10076 {
10077 let buffer = self.buffer.read(cx);
10078 let snapshot = buffer.snapshot(cx);
10079 for selection in &selections {
10080 let settings = buffer.language_settings_at(selection.start, cx);
10081 let tab_size = settings.tab_size.get();
10082 let mut rows = selection.spanned_rows(false, &display_map);
10083
10084 // Avoid re-outdenting a row that has already been outdented by a
10085 // previous selection.
10086 if let Some(last_row) = last_outdent {
10087 if last_row == rows.start {
10088 rows.start = rows.start.next_row();
10089 }
10090 }
10091 let has_multiple_rows = rows.len() > 1;
10092 for row in rows.iter_rows() {
10093 let indent_size = snapshot.indent_size_for_line(row);
10094 if indent_size.len > 0 {
10095 let deletion_len = match indent_size.kind {
10096 IndentKind::Space => {
10097 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10098 if columns_to_prev_tab_stop == 0 {
10099 tab_size
10100 } else {
10101 columns_to_prev_tab_stop
10102 }
10103 }
10104 IndentKind::Tab => 1,
10105 };
10106 let start = if has_multiple_rows
10107 || deletion_len > selection.start.column
10108 || indent_size.len < selection.start.column
10109 {
10110 0
10111 } else {
10112 selection.start.column - deletion_len
10113 };
10114 deletion_ranges.push(
10115 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10116 );
10117 last_outdent = Some(row);
10118 }
10119 }
10120 }
10121 }
10122
10123 self.transact(window, cx, |this, window, cx| {
10124 this.buffer.update(cx, |buffer, cx| {
10125 let empty_str: Arc<str> = Arc::default();
10126 buffer.edit(
10127 deletion_ranges
10128 .into_iter()
10129 .map(|range| (range, empty_str.clone())),
10130 None,
10131 cx,
10132 );
10133 });
10134 let selections = this.selections.all::<usize>(cx);
10135 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10136 });
10137 }
10138
10139 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10140 if self.read_only(cx) {
10141 return;
10142 }
10143 if self.mode.is_single_line() {
10144 cx.propagate();
10145 return;
10146 }
10147
10148 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10149 let selections = self
10150 .selections
10151 .all::<usize>(cx)
10152 .into_iter()
10153 .map(|s| s.range());
10154
10155 self.transact(window, cx, |this, window, cx| {
10156 this.buffer.update(cx, |buffer, cx| {
10157 buffer.autoindent_ranges(selections, cx);
10158 });
10159 let selections = this.selections.all::<usize>(cx);
10160 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10161 });
10162 }
10163
10164 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10165 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10166 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10167 let selections = self.selections.all::<Point>(cx);
10168
10169 let mut new_cursors = Vec::new();
10170 let mut edit_ranges = Vec::new();
10171 let mut selections = selections.iter().peekable();
10172 while let Some(selection) = selections.next() {
10173 let mut rows = selection.spanned_rows(false, &display_map);
10174 let goal_display_column = selection.head().to_display_point(&display_map).column();
10175
10176 // Accumulate contiguous regions of rows that we want to delete.
10177 while let Some(next_selection) = selections.peek() {
10178 let next_rows = next_selection.spanned_rows(false, &display_map);
10179 if next_rows.start <= rows.end {
10180 rows.end = next_rows.end;
10181 selections.next().unwrap();
10182 } else {
10183 break;
10184 }
10185 }
10186
10187 let buffer = &display_map.buffer_snapshot;
10188 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10189 let edit_end;
10190 let cursor_buffer_row;
10191 if buffer.max_point().row >= rows.end.0 {
10192 // If there's a line after the range, delete the \n from the end of the row range
10193 // and position the cursor on the next line.
10194 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10195 cursor_buffer_row = rows.end;
10196 } else {
10197 // If there isn't a line after the range, delete the \n from the line before the
10198 // start of the row range and position the cursor there.
10199 edit_start = edit_start.saturating_sub(1);
10200 edit_end = buffer.len();
10201 cursor_buffer_row = rows.start.previous_row();
10202 }
10203
10204 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10205 *cursor.column_mut() =
10206 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10207
10208 new_cursors.push((
10209 selection.id,
10210 buffer.anchor_after(cursor.to_point(&display_map)),
10211 ));
10212 edit_ranges.push(edit_start..edit_end);
10213 }
10214
10215 self.transact(window, cx, |this, window, cx| {
10216 let buffer = this.buffer.update(cx, |buffer, cx| {
10217 let empty_str: Arc<str> = Arc::default();
10218 buffer.edit(
10219 edit_ranges
10220 .into_iter()
10221 .map(|range| (range, empty_str.clone())),
10222 None,
10223 cx,
10224 );
10225 buffer.snapshot(cx)
10226 });
10227 let new_selections = new_cursors
10228 .into_iter()
10229 .map(|(id, cursor)| {
10230 let cursor = cursor.to_point(&buffer);
10231 Selection {
10232 id,
10233 start: cursor,
10234 end: cursor,
10235 reversed: false,
10236 goal: SelectionGoal::None,
10237 }
10238 })
10239 .collect();
10240
10241 this.change_selections(Default::default(), window, cx, |s| {
10242 s.select(new_selections);
10243 });
10244 });
10245 }
10246
10247 pub fn join_lines_impl(
10248 &mut self,
10249 insert_whitespace: bool,
10250 window: &mut Window,
10251 cx: &mut Context<Self>,
10252 ) {
10253 if self.read_only(cx) {
10254 return;
10255 }
10256 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10257 for selection in self.selections.all::<Point>(cx) {
10258 let start = MultiBufferRow(selection.start.row);
10259 // Treat single line selections as if they include the next line. Otherwise this action
10260 // would do nothing for single line selections individual cursors.
10261 let end = if selection.start.row == selection.end.row {
10262 MultiBufferRow(selection.start.row + 1)
10263 } else {
10264 MultiBufferRow(selection.end.row)
10265 };
10266
10267 if let Some(last_row_range) = row_ranges.last_mut() {
10268 if start <= last_row_range.end {
10269 last_row_range.end = end;
10270 continue;
10271 }
10272 }
10273 row_ranges.push(start..end);
10274 }
10275
10276 let snapshot = self.buffer.read(cx).snapshot(cx);
10277 let mut cursor_positions = Vec::new();
10278 for row_range in &row_ranges {
10279 let anchor = snapshot.anchor_before(Point::new(
10280 row_range.end.previous_row().0,
10281 snapshot.line_len(row_range.end.previous_row()),
10282 ));
10283 cursor_positions.push(anchor..anchor);
10284 }
10285
10286 self.transact(window, cx, |this, window, cx| {
10287 for row_range in row_ranges.into_iter().rev() {
10288 for row in row_range.iter_rows().rev() {
10289 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10290 let next_line_row = row.next_row();
10291 let indent = snapshot.indent_size_for_line(next_line_row);
10292 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10293
10294 let replace =
10295 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10296 " "
10297 } else {
10298 ""
10299 };
10300
10301 this.buffer.update(cx, |buffer, cx| {
10302 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10303 });
10304 }
10305 }
10306
10307 this.change_selections(Default::default(), window, cx, |s| {
10308 s.select_anchor_ranges(cursor_positions)
10309 });
10310 });
10311 }
10312
10313 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10314 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10315 self.join_lines_impl(true, window, cx);
10316 }
10317
10318 pub fn sort_lines_case_sensitive(
10319 &mut self,
10320 _: &SortLinesCaseSensitive,
10321 window: &mut Window,
10322 cx: &mut Context<Self>,
10323 ) {
10324 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10325 }
10326
10327 pub fn sort_lines_by_length(
10328 &mut self,
10329 _: &SortLinesByLength,
10330 window: &mut Window,
10331 cx: &mut Context<Self>,
10332 ) {
10333 self.manipulate_immutable_lines(window, cx, |lines| {
10334 lines.sort_by_key(|&line| line.chars().count())
10335 })
10336 }
10337
10338 pub fn sort_lines_case_insensitive(
10339 &mut self,
10340 _: &SortLinesCaseInsensitive,
10341 window: &mut Window,
10342 cx: &mut Context<Self>,
10343 ) {
10344 self.manipulate_immutable_lines(window, cx, |lines| {
10345 lines.sort_by_key(|line| line.to_lowercase())
10346 })
10347 }
10348
10349 pub fn unique_lines_case_insensitive(
10350 &mut self,
10351 _: &UniqueLinesCaseInsensitive,
10352 window: &mut Window,
10353 cx: &mut Context<Self>,
10354 ) {
10355 self.manipulate_immutable_lines(window, cx, |lines| {
10356 let mut seen = HashSet::default();
10357 lines.retain(|line| seen.insert(line.to_lowercase()));
10358 })
10359 }
10360
10361 pub fn unique_lines_case_sensitive(
10362 &mut self,
10363 _: &UniqueLinesCaseSensitive,
10364 window: &mut Window,
10365 cx: &mut Context<Self>,
10366 ) {
10367 self.manipulate_immutable_lines(window, cx, |lines| {
10368 let mut seen = HashSet::default();
10369 lines.retain(|line| seen.insert(*line));
10370 })
10371 }
10372
10373 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10374 let Some(project) = self.project.clone() else {
10375 return;
10376 };
10377 self.reload(project, window, cx)
10378 .detach_and_notify_err(window, cx);
10379 }
10380
10381 pub fn restore_file(
10382 &mut self,
10383 _: &::git::RestoreFile,
10384 window: &mut Window,
10385 cx: &mut Context<Self>,
10386 ) {
10387 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10388 let mut buffer_ids = HashSet::default();
10389 let snapshot = self.buffer().read(cx).snapshot(cx);
10390 for selection in self.selections.all::<usize>(cx) {
10391 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10392 }
10393
10394 let buffer = self.buffer().read(cx);
10395 let ranges = buffer_ids
10396 .into_iter()
10397 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10398 .collect::<Vec<_>>();
10399
10400 self.restore_hunks_in_ranges(ranges, window, cx);
10401 }
10402
10403 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10404 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10405 let selections = self
10406 .selections
10407 .all(cx)
10408 .into_iter()
10409 .map(|s| s.range())
10410 .collect();
10411 self.restore_hunks_in_ranges(selections, window, cx);
10412 }
10413
10414 pub fn restore_hunks_in_ranges(
10415 &mut self,
10416 ranges: Vec<Range<Point>>,
10417 window: &mut Window,
10418 cx: &mut Context<Editor>,
10419 ) {
10420 let mut revert_changes = HashMap::default();
10421 let chunk_by = self
10422 .snapshot(window, cx)
10423 .hunks_for_ranges(ranges)
10424 .into_iter()
10425 .chunk_by(|hunk| hunk.buffer_id);
10426 for (buffer_id, hunks) in &chunk_by {
10427 let hunks = hunks.collect::<Vec<_>>();
10428 for hunk in &hunks {
10429 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10430 }
10431 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10432 }
10433 drop(chunk_by);
10434 if !revert_changes.is_empty() {
10435 self.transact(window, cx, |editor, window, cx| {
10436 editor.restore(revert_changes, window, cx);
10437 });
10438 }
10439 }
10440
10441 pub fn open_active_item_in_terminal(
10442 &mut self,
10443 _: &OpenInTerminal,
10444 window: &mut Window,
10445 cx: &mut Context<Self>,
10446 ) {
10447 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10448 let project_path = buffer.read(cx).project_path(cx)?;
10449 let project = self.project.as_ref()?.read(cx);
10450 let entry = project.entry_for_path(&project_path, cx)?;
10451 let parent = match &entry.canonical_path {
10452 Some(canonical_path) => canonical_path.to_path_buf(),
10453 None => project.absolute_path(&project_path, cx)?,
10454 }
10455 .parent()?
10456 .to_path_buf();
10457 Some(parent)
10458 }) {
10459 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10460 }
10461 }
10462
10463 fn set_breakpoint_context_menu(
10464 &mut self,
10465 display_row: DisplayRow,
10466 position: Option<Anchor>,
10467 clicked_point: gpui::Point<Pixels>,
10468 window: &mut Window,
10469 cx: &mut Context<Self>,
10470 ) {
10471 let source = self
10472 .buffer
10473 .read(cx)
10474 .snapshot(cx)
10475 .anchor_before(Point::new(display_row.0, 0u32));
10476
10477 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10478
10479 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10480 self,
10481 source,
10482 clicked_point,
10483 context_menu,
10484 window,
10485 cx,
10486 );
10487 }
10488
10489 fn add_edit_breakpoint_block(
10490 &mut self,
10491 anchor: Anchor,
10492 breakpoint: &Breakpoint,
10493 edit_action: BreakpointPromptEditAction,
10494 window: &mut Window,
10495 cx: &mut Context<Self>,
10496 ) {
10497 let weak_editor = cx.weak_entity();
10498 let bp_prompt = cx.new(|cx| {
10499 BreakpointPromptEditor::new(
10500 weak_editor,
10501 anchor,
10502 breakpoint.clone(),
10503 edit_action,
10504 window,
10505 cx,
10506 )
10507 });
10508
10509 let height = bp_prompt.update(cx, |this, cx| {
10510 this.prompt
10511 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10512 });
10513 let cloned_prompt = bp_prompt.clone();
10514 let blocks = vec![BlockProperties {
10515 style: BlockStyle::Sticky,
10516 placement: BlockPlacement::Above(anchor),
10517 height: Some(height),
10518 render: Arc::new(move |cx| {
10519 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10520 cloned_prompt.clone().into_any_element()
10521 }),
10522 priority: 0,
10523 }];
10524
10525 let focus_handle = bp_prompt.focus_handle(cx);
10526 window.focus(&focus_handle);
10527
10528 let block_ids = self.insert_blocks(blocks, None, cx);
10529 bp_prompt.update(cx, |prompt, _| {
10530 prompt.add_block_ids(block_ids);
10531 });
10532 }
10533
10534 pub(crate) fn breakpoint_at_row(
10535 &self,
10536 row: u32,
10537 window: &mut Window,
10538 cx: &mut Context<Self>,
10539 ) -> Option<(Anchor, Breakpoint)> {
10540 let snapshot = self.snapshot(window, cx);
10541 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10542
10543 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10544 }
10545
10546 pub(crate) fn breakpoint_at_anchor(
10547 &self,
10548 breakpoint_position: Anchor,
10549 snapshot: &EditorSnapshot,
10550 cx: &mut Context<Self>,
10551 ) -> Option<(Anchor, Breakpoint)> {
10552 let project = self.project.clone()?;
10553
10554 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10555 snapshot
10556 .buffer_snapshot
10557 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10558 })?;
10559
10560 let enclosing_excerpt = breakpoint_position.excerpt_id;
10561 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10562 let buffer_snapshot = buffer.read(cx).snapshot();
10563
10564 let row = buffer_snapshot
10565 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10566 .row;
10567
10568 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10569 let anchor_end = snapshot
10570 .buffer_snapshot
10571 .anchor_after(Point::new(row, line_len));
10572
10573 let bp = self
10574 .breakpoint_store
10575 .as_ref()?
10576 .read_with(cx, |breakpoint_store, cx| {
10577 breakpoint_store
10578 .breakpoints(
10579 &buffer,
10580 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10581 &buffer_snapshot,
10582 cx,
10583 )
10584 .next()
10585 .and_then(|(bp, _)| {
10586 let breakpoint_row = buffer_snapshot
10587 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10588 .row;
10589
10590 if breakpoint_row == row {
10591 snapshot
10592 .buffer_snapshot
10593 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10594 .map(|position| (position, bp.bp.clone()))
10595 } else {
10596 None
10597 }
10598 })
10599 });
10600 bp
10601 }
10602
10603 pub fn edit_log_breakpoint(
10604 &mut self,
10605 _: &EditLogBreakpoint,
10606 window: &mut Window,
10607 cx: &mut Context<Self>,
10608 ) {
10609 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10610 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10611 message: None,
10612 state: BreakpointState::Enabled,
10613 condition: None,
10614 hit_condition: None,
10615 });
10616
10617 self.add_edit_breakpoint_block(
10618 anchor,
10619 &breakpoint,
10620 BreakpointPromptEditAction::Log,
10621 window,
10622 cx,
10623 );
10624 }
10625 }
10626
10627 fn breakpoints_at_cursors(
10628 &self,
10629 window: &mut Window,
10630 cx: &mut Context<Self>,
10631 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10632 let snapshot = self.snapshot(window, cx);
10633 let cursors = self
10634 .selections
10635 .disjoint_anchors()
10636 .into_iter()
10637 .map(|selection| {
10638 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10639
10640 let breakpoint_position = self
10641 .breakpoint_at_row(cursor_position.row, window, cx)
10642 .map(|bp| bp.0)
10643 .unwrap_or_else(|| {
10644 snapshot
10645 .display_snapshot
10646 .buffer_snapshot
10647 .anchor_after(Point::new(cursor_position.row, 0))
10648 });
10649
10650 let breakpoint = self
10651 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10652 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10653
10654 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10655 })
10656 // 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.
10657 .collect::<HashMap<Anchor, _>>();
10658
10659 cursors.into_iter().collect()
10660 }
10661
10662 pub fn enable_breakpoint(
10663 &mut self,
10664 _: &crate::actions::EnableBreakpoint,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10669 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10670 continue;
10671 };
10672 self.edit_breakpoint_at_anchor(
10673 anchor,
10674 breakpoint,
10675 BreakpointEditAction::InvertState,
10676 cx,
10677 );
10678 }
10679 }
10680
10681 pub fn disable_breakpoint(
10682 &mut self,
10683 _: &crate::actions::DisableBreakpoint,
10684 window: &mut Window,
10685 cx: &mut Context<Self>,
10686 ) {
10687 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10688 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10689 continue;
10690 };
10691 self.edit_breakpoint_at_anchor(
10692 anchor,
10693 breakpoint,
10694 BreakpointEditAction::InvertState,
10695 cx,
10696 );
10697 }
10698 }
10699
10700 pub fn toggle_breakpoint(
10701 &mut self,
10702 _: &crate::actions::ToggleBreakpoint,
10703 window: &mut Window,
10704 cx: &mut Context<Self>,
10705 ) {
10706 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10707 if let Some(breakpoint) = breakpoint {
10708 self.edit_breakpoint_at_anchor(
10709 anchor,
10710 breakpoint,
10711 BreakpointEditAction::Toggle,
10712 cx,
10713 );
10714 } else {
10715 self.edit_breakpoint_at_anchor(
10716 anchor,
10717 Breakpoint::new_standard(),
10718 BreakpointEditAction::Toggle,
10719 cx,
10720 );
10721 }
10722 }
10723 }
10724
10725 pub fn edit_breakpoint_at_anchor(
10726 &mut self,
10727 breakpoint_position: Anchor,
10728 breakpoint: Breakpoint,
10729 edit_action: BreakpointEditAction,
10730 cx: &mut Context<Self>,
10731 ) {
10732 let Some(breakpoint_store) = &self.breakpoint_store else {
10733 return;
10734 };
10735
10736 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10737 if breakpoint_position == Anchor::min() {
10738 self.buffer()
10739 .read(cx)
10740 .excerpt_buffer_ids()
10741 .into_iter()
10742 .next()
10743 } else {
10744 None
10745 }
10746 }) else {
10747 return;
10748 };
10749
10750 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10751 return;
10752 };
10753
10754 breakpoint_store.update(cx, |breakpoint_store, cx| {
10755 breakpoint_store.toggle_breakpoint(
10756 buffer,
10757 BreakpointWithPosition {
10758 position: breakpoint_position.text_anchor,
10759 bp: breakpoint,
10760 },
10761 edit_action,
10762 cx,
10763 );
10764 });
10765
10766 cx.notify();
10767 }
10768
10769 #[cfg(any(test, feature = "test-support"))]
10770 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10771 self.breakpoint_store.clone()
10772 }
10773
10774 pub fn prepare_restore_change(
10775 &self,
10776 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10777 hunk: &MultiBufferDiffHunk,
10778 cx: &mut App,
10779 ) -> Option<()> {
10780 if hunk.is_created_file() {
10781 return None;
10782 }
10783 let buffer = self.buffer.read(cx);
10784 let diff = buffer.diff_for(hunk.buffer_id)?;
10785 let buffer = buffer.buffer(hunk.buffer_id)?;
10786 let buffer = buffer.read(cx);
10787 let original_text = diff
10788 .read(cx)
10789 .base_text()
10790 .as_rope()
10791 .slice(hunk.diff_base_byte_range.clone());
10792 let buffer_snapshot = buffer.snapshot();
10793 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10794 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10795 probe
10796 .0
10797 .start
10798 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10799 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10800 }) {
10801 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10802 Some(())
10803 } else {
10804 None
10805 }
10806 }
10807
10808 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10809 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10810 }
10811
10812 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10813 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10814 }
10815
10816 fn manipulate_lines<M>(
10817 &mut self,
10818 window: &mut Window,
10819 cx: &mut Context<Self>,
10820 mut manipulate: M,
10821 ) where
10822 M: FnMut(&str) -> LineManipulationResult,
10823 {
10824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10825
10826 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10827 let buffer = self.buffer.read(cx).snapshot(cx);
10828
10829 let mut edits = Vec::new();
10830
10831 let selections = self.selections.all::<Point>(cx);
10832 let mut selections = selections.iter().peekable();
10833 let mut contiguous_row_selections = Vec::new();
10834 let mut new_selections = Vec::new();
10835 let mut added_lines = 0;
10836 let mut removed_lines = 0;
10837
10838 while let Some(selection) = selections.next() {
10839 let (start_row, end_row) = consume_contiguous_rows(
10840 &mut contiguous_row_selections,
10841 selection,
10842 &display_map,
10843 &mut selections,
10844 );
10845
10846 let start_point = Point::new(start_row.0, 0);
10847 let end_point = Point::new(
10848 end_row.previous_row().0,
10849 buffer.line_len(end_row.previous_row()),
10850 );
10851 let text = buffer
10852 .text_for_range(start_point..end_point)
10853 .collect::<String>();
10854
10855 let LineManipulationResult {
10856 new_text,
10857 line_count_before,
10858 line_count_after,
10859 } = manipulate(&text);
10860
10861 edits.push((start_point..end_point, new_text));
10862
10863 // Selections must change based on added and removed line count
10864 let start_row =
10865 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10866 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10867 new_selections.push(Selection {
10868 id: selection.id,
10869 start: start_row,
10870 end: end_row,
10871 goal: SelectionGoal::None,
10872 reversed: selection.reversed,
10873 });
10874
10875 if line_count_after > line_count_before {
10876 added_lines += line_count_after - line_count_before;
10877 } else if line_count_before > line_count_after {
10878 removed_lines += line_count_before - line_count_after;
10879 }
10880 }
10881
10882 self.transact(window, cx, |this, window, cx| {
10883 let buffer = this.buffer.update(cx, |buffer, cx| {
10884 buffer.edit(edits, None, cx);
10885 buffer.snapshot(cx)
10886 });
10887
10888 // Recalculate offsets on newly edited buffer
10889 let new_selections = new_selections
10890 .iter()
10891 .map(|s| {
10892 let start_point = Point::new(s.start.0, 0);
10893 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10894 Selection {
10895 id: s.id,
10896 start: buffer.point_to_offset(start_point),
10897 end: buffer.point_to_offset(end_point),
10898 goal: s.goal,
10899 reversed: s.reversed,
10900 }
10901 })
10902 .collect();
10903
10904 this.change_selections(Default::default(), window, cx, |s| {
10905 s.select(new_selections);
10906 });
10907
10908 this.request_autoscroll(Autoscroll::fit(), cx);
10909 });
10910 }
10911
10912 fn manipulate_immutable_lines<Fn>(
10913 &mut self,
10914 window: &mut Window,
10915 cx: &mut Context<Self>,
10916 mut callback: Fn,
10917 ) where
10918 Fn: FnMut(&mut Vec<&str>),
10919 {
10920 self.manipulate_lines(window, cx, |text| {
10921 let mut lines: Vec<&str> = text.split('\n').collect();
10922 let line_count_before = lines.len();
10923
10924 callback(&mut lines);
10925
10926 LineManipulationResult {
10927 new_text: lines.join("\n"),
10928 line_count_before,
10929 line_count_after: lines.len(),
10930 }
10931 });
10932 }
10933
10934 fn manipulate_mutable_lines<Fn>(
10935 &mut self,
10936 window: &mut Window,
10937 cx: &mut Context<Self>,
10938 mut callback: Fn,
10939 ) where
10940 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10941 {
10942 self.manipulate_lines(window, cx, |text| {
10943 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10944 let line_count_before = lines.len();
10945
10946 callback(&mut lines);
10947
10948 LineManipulationResult {
10949 new_text: lines.join("\n"),
10950 line_count_before,
10951 line_count_after: lines.len(),
10952 }
10953 });
10954 }
10955
10956 pub fn convert_indentation_to_spaces(
10957 &mut self,
10958 _: &ConvertIndentationToSpaces,
10959 window: &mut Window,
10960 cx: &mut Context<Self>,
10961 ) {
10962 let settings = self.buffer.read(cx).language_settings(cx);
10963 let tab_size = settings.tab_size.get() as usize;
10964
10965 self.manipulate_mutable_lines(window, cx, |lines| {
10966 // Allocates a reasonably sized scratch buffer once for the whole loop
10967 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10968 // Avoids recomputing spaces that could be inserted many times
10969 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10970 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10971 .collect();
10972
10973 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10974 let mut chars = line.as_ref().chars();
10975 let mut col = 0;
10976 let mut changed = false;
10977
10978 while let Some(ch) = chars.next() {
10979 match ch {
10980 ' ' => {
10981 reindented_line.push(' ');
10982 col += 1;
10983 }
10984 '\t' => {
10985 // \t are converted to spaces depending on the current column
10986 let spaces_len = tab_size - (col % tab_size);
10987 reindented_line.extend(&space_cache[spaces_len - 1]);
10988 col += spaces_len;
10989 changed = true;
10990 }
10991 _ => {
10992 // If we dont append before break, the character is consumed
10993 reindented_line.push(ch);
10994 break;
10995 }
10996 }
10997 }
10998
10999 if !changed {
11000 reindented_line.clear();
11001 continue;
11002 }
11003 // Append the rest of the line and replace old reference with new one
11004 reindented_line.extend(chars);
11005 *line = Cow::Owned(reindented_line.clone());
11006 reindented_line.clear();
11007 }
11008 });
11009 }
11010
11011 pub fn convert_indentation_to_tabs(
11012 &mut self,
11013 _: &ConvertIndentationToTabs,
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 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.chars();
11030 let mut spaces_count = 0;
11031 let mut first_non_indent_char = None;
11032 let mut changed = false;
11033
11034 while let Some(ch) = chars.next() {
11035 match ch {
11036 ' ' => {
11037 // Keep track of spaces. Append \t when we reach tab_size
11038 spaces_count += 1;
11039 changed = true;
11040 if spaces_count == tab_size {
11041 reindented_line.push('\t');
11042 spaces_count = 0;
11043 }
11044 }
11045 '\t' => {
11046 reindented_line.push('\t');
11047 spaces_count = 0;
11048 }
11049 _ => {
11050 // Dont append it yet, we might have remaining spaces
11051 first_non_indent_char = Some(ch);
11052 break;
11053 }
11054 }
11055 }
11056
11057 if !changed {
11058 reindented_line.clear();
11059 continue;
11060 }
11061 // Remaining spaces that didn't make a full tab stop
11062 if spaces_count > 0 {
11063 reindented_line.extend(&space_cache[spaces_count - 1]);
11064 }
11065 // If we consume an extra character that was not indentation, add it back
11066 if let Some(extra_char) = first_non_indent_char {
11067 reindented_line.push(extra_char);
11068 }
11069 // Append the rest of the line and replace old reference with new one
11070 reindented_line.extend(chars);
11071 *line = Cow::Owned(reindented_line.clone());
11072 reindented_line.clear();
11073 }
11074 });
11075 }
11076
11077 pub fn convert_to_upper_case(
11078 &mut self,
11079 _: &ConvertToUpperCase,
11080 window: &mut Window,
11081 cx: &mut Context<Self>,
11082 ) {
11083 self.manipulate_text(window, cx, |text| text.to_uppercase())
11084 }
11085
11086 pub fn convert_to_lower_case(
11087 &mut self,
11088 _: &ConvertToLowerCase,
11089 window: &mut Window,
11090 cx: &mut Context<Self>,
11091 ) {
11092 self.manipulate_text(window, cx, |text| text.to_lowercase())
11093 }
11094
11095 pub fn convert_to_title_case(
11096 &mut self,
11097 _: &ConvertToTitleCase,
11098 window: &mut Window,
11099 cx: &mut Context<Self>,
11100 ) {
11101 self.manipulate_text(window, cx, |text| {
11102 text.split('\n')
11103 .map(|line| line.to_case(Case::Title))
11104 .join("\n")
11105 })
11106 }
11107
11108 pub fn convert_to_snake_case(
11109 &mut self,
11110 _: &ConvertToSnakeCase,
11111 window: &mut Window,
11112 cx: &mut Context<Self>,
11113 ) {
11114 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11115 }
11116
11117 pub fn convert_to_kebab_case(
11118 &mut self,
11119 _: &ConvertToKebabCase,
11120 window: &mut Window,
11121 cx: &mut Context<Self>,
11122 ) {
11123 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11124 }
11125
11126 pub fn convert_to_upper_camel_case(
11127 &mut self,
11128 _: &ConvertToUpperCamelCase,
11129 window: &mut Window,
11130 cx: &mut Context<Self>,
11131 ) {
11132 self.manipulate_text(window, cx, |text| {
11133 text.split('\n')
11134 .map(|line| line.to_case(Case::UpperCamel))
11135 .join("\n")
11136 })
11137 }
11138
11139 pub fn convert_to_lower_camel_case(
11140 &mut self,
11141 _: &ConvertToLowerCamelCase,
11142 window: &mut Window,
11143 cx: &mut Context<Self>,
11144 ) {
11145 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11146 }
11147
11148 pub fn convert_to_opposite_case(
11149 &mut self,
11150 _: &ConvertToOppositeCase,
11151 window: &mut Window,
11152 cx: &mut Context<Self>,
11153 ) {
11154 self.manipulate_text(window, cx, |text| {
11155 text.chars()
11156 .fold(String::with_capacity(text.len()), |mut t, c| {
11157 if c.is_uppercase() {
11158 t.extend(c.to_lowercase());
11159 } else {
11160 t.extend(c.to_uppercase());
11161 }
11162 t
11163 })
11164 })
11165 }
11166
11167 pub fn convert_to_sentence_case(
11168 &mut self,
11169 _: &ConvertToSentenceCase,
11170 window: &mut Window,
11171 cx: &mut Context<Self>,
11172 ) {
11173 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11174 }
11175
11176 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11177 self.manipulate_text(window, cx, |text| {
11178 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11179 if has_upper_case_characters {
11180 text.to_lowercase()
11181 } else {
11182 text.to_uppercase()
11183 }
11184 })
11185 }
11186
11187 pub fn convert_to_rot13(
11188 &mut self,
11189 _: &ConvertToRot13,
11190 window: &mut Window,
11191 cx: &mut Context<Self>,
11192 ) {
11193 self.manipulate_text(window, cx, |text| {
11194 text.chars()
11195 .map(|c| match c {
11196 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11197 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11198 _ => c,
11199 })
11200 .collect()
11201 })
11202 }
11203
11204 pub fn convert_to_rot47(
11205 &mut self,
11206 _: &ConvertToRot47,
11207 window: &mut Window,
11208 cx: &mut Context<Self>,
11209 ) {
11210 self.manipulate_text(window, cx, |text| {
11211 text.chars()
11212 .map(|c| {
11213 let code_point = c as u32;
11214 if code_point >= 33 && code_point <= 126 {
11215 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11216 }
11217 c
11218 })
11219 .collect()
11220 })
11221 }
11222
11223 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11224 where
11225 Fn: FnMut(&str) -> String,
11226 {
11227 let buffer = self.buffer.read(cx).snapshot(cx);
11228
11229 let mut new_selections = Vec::new();
11230 let mut edits = Vec::new();
11231 let mut selection_adjustment = 0i32;
11232
11233 for selection in self.selections.all::<usize>(cx) {
11234 let selection_is_empty = selection.is_empty();
11235
11236 let (start, end) = if selection_is_empty {
11237 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11238 (word_range.start, word_range.end)
11239 } else {
11240 (selection.start, selection.end)
11241 };
11242
11243 let text = buffer.text_for_range(start..end).collect::<String>();
11244 let old_length = text.len() as i32;
11245 let text = callback(&text);
11246
11247 new_selections.push(Selection {
11248 start: (start as i32 - selection_adjustment) as usize,
11249 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11250 goal: SelectionGoal::None,
11251 ..selection
11252 });
11253
11254 selection_adjustment += old_length - text.len() as i32;
11255
11256 edits.push((start..end, text));
11257 }
11258
11259 self.transact(window, cx, |this, window, cx| {
11260 this.buffer.update(cx, |buffer, cx| {
11261 buffer.edit(edits, None, cx);
11262 });
11263
11264 this.change_selections(Default::default(), window, cx, |s| {
11265 s.select(new_selections);
11266 });
11267
11268 this.request_autoscroll(Autoscroll::fit(), cx);
11269 });
11270 }
11271
11272 pub fn move_selection_on_drop(
11273 &mut self,
11274 selection: &Selection<Anchor>,
11275 target: DisplayPoint,
11276 is_cut: bool,
11277 window: &mut Window,
11278 cx: &mut Context<Self>,
11279 ) {
11280 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11281 let buffer = &display_map.buffer_snapshot;
11282 let mut edits = Vec::new();
11283 let insert_point = display_map
11284 .clip_point(target, Bias::Left)
11285 .to_point(&display_map);
11286 let text = buffer
11287 .text_for_range(selection.start..selection.end)
11288 .collect::<String>();
11289 if is_cut {
11290 edits.push(((selection.start..selection.end), String::new()));
11291 }
11292 let insert_anchor = buffer.anchor_before(insert_point);
11293 edits.push(((insert_anchor..insert_anchor), text));
11294 let last_edit_start = insert_anchor.bias_left(buffer);
11295 let last_edit_end = insert_anchor.bias_right(buffer);
11296 self.transact(window, cx, |this, window, cx| {
11297 this.buffer.update(cx, |buffer, cx| {
11298 buffer.edit(edits, None, cx);
11299 });
11300 this.change_selections(Default::default(), window, cx, |s| {
11301 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11302 });
11303 });
11304 }
11305
11306 pub fn clear_selection_drag_state(&mut self) {
11307 self.selection_drag_state = SelectionDragState::None;
11308 }
11309
11310 pub fn duplicate(
11311 &mut self,
11312 upwards: bool,
11313 whole_lines: bool,
11314 window: &mut Window,
11315 cx: &mut Context<Self>,
11316 ) {
11317 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11318
11319 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11320 let buffer = &display_map.buffer_snapshot;
11321 let selections = self.selections.all::<Point>(cx);
11322
11323 let mut edits = Vec::new();
11324 let mut selections_iter = selections.iter().peekable();
11325 while let Some(selection) = selections_iter.next() {
11326 let mut rows = selection.spanned_rows(false, &display_map);
11327 // duplicate line-wise
11328 if whole_lines || selection.start == selection.end {
11329 // Avoid duplicating the same lines twice.
11330 while let Some(next_selection) = selections_iter.peek() {
11331 let next_rows = next_selection.spanned_rows(false, &display_map);
11332 if next_rows.start < rows.end {
11333 rows.end = next_rows.end;
11334 selections_iter.next().unwrap();
11335 } else {
11336 break;
11337 }
11338 }
11339
11340 // Copy the text from the selected row region and splice it either at the start
11341 // or end of the region.
11342 let start = Point::new(rows.start.0, 0);
11343 let end = Point::new(
11344 rows.end.previous_row().0,
11345 buffer.line_len(rows.end.previous_row()),
11346 );
11347 let text = buffer
11348 .text_for_range(start..end)
11349 .chain(Some("\n"))
11350 .collect::<String>();
11351 let insert_location = if upwards {
11352 Point::new(rows.end.0, 0)
11353 } else {
11354 start
11355 };
11356 edits.push((insert_location..insert_location, text));
11357 } else {
11358 // duplicate character-wise
11359 let start = selection.start;
11360 let end = selection.end;
11361 let text = buffer.text_for_range(start..end).collect::<String>();
11362 edits.push((selection.end..selection.end, text));
11363 }
11364 }
11365
11366 self.transact(window, cx, |this, _, cx| {
11367 this.buffer.update(cx, |buffer, cx| {
11368 buffer.edit(edits, None, cx);
11369 });
11370
11371 this.request_autoscroll(Autoscroll::fit(), cx);
11372 });
11373 }
11374
11375 pub fn duplicate_line_up(
11376 &mut self,
11377 _: &DuplicateLineUp,
11378 window: &mut Window,
11379 cx: &mut Context<Self>,
11380 ) {
11381 self.duplicate(true, true, window, cx);
11382 }
11383
11384 pub fn duplicate_line_down(
11385 &mut self,
11386 _: &DuplicateLineDown,
11387 window: &mut Window,
11388 cx: &mut Context<Self>,
11389 ) {
11390 self.duplicate(false, true, window, cx);
11391 }
11392
11393 pub fn duplicate_selection(
11394 &mut self,
11395 _: &DuplicateSelection,
11396 window: &mut Window,
11397 cx: &mut Context<Self>,
11398 ) {
11399 self.duplicate(false, false, window, cx);
11400 }
11401
11402 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11403 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11404 if self.mode.is_single_line() {
11405 cx.propagate();
11406 return;
11407 }
11408
11409 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11410 let buffer = self.buffer.read(cx).snapshot(cx);
11411
11412 let mut edits = Vec::new();
11413 let mut unfold_ranges = Vec::new();
11414 let mut refold_creases = Vec::new();
11415
11416 let selections = self.selections.all::<Point>(cx);
11417 let mut selections = selections.iter().peekable();
11418 let mut contiguous_row_selections = Vec::new();
11419 let mut new_selections = Vec::new();
11420
11421 while let Some(selection) = selections.next() {
11422 // Find all the selections that span a contiguous row range
11423 let (start_row, end_row) = consume_contiguous_rows(
11424 &mut contiguous_row_selections,
11425 selection,
11426 &display_map,
11427 &mut selections,
11428 );
11429
11430 // Move the text spanned by the row range to be before the line preceding the row range
11431 if start_row.0 > 0 {
11432 let range_to_move = Point::new(
11433 start_row.previous_row().0,
11434 buffer.line_len(start_row.previous_row()),
11435 )
11436 ..Point::new(
11437 end_row.previous_row().0,
11438 buffer.line_len(end_row.previous_row()),
11439 );
11440 let insertion_point = display_map
11441 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11442 .0;
11443
11444 // Don't move lines across excerpts
11445 if buffer
11446 .excerpt_containing(insertion_point..range_to_move.end)
11447 .is_some()
11448 {
11449 let text = buffer
11450 .text_for_range(range_to_move.clone())
11451 .flat_map(|s| s.chars())
11452 .skip(1)
11453 .chain(['\n'])
11454 .collect::<String>();
11455
11456 edits.push((
11457 buffer.anchor_after(range_to_move.start)
11458 ..buffer.anchor_before(range_to_move.end),
11459 String::new(),
11460 ));
11461 let insertion_anchor = buffer.anchor_after(insertion_point);
11462 edits.push((insertion_anchor..insertion_anchor, text));
11463
11464 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11465
11466 // Move selections up
11467 new_selections.extend(contiguous_row_selections.drain(..).map(
11468 |mut selection| {
11469 selection.start.row -= row_delta;
11470 selection.end.row -= row_delta;
11471 selection
11472 },
11473 ));
11474
11475 // Move folds up
11476 unfold_ranges.push(range_to_move.clone());
11477 for fold in display_map.folds_in_range(
11478 buffer.anchor_before(range_to_move.start)
11479 ..buffer.anchor_after(range_to_move.end),
11480 ) {
11481 let mut start = fold.range.start.to_point(&buffer);
11482 let mut end = fold.range.end.to_point(&buffer);
11483 start.row -= row_delta;
11484 end.row -= row_delta;
11485 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11486 }
11487 }
11488 }
11489
11490 // If we didn't move line(s), preserve the existing selections
11491 new_selections.append(&mut contiguous_row_selections);
11492 }
11493
11494 self.transact(window, cx, |this, window, cx| {
11495 this.unfold_ranges(&unfold_ranges, true, true, cx);
11496 this.buffer.update(cx, |buffer, cx| {
11497 for (range, text) in edits {
11498 buffer.edit([(range, text)], None, cx);
11499 }
11500 });
11501 this.fold_creases(refold_creases, true, window, cx);
11502 this.change_selections(Default::default(), window, cx, |s| {
11503 s.select(new_selections);
11504 })
11505 });
11506 }
11507
11508 pub fn move_line_down(
11509 &mut self,
11510 _: &MoveLineDown,
11511 window: &mut Window,
11512 cx: &mut Context<Self>,
11513 ) {
11514 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11515 if self.mode.is_single_line() {
11516 cx.propagate();
11517 return;
11518 }
11519
11520 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11521 let buffer = self.buffer.read(cx).snapshot(cx);
11522
11523 let mut edits = Vec::new();
11524 let mut unfold_ranges = Vec::new();
11525 let mut refold_creases = Vec::new();
11526
11527 let selections = self.selections.all::<Point>(cx);
11528 let mut selections = selections.iter().peekable();
11529 let mut contiguous_row_selections = Vec::new();
11530 let mut new_selections = Vec::new();
11531
11532 while let Some(selection) = selections.next() {
11533 // Find all the selections that span a contiguous row range
11534 let (start_row, end_row) = consume_contiguous_rows(
11535 &mut contiguous_row_selections,
11536 selection,
11537 &display_map,
11538 &mut selections,
11539 );
11540
11541 // Move the text spanned by the row range to be after the last line of the row range
11542 if end_row.0 <= buffer.max_point().row {
11543 let range_to_move =
11544 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11545 let insertion_point = display_map
11546 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11547 .0;
11548
11549 // Don't move lines across excerpt boundaries
11550 if buffer
11551 .excerpt_containing(range_to_move.start..insertion_point)
11552 .is_some()
11553 {
11554 let mut text = String::from("\n");
11555 text.extend(buffer.text_for_range(range_to_move.clone()));
11556 text.pop(); // Drop trailing newline
11557 edits.push((
11558 buffer.anchor_after(range_to_move.start)
11559 ..buffer.anchor_before(range_to_move.end),
11560 String::new(),
11561 ));
11562 let insertion_anchor = buffer.anchor_after(insertion_point);
11563 edits.push((insertion_anchor..insertion_anchor, text));
11564
11565 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11566
11567 // Move selections down
11568 new_selections.extend(contiguous_row_selections.drain(..).map(
11569 |mut selection| {
11570 selection.start.row += row_delta;
11571 selection.end.row += row_delta;
11572 selection
11573 },
11574 ));
11575
11576 // Move folds down
11577 unfold_ranges.push(range_to_move.clone());
11578 for fold in display_map.folds_in_range(
11579 buffer.anchor_before(range_to_move.start)
11580 ..buffer.anchor_after(range_to_move.end),
11581 ) {
11582 let mut start = fold.range.start.to_point(&buffer);
11583 let mut end = fold.range.end.to_point(&buffer);
11584 start.row += row_delta;
11585 end.row += row_delta;
11586 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11587 }
11588 }
11589 }
11590
11591 // If we didn't move line(s), preserve the existing selections
11592 new_selections.append(&mut contiguous_row_selections);
11593 }
11594
11595 self.transact(window, cx, |this, window, cx| {
11596 this.unfold_ranges(&unfold_ranges, true, true, cx);
11597 this.buffer.update(cx, |buffer, cx| {
11598 for (range, text) in edits {
11599 buffer.edit([(range, text)], None, cx);
11600 }
11601 });
11602 this.fold_creases(refold_creases, true, window, cx);
11603 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11604 });
11605 }
11606
11607 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11609 let text_layout_details = &self.text_layout_details(window);
11610 self.transact(window, cx, |this, window, cx| {
11611 let edits = this.change_selections(Default::default(), window, cx, |s| {
11612 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11613 s.move_with(|display_map, selection| {
11614 if !selection.is_empty() {
11615 return;
11616 }
11617
11618 let mut head = selection.head();
11619 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11620 if head.column() == display_map.line_len(head.row()) {
11621 transpose_offset = display_map
11622 .buffer_snapshot
11623 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11624 }
11625
11626 if transpose_offset == 0 {
11627 return;
11628 }
11629
11630 *head.column_mut() += 1;
11631 head = display_map.clip_point(head, Bias::Right);
11632 let goal = SelectionGoal::HorizontalPosition(
11633 display_map
11634 .x_for_display_point(head, text_layout_details)
11635 .into(),
11636 );
11637 selection.collapse_to(head, goal);
11638
11639 let transpose_start = display_map
11640 .buffer_snapshot
11641 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11642 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11643 let transpose_end = display_map
11644 .buffer_snapshot
11645 .clip_offset(transpose_offset + 1, Bias::Right);
11646 if let Some(ch) =
11647 display_map.buffer_snapshot.chars_at(transpose_start).next()
11648 {
11649 edits.push((transpose_start..transpose_offset, String::new()));
11650 edits.push((transpose_end..transpose_end, ch.to_string()));
11651 }
11652 }
11653 });
11654 edits
11655 });
11656 this.buffer
11657 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11658 let selections = this.selections.all::<usize>(cx);
11659 this.change_selections(Default::default(), window, cx, |s| {
11660 s.select(selections);
11661 });
11662 });
11663 }
11664
11665 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11666 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11667 if self.mode.is_single_line() {
11668 cx.propagate();
11669 return;
11670 }
11671
11672 self.rewrap_impl(RewrapOptions::default(), cx)
11673 }
11674
11675 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11676 let buffer = self.buffer.read(cx).snapshot(cx);
11677 let selections = self.selections.all::<Point>(cx);
11678
11679 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11680 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11681 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11682 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11683 .peekable();
11684
11685 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11686 row
11687 } else {
11688 return Vec::new();
11689 };
11690
11691 let language_settings = buffer.language_settings_at(selection.head(), cx);
11692 let language_scope = buffer.language_scope_at(selection.head());
11693
11694 let indent_and_prefix_for_row =
11695 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11696 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11697 let (comment_prefix, rewrap_prefix) =
11698 if let Some(language_scope) = &language_scope {
11699 let indent_end = Point::new(row, indent.len);
11700 let comment_prefix = language_scope
11701 .line_comment_prefixes()
11702 .iter()
11703 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11704 .map(|prefix| prefix.to_string());
11705 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11706 let line_text_after_indent = buffer
11707 .text_for_range(indent_end..line_end)
11708 .collect::<String>();
11709 let rewrap_prefix = language_scope
11710 .rewrap_prefixes()
11711 .iter()
11712 .find_map(|prefix_regex| {
11713 prefix_regex.find(&line_text_after_indent).map(|mat| {
11714 if mat.start() == 0 {
11715 Some(mat.as_str().to_string())
11716 } else {
11717 None
11718 }
11719 })
11720 })
11721 .flatten();
11722 (comment_prefix, rewrap_prefix)
11723 } else {
11724 (None, None)
11725 };
11726 (indent, comment_prefix, rewrap_prefix)
11727 };
11728
11729 let mut ranges = Vec::new();
11730 let from_empty_selection = selection.is_empty();
11731
11732 let mut current_range_start = first_row;
11733 let mut prev_row = first_row;
11734 let (
11735 mut current_range_indent,
11736 mut current_range_comment_prefix,
11737 mut current_range_rewrap_prefix,
11738 ) = indent_and_prefix_for_row(first_row);
11739
11740 for row in non_blank_rows_iter.skip(1) {
11741 let has_paragraph_break = row > prev_row + 1;
11742
11743 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11744 indent_and_prefix_for_row(row);
11745
11746 let has_indent_change = row_indent != current_range_indent;
11747 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11748
11749 let has_boundary_change = has_comment_change
11750 || row_rewrap_prefix.is_some()
11751 || (has_indent_change && current_range_comment_prefix.is_some());
11752
11753 if has_paragraph_break || has_boundary_change {
11754 ranges.push((
11755 language_settings.clone(),
11756 Point::new(current_range_start, 0)
11757 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11758 current_range_indent,
11759 current_range_comment_prefix.clone(),
11760 current_range_rewrap_prefix.clone(),
11761 from_empty_selection,
11762 ));
11763 current_range_start = row;
11764 current_range_indent = row_indent;
11765 current_range_comment_prefix = row_comment_prefix;
11766 current_range_rewrap_prefix = row_rewrap_prefix;
11767 }
11768 prev_row = row;
11769 }
11770
11771 ranges.push((
11772 language_settings.clone(),
11773 Point::new(current_range_start, 0)
11774 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11775 current_range_indent,
11776 current_range_comment_prefix,
11777 current_range_rewrap_prefix,
11778 from_empty_selection,
11779 ));
11780
11781 ranges
11782 });
11783
11784 let mut edits = Vec::new();
11785 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11786
11787 for (
11788 language_settings,
11789 wrap_range,
11790 indent_size,
11791 comment_prefix,
11792 rewrap_prefix,
11793 from_empty_selection,
11794 ) in wrap_ranges
11795 {
11796 let mut start_row = wrap_range.start.row;
11797 let mut end_row = wrap_range.end.row;
11798
11799 // Skip selections that overlap with a range that has already been rewrapped.
11800 let selection_range = start_row..end_row;
11801 if rewrapped_row_ranges
11802 .iter()
11803 .any(|range| range.overlaps(&selection_range))
11804 {
11805 continue;
11806 }
11807
11808 let tab_size = language_settings.tab_size;
11809
11810 let indent_prefix = indent_size.chars().collect::<String>();
11811 let mut line_prefix = indent_prefix.clone();
11812 let mut inside_comment = false;
11813 if let Some(prefix) = &comment_prefix {
11814 line_prefix.push_str(prefix);
11815 inside_comment = true;
11816 }
11817 if let Some(prefix) = &rewrap_prefix {
11818 line_prefix.push_str(prefix);
11819 }
11820
11821 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11822 RewrapBehavior::InComments => inside_comment,
11823 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11824 RewrapBehavior::Anywhere => true,
11825 };
11826
11827 let should_rewrap = options.override_language_settings
11828 || allow_rewrap_based_on_language
11829 || self.hard_wrap.is_some();
11830 if !should_rewrap {
11831 continue;
11832 }
11833
11834 if from_empty_selection {
11835 'expand_upwards: while start_row > 0 {
11836 let prev_row = start_row - 1;
11837 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11838 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11839 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11840 {
11841 start_row = prev_row;
11842 } else {
11843 break 'expand_upwards;
11844 }
11845 }
11846
11847 'expand_downwards: while end_row < buffer.max_point().row {
11848 let next_row = end_row + 1;
11849 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11850 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11851 && !buffer.is_line_blank(MultiBufferRow(next_row))
11852 {
11853 end_row = next_row;
11854 } else {
11855 break 'expand_downwards;
11856 }
11857 }
11858 }
11859
11860 let start = Point::new(start_row, 0);
11861 let start_offset = start.to_offset(&buffer);
11862 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11863 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11864 let Some(lines_without_prefixes) = selection_text
11865 .lines()
11866 .enumerate()
11867 .map(|(ix, line)| {
11868 let line_trimmed = line.trim_start();
11869 if rewrap_prefix.is_some() && ix > 0 {
11870 Ok(line_trimmed)
11871 } else {
11872 line_trimmed
11873 .strip_prefix(&line_prefix.trim_start())
11874 .with_context(|| {
11875 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11876 })
11877 }
11878 })
11879 .collect::<Result<Vec<_>, _>>()
11880 .log_err()
11881 else {
11882 continue;
11883 };
11884
11885 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11886 buffer
11887 .language_settings_at(Point::new(start_row, 0), cx)
11888 .preferred_line_length as usize
11889 });
11890
11891 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11892 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11893 } else {
11894 line_prefix.clone()
11895 };
11896
11897 let wrapped_text = wrap_with_prefix(
11898 line_prefix,
11899 subsequent_lines_prefix,
11900 lines_without_prefixes.join("\n"),
11901 wrap_column,
11902 tab_size,
11903 options.preserve_existing_whitespace,
11904 );
11905
11906 // TODO: should always use char-based diff while still supporting cursor behavior that
11907 // matches vim.
11908 let mut diff_options = DiffOptions::default();
11909 if options.override_language_settings {
11910 diff_options.max_word_diff_len = 0;
11911 diff_options.max_word_diff_line_count = 0;
11912 } else {
11913 diff_options.max_word_diff_len = usize::MAX;
11914 diff_options.max_word_diff_line_count = usize::MAX;
11915 }
11916
11917 for (old_range, new_text) in
11918 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11919 {
11920 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11921 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11922 edits.push((edit_start..edit_end, new_text));
11923 }
11924
11925 rewrapped_row_ranges.push(start_row..=end_row);
11926 }
11927
11928 self.buffer
11929 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11930 }
11931
11932 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11933 let mut text = String::new();
11934 let buffer = self.buffer.read(cx).snapshot(cx);
11935 let mut selections = self.selections.all::<Point>(cx);
11936 let mut clipboard_selections = Vec::with_capacity(selections.len());
11937 {
11938 let max_point = buffer.max_point();
11939 let mut is_first = true;
11940 for selection in &mut selections {
11941 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11942 if is_entire_line {
11943 selection.start = Point::new(selection.start.row, 0);
11944 if !selection.is_empty() && selection.end.column == 0 {
11945 selection.end = cmp::min(max_point, selection.end);
11946 } else {
11947 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11948 }
11949 selection.goal = SelectionGoal::None;
11950 }
11951 if is_first {
11952 is_first = false;
11953 } else {
11954 text += "\n";
11955 }
11956 let mut len = 0;
11957 for chunk in buffer.text_for_range(selection.start..selection.end) {
11958 text.push_str(chunk);
11959 len += chunk.len();
11960 }
11961 clipboard_selections.push(ClipboardSelection {
11962 len,
11963 is_entire_line,
11964 first_line_indent: buffer
11965 .indent_size_for_line(MultiBufferRow(selection.start.row))
11966 .len,
11967 });
11968 }
11969 }
11970
11971 self.transact(window, cx, |this, window, cx| {
11972 this.change_selections(Default::default(), window, cx, |s| {
11973 s.select(selections);
11974 });
11975 this.insert("", window, cx);
11976 });
11977 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11978 }
11979
11980 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11981 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11982 let item = self.cut_common(window, cx);
11983 cx.write_to_clipboard(item);
11984 }
11985
11986 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11987 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11988 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11989 s.move_with(|snapshot, sel| {
11990 if sel.is_empty() {
11991 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11992 }
11993 });
11994 });
11995 let item = self.cut_common(window, cx);
11996 cx.set_global(KillRing(item))
11997 }
11998
11999 pub fn kill_ring_yank(
12000 &mut self,
12001 _: &KillRingYank,
12002 window: &mut Window,
12003 cx: &mut Context<Self>,
12004 ) {
12005 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12006 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12007 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12008 (kill_ring.text().to_string(), kill_ring.metadata_json())
12009 } else {
12010 return;
12011 }
12012 } else {
12013 return;
12014 };
12015 self.do_paste(&text, metadata, false, window, cx);
12016 }
12017
12018 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12019 self.do_copy(true, cx);
12020 }
12021
12022 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12023 self.do_copy(false, cx);
12024 }
12025
12026 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12027 let selections = self.selections.all::<Point>(cx);
12028 let buffer = self.buffer.read(cx).read(cx);
12029 let mut text = String::new();
12030
12031 let mut clipboard_selections = Vec::with_capacity(selections.len());
12032 {
12033 let max_point = buffer.max_point();
12034 let mut is_first = true;
12035 for selection in &selections {
12036 let mut start = selection.start;
12037 let mut end = selection.end;
12038 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12039 if is_entire_line {
12040 start = Point::new(start.row, 0);
12041 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12042 }
12043
12044 let mut trimmed_selections = Vec::new();
12045 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12046 let row = MultiBufferRow(start.row);
12047 let first_indent = buffer.indent_size_for_line(row);
12048 if first_indent.len == 0 || start.column > first_indent.len {
12049 trimmed_selections.push(start..end);
12050 } else {
12051 trimmed_selections.push(
12052 Point::new(row.0, first_indent.len)
12053 ..Point::new(row.0, buffer.line_len(row)),
12054 );
12055 for row in start.row + 1..=end.row {
12056 let mut line_len = buffer.line_len(MultiBufferRow(row));
12057 if row == end.row {
12058 line_len = end.column;
12059 }
12060 if line_len == 0 {
12061 trimmed_selections
12062 .push(Point::new(row, 0)..Point::new(row, line_len));
12063 continue;
12064 }
12065 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12066 if row_indent_size.len >= first_indent.len {
12067 trimmed_selections.push(
12068 Point::new(row, first_indent.len)..Point::new(row, line_len),
12069 );
12070 } else {
12071 trimmed_selections.clear();
12072 trimmed_selections.push(start..end);
12073 break;
12074 }
12075 }
12076 }
12077 } else {
12078 trimmed_selections.push(start..end);
12079 }
12080
12081 for trimmed_range in trimmed_selections {
12082 if is_first {
12083 is_first = false;
12084 } else {
12085 text += "\n";
12086 }
12087 let mut len = 0;
12088 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12089 text.push_str(chunk);
12090 len += chunk.len();
12091 }
12092 clipboard_selections.push(ClipboardSelection {
12093 len,
12094 is_entire_line,
12095 first_line_indent: buffer
12096 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12097 .len,
12098 });
12099 }
12100 }
12101 }
12102
12103 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12104 text,
12105 clipboard_selections,
12106 ));
12107 }
12108
12109 pub fn do_paste(
12110 &mut self,
12111 text: &String,
12112 clipboard_selections: Option<Vec<ClipboardSelection>>,
12113 handle_entire_lines: bool,
12114 window: &mut Window,
12115 cx: &mut Context<Self>,
12116 ) {
12117 if self.read_only(cx) {
12118 return;
12119 }
12120
12121 let clipboard_text = Cow::Borrowed(text);
12122
12123 self.transact(window, cx, |this, window, cx| {
12124 if let Some(mut clipboard_selections) = clipboard_selections {
12125 let old_selections = this.selections.all::<usize>(cx);
12126 let all_selections_were_entire_line =
12127 clipboard_selections.iter().all(|s| s.is_entire_line);
12128 let first_selection_indent_column =
12129 clipboard_selections.first().map(|s| s.first_line_indent);
12130 if clipboard_selections.len() != old_selections.len() {
12131 clipboard_selections.drain(..);
12132 }
12133 let cursor_offset = this.selections.last::<usize>(cx).head();
12134 let mut auto_indent_on_paste = true;
12135
12136 this.buffer.update(cx, |buffer, cx| {
12137 let snapshot = buffer.read(cx);
12138 auto_indent_on_paste = snapshot
12139 .language_settings_at(cursor_offset, cx)
12140 .auto_indent_on_paste;
12141
12142 let mut start_offset = 0;
12143 let mut edits = Vec::new();
12144 let mut original_indent_columns = Vec::new();
12145 for (ix, selection) in old_selections.iter().enumerate() {
12146 let to_insert;
12147 let entire_line;
12148 let original_indent_column;
12149 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12150 let end_offset = start_offset + clipboard_selection.len;
12151 to_insert = &clipboard_text[start_offset..end_offset];
12152 entire_line = clipboard_selection.is_entire_line;
12153 start_offset = end_offset + 1;
12154 original_indent_column = Some(clipboard_selection.first_line_indent);
12155 } else {
12156 to_insert = clipboard_text.as_str();
12157 entire_line = all_selections_were_entire_line;
12158 original_indent_column = first_selection_indent_column
12159 }
12160
12161 // If the corresponding selection was empty when this slice of the
12162 // clipboard text was written, then the entire line containing the
12163 // selection was copied. If this selection is also currently empty,
12164 // then paste the line before the current line of the buffer.
12165 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12166 let column = selection.start.to_point(&snapshot).column as usize;
12167 let line_start = selection.start - column;
12168 line_start..line_start
12169 } else {
12170 selection.range()
12171 };
12172
12173 edits.push((range, to_insert));
12174 original_indent_columns.push(original_indent_column);
12175 }
12176 drop(snapshot);
12177
12178 buffer.edit(
12179 edits,
12180 if auto_indent_on_paste {
12181 Some(AutoindentMode::Block {
12182 original_indent_columns,
12183 })
12184 } else {
12185 None
12186 },
12187 cx,
12188 );
12189 });
12190
12191 let selections = this.selections.all::<usize>(cx);
12192 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12193 } else {
12194 this.insert(&clipboard_text, window, cx);
12195 }
12196 });
12197 }
12198
12199 pub fn diff_clipboard_with_selection(
12200 &mut self,
12201 _: &DiffClipboardWithSelection,
12202 window: &mut Window,
12203 cx: &mut Context<Self>,
12204 ) {
12205 let selections = self.selections.all::<usize>(cx);
12206
12207 if selections.is_empty() {
12208 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12209 return;
12210 };
12211
12212 let clipboard_text = match cx.read_from_clipboard() {
12213 Some(item) => match item.entries().first() {
12214 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12215 _ => None,
12216 },
12217 None => None,
12218 };
12219
12220 let Some(clipboard_text) = clipboard_text else {
12221 log::warn!("Clipboard doesn't contain text.");
12222 return;
12223 };
12224
12225 window.dispatch_action(
12226 Box::new(DiffClipboardWithSelectionData {
12227 clipboard_text,
12228 editor: cx.entity(),
12229 }),
12230 cx,
12231 );
12232 }
12233
12234 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12235 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12236 if let Some(item) = cx.read_from_clipboard() {
12237 let entries = item.entries();
12238
12239 match entries.first() {
12240 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12241 // of all the pasted entries.
12242 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12243 .do_paste(
12244 clipboard_string.text(),
12245 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12246 true,
12247 window,
12248 cx,
12249 ),
12250 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12251 }
12252 }
12253 }
12254
12255 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12256 if self.read_only(cx) {
12257 return;
12258 }
12259
12260 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12261
12262 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12263 if let Some((selections, _)) =
12264 self.selection_history.transaction(transaction_id).cloned()
12265 {
12266 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12267 s.select_anchors(selections.to_vec());
12268 });
12269 } else {
12270 log::error!(
12271 "No entry in selection_history found for undo. \
12272 This may correspond to a bug where undo does not update the selection. \
12273 If this is occurring, please add details to \
12274 https://github.com/zed-industries/zed/issues/22692"
12275 );
12276 }
12277 self.request_autoscroll(Autoscroll::fit(), cx);
12278 self.unmark_text(window, cx);
12279 self.refresh_edit_prediction(true, false, window, cx);
12280 cx.emit(EditorEvent::Edited { transaction_id });
12281 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12282 }
12283 }
12284
12285 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12286 if self.read_only(cx) {
12287 return;
12288 }
12289
12290 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12291
12292 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12293 if let Some((_, Some(selections))) =
12294 self.selection_history.transaction(transaction_id).cloned()
12295 {
12296 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12297 s.select_anchors(selections.to_vec());
12298 });
12299 } else {
12300 log::error!(
12301 "No entry in selection_history found for redo. \
12302 This may correspond to a bug where undo does not update the selection. \
12303 If this is occurring, please add details to \
12304 https://github.com/zed-industries/zed/issues/22692"
12305 );
12306 }
12307 self.request_autoscroll(Autoscroll::fit(), cx);
12308 self.unmark_text(window, cx);
12309 self.refresh_edit_prediction(true, false, window, cx);
12310 cx.emit(EditorEvent::Edited { transaction_id });
12311 }
12312 }
12313
12314 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12315 self.buffer
12316 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12317 }
12318
12319 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12320 self.buffer
12321 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12322 }
12323
12324 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12325 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12326 self.change_selections(Default::default(), window, cx, |s| {
12327 s.move_with(|map, selection| {
12328 let cursor = if selection.is_empty() {
12329 movement::left(map, selection.start)
12330 } else {
12331 selection.start
12332 };
12333 selection.collapse_to(cursor, SelectionGoal::None);
12334 });
12335 })
12336 }
12337
12338 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12340 self.change_selections(Default::default(), window, cx, |s| {
12341 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12342 })
12343 }
12344
12345 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12346 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12347 self.change_selections(Default::default(), window, cx, |s| {
12348 s.move_with(|map, selection| {
12349 let cursor = if selection.is_empty() {
12350 movement::right(map, selection.end)
12351 } else {
12352 selection.end
12353 };
12354 selection.collapse_to(cursor, SelectionGoal::None)
12355 });
12356 })
12357 }
12358
12359 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12360 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12361 self.change_selections(Default::default(), window, cx, |s| {
12362 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12363 })
12364 }
12365
12366 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12367 if self.take_rename(true, window, cx).is_some() {
12368 return;
12369 }
12370
12371 if self.mode.is_single_line() {
12372 cx.propagate();
12373 return;
12374 }
12375
12376 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12377
12378 let text_layout_details = &self.text_layout_details(window);
12379 let selection_count = self.selections.count();
12380 let first_selection = self.selections.first_anchor();
12381
12382 self.change_selections(Default::default(), window, cx, |s| {
12383 s.move_with(|map, selection| {
12384 if !selection.is_empty() {
12385 selection.goal = SelectionGoal::None;
12386 }
12387 let (cursor, goal) = movement::up(
12388 map,
12389 selection.start,
12390 selection.goal,
12391 false,
12392 text_layout_details,
12393 );
12394 selection.collapse_to(cursor, goal);
12395 });
12396 });
12397
12398 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12399 {
12400 cx.propagate();
12401 }
12402 }
12403
12404 pub fn move_up_by_lines(
12405 &mut self,
12406 action: &MoveUpByLines,
12407 window: &mut Window,
12408 cx: &mut Context<Self>,
12409 ) {
12410 if self.take_rename(true, window, cx).is_some() {
12411 return;
12412 }
12413
12414 if self.mode.is_single_line() {
12415 cx.propagate();
12416 return;
12417 }
12418
12419 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12420
12421 let text_layout_details = &self.text_layout_details(window);
12422
12423 self.change_selections(Default::default(), window, cx, |s| {
12424 s.move_with(|map, selection| {
12425 if !selection.is_empty() {
12426 selection.goal = SelectionGoal::None;
12427 }
12428 let (cursor, goal) = movement::up_by_rows(
12429 map,
12430 selection.start,
12431 action.lines,
12432 selection.goal,
12433 false,
12434 text_layout_details,
12435 );
12436 selection.collapse_to(cursor, goal);
12437 });
12438 })
12439 }
12440
12441 pub fn move_down_by_lines(
12442 &mut self,
12443 action: &MoveDownByLines,
12444 window: &mut Window,
12445 cx: &mut Context<Self>,
12446 ) {
12447 if self.take_rename(true, window, cx).is_some() {
12448 return;
12449 }
12450
12451 if self.mode.is_single_line() {
12452 cx.propagate();
12453 return;
12454 }
12455
12456 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12457
12458 let text_layout_details = &self.text_layout_details(window);
12459
12460 self.change_selections(Default::default(), window, cx, |s| {
12461 s.move_with(|map, selection| {
12462 if !selection.is_empty() {
12463 selection.goal = SelectionGoal::None;
12464 }
12465 let (cursor, goal) = movement::down_by_rows(
12466 map,
12467 selection.start,
12468 action.lines,
12469 selection.goal,
12470 false,
12471 text_layout_details,
12472 );
12473 selection.collapse_to(cursor, goal);
12474 });
12475 })
12476 }
12477
12478 pub fn select_down_by_lines(
12479 &mut self,
12480 action: &SelectDownByLines,
12481 window: &mut Window,
12482 cx: &mut Context<Self>,
12483 ) {
12484 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12485 let text_layout_details = &self.text_layout_details(window);
12486 self.change_selections(Default::default(), window, cx, |s| {
12487 s.move_heads_with(|map, head, goal| {
12488 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12489 })
12490 })
12491 }
12492
12493 pub fn select_up_by_lines(
12494 &mut self,
12495 action: &SelectUpByLines,
12496 window: &mut Window,
12497 cx: &mut Context<Self>,
12498 ) {
12499 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12500 let text_layout_details = &self.text_layout_details(window);
12501 self.change_selections(Default::default(), window, cx, |s| {
12502 s.move_heads_with(|map, head, goal| {
12503 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12504 })
12505 })
12506 }
12507
12508 pub fn select_page_up(
12509 &mut self,
12510 _: &SelectPageUp,
12511 window: &mut Window,
12512 cx: &mut Context<Self>,
12513 ) {
12514 let Some(row_count) = self.visible_row_count() else {
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_heads_with(|map, head, goal| {
12524 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12525 })
12526 })
12527 }
12528
12529 pub fn move_page_up(
12530 &mut self,
12531 action: &MovePageUp,
12532 window: &mut Window,
12533 cx: &mut Context<Self>,
12534 ) {
12535 if self.take_rename(true, window, cx).is_some() {
12536 return;
12537 }
12538
12539 if self
12540 .context_menu
12541 .borrow_mut()
12542 .as_mut()
12543 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12544 .unwrap_or(false)
12545 {
12546 return;
12547 }
12548
12549 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12550 cx.propagate();
12551 return;
12552 }
12553
12554 let Some(row_count) = self.visible_row_count() else {
12555 return;
12556 };
12557
12558 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12559
12560 let effects = if action.center_cursor {
12561 SelectionEffects::scroll(Autoscroll::center())
12562 } else {
12563 SelectionEffects::default()
12564 };
12565
12566 let text_layout_details = &self.text_layout_details(window);
12567
12568 self.change_selections(effects, window, cx, |s| {
12569 s.move_with(|map, selection| {
12570 if !selection.is_empty() {
12571 selection.goal = SelectionGoal::None;
12572 }
12573 let (cursor, goal) = movement::up_by_rows(
12574 map,
12575 selection.end,
12576 row_count,
12577 selection.goal,
12578 false,
12579 text_layout_details,
12580 );
12581 selection.collapse_to(cursor, goal);
12582 });
12583 });
12584 }
12585
12586 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12587 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12588 let text_layout_details = &self.text_layout_details(window);
12589 self.change_selections(Default::default(), window, cx, |s| {
12590 s.move_heads_with(|map, head, goal| {
12591 movement::up(map, head, goal, false, text_layout_details)
12592 })
12593 })
12594 }
12595
12596 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12597 self.take_rename(true, window, cx);
12598
12599 if self.mode.is_single_line() {
12600 cx.propagate();
12601 return;
12602 }
12603
12604 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12605
12606 let text_layout_details = &self.text_layout_details(window);
12607 let selection_count = self.selections.count();
12608 let first_selection = self.selections.first_anchor();
12609
12610 self.change_selections(Default::default(), window, cx, |s| {
12611 s.move_with(|map, selection| {
12612 if !selection.is_empty() {
12613 selection.goal = SelectionGoal::None;
12614 }
12615 let (cursor, goal) = movement::down(
12616 map,
12617 selection.end,
12618 selection.goal,
12619 false,
12620 text_layout_details,
12621 );
12622 selection.collapse_to(cursor, goal);
12623 });
12624 });
12625
12626 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12627 {
12628 cx.propagate();
12629 }
12630 }
12631
12632 pub fn select_page_down(
12633 &mut self,
12634 _: &SelectPageDown,
12635 window: &mut Window,
12636 cx: &mut Context<Self>,
12637 ) {
12638 let Some(row_count) = self.visible_row_count() else {
12639 return;
12640 };
12641
12642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12643
12644 let text_layout_details = &self.text_layout_details(window);
12645
12646 self.change_selections(Default::default(), window, cx, |s| {
12647 s.move_heads_with(|map, head, goal| {
12648 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12649 })
12650 })
12651 }
12652
12653 pub fn move_page_down(
12654 &mut self,
12655 action: &MovePageDown,
12656 window: &mut Window,
12657 cx: &mut Context<Self>,
12658 ) {
12659 if self.take_rename(true, window, cx).is_some() {
12660 return;
12661 }
12662
12663 if self
12664 .context_menu
12665 .borrow_mut()
12666 .as_mut()
12667 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12668 .unwrap_or(false)
12669 {
12670 return;
12671 }
12672
12673 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12674 cx.propagate();
12675 return;
12676 }
12677
12678 let Some(row_count) = self.visible_row_count() else {
12679 return;
12680 };
12681
12682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12683
12684 let effects = if action.center_cursor {
12685 SelectionEffects::scroll(Autoscroll::center())
12686 } else {
12687 SelectionEffects::default()
12688 };
12689
12690 let text_layout_details = &self.text_layout_details(window);
12691 self.change_selections(effects, window, cx, |s| {
12692 s.move_with(|map, selection| {
12693 if !selection.is_empty() {
12694 selection.goal = SelectionGoal::None;
12695 }
12696 let (cursor, goal) = movement::down_by_rows(
12697 map,
12698 selection.end,
12699 row_count,
12700 selection.goal,
12701 false,
12702 text_layout_details,
12703 );
12704 selection.collapse_to(cursor, goal);
12705 });
12706 });
12707 }
12708
12709 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12710 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12711 let text_layout_details = &self.text_layout_details(window);
12712 self.change_selections(Default::default(), window, cx, |s| {
12713 s.move_heads_with(|map, head, goal| {
12714 movement::down(map, head, goal, false, text_layout_details)
12715 })
12716 });
12717 }
12718
12719 pub fn context_menu_first(
12720 &mut self,
12721 _: &ContextMenuFirst,
12722 window: &mut Window,
12723 cx: &mut Context<Self>,
12724 ) {
12725 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12726 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12727 }
12728 }
12729
12730 pub fn context_menu_prev(
12731 &mut self,
12732 _: &ContextMenuPrevious,
12733 window: &mut Window,
12734 cx: &mut Context<Self>,
12735 ) {
12736 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12737 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12738 }
12739 }
12740
12741 pub fn context_menu_next(
12742 &mut self,
12743 _: &ContextMenuNext,
12744 window: &mut Window,
12745 cx: &mut Context<Self>,
12746 ) {
12747 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12748 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12749 }
12750 }
12751
12752 pub fn context_menu_last(
12753 &mut self,
12754 _: &ContextMenuLast,
12755 window: &mut Window,
12756 cx: &mut Context<Self>,
12757 ) {
12758 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12759 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12760 }
12761 }
12762
12763 pub fn signature_help_prev(
12764 &mut self,
12765 _: &SignatureHelpPrevious,
12766 _: &mut Window,
12767 cx: &mut Context<Self>,
12768 ) {
12769 if let Some(popover) = self.signature_help_state.popover_mut() {
12770 if popover.current_signature == 0 {
12771 popover.current_signature = popover.signatures.len() - 1;
12772 } else {
12773 popover.current_signature -= 1;
12774 }
12775 cx.notify();
12776 }
12777 }
12778
12779 pub fn signature_help_next(
12780 &mut self,
12781 _: &SignatureHelpNext,
12782 _: &mut Window,
12783 cx: &mut Context<Self>,
12784 ) {
12785 if let Some(popover) = self.signature_help_state.popover_mut() {
12786 if popover.current_signature + 1 == popover.signatures.len() {
12787 popover.current_signature = 0;
12788 } else {
12789 popover.current_signature += 1;
12790 }
12791 cx.notify();
12792 }
12793 }
12794
12795 pub fn move_to_previous_word_start(
12796 &mut self,
12797 _: &MoveToPreviousWordStart,
12798 window: &mut Window,
12799 cx: &mut Context<Self>,
12800 ) {
12801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12802 self.change_selections(Default::default(), window, cx, |s| {
12803 s.move_cursors_with(|map, head, _| {
12804 (
12805 movement::previous_word_start(map, head),
12806 SelectionGoal::None,
12807 )
12808 });
12809 })
12810 }
12811
12812 pub fn move_to_previous_subword_start(
12813 &mut self,
12814 _: &MoveToPreviousSubwordStart,
12815 window: &mut Window,
12816 cx: &mut Context<Self>,
12817 ) {
12818 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12819 self.change_selections(Default::default(), window, cx, |s| {
12820 s.move_cursors_with(|map, head, _| {
12821 (
12822 movement::previous_subword_start(map, head),
12823 SelectionGoal::None,
12824 )
12825 });
12826 })
12827 }
12828
12829 pub fn select_to_previous_word_start(
12830 &mut self,
12831 _: &SelectToPreviousWordStart,
12832 window: &mut Window,
12833 cx: &mut Context<Self>,
12834 ) {
12835 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12836 self.change_selections(Default::default(), window, cx, |s| {
12837 s.move_heads_with(|map, head, _| {
12838 (
12839 movement::previous_word_start(map, head),
12840 SelectionGoal::None,
12841 )
12842 });
12843 })
12844 }
12845
12846 pub fn select_to_previous_subword_start(
12847 &mut self,
12848 _: &SelectToPreviousSubwordStart,
12849 window: &mut Window,
12850 cx: &mut Context<Self>,
12851 ) {
12852 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12853 self.change_selections(Default::default(), window, cx, |s| {
12854 s.move_heads_with(|map, head, _| {
12855 (
12856 movement::previous_subword_start(map, head),
12857 SelectionGoal::None,
12858 )
12859 });
12860 })
12861 }
12862
12863 pub fn delete_to_previous_word_start(
12864 &mut self,
12865 action: &DeleteToPreviousWordStart,
12866 window: &mut Window,
12867 cx: &mut Context<Self>,
12868 ) {
12869 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12870 self.transact(window, cx, |this, window, cx| {
12871 this.select_autoclose_pair(window, cx);
12872 this.change_selections(Default::default(), window, cx, |s| {
12873 s.move_with(|map, selection| {
12874 if selection.is_empty() {
12875 let cursor = if action.ignore_newlines {
12876 movement::previous_word_start(map, selection.head())
12877 } else {
12878 movement::previous_word_start_or_newline(map, selection.head())
12879 };
12880 selection.set_head(cursor, SelectionGoal::None);
12881 }
12882 });
12883 });
12884 this.insert("", window, cx);
12885 });
12886 }
12887
12888 pub fn delete_to_previous_subword_start(
12889 &mut self,
12890 _: &DeleteToPreviousSubwordStart,
12891 window: &mut Window,
12892 cx: &mut Context<Self>,
12893 ) {
12894 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12895 self.transact(window, cx, |this, window, cx| {
12896 this.select_autoclose_pair(window, cx);
12897 this.change_selections(Default::default(), window, cx, |s| {
12898 s.move_with(|map, selection| {
12899 if selection.is_empty() {
12900 let cursor = movement::previous_subword_start(map, selection.head());
12901 selection.set_head(cursor, SelectionGoal::None);
12902 }
12903 });
12904 });
12905 this.insert("", window, cx);
12906 });
12907 }
12908
12909 pub fn move_to_next_word_end(
12910 &mut self,
12911 _: &MoveToNextWordEnd,
12912 window: &mut Window,
12913 cx: &mut Context<Self>,
12914 ) {
12915 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12916 self.change_selections(Default::default(), window, cx, |s| {
12917 s.move_cursors_with(|map, head, _| {
12918 (movement::next_word_end(map, head), SelectionGoal::None)
12919 });
12920 })
12921 }
12922
12923 pub fn move_to_next_subword_end(
12924 &mut self,
12925 _: &MoveToNextSubwordEnd,
12926 window: &mut Window,
12927 cx: &mut Context<Self>,
12928 ) {
12929 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12930 self.change_selections(Default::default(), window, cx, |s| {
12931 s.move_cursors_with(|map, head, _| {
12932 (movement::next_subword_end(map, head), SelectionGoal::None)
12933 });
12934 })
12935 }
12936
12937 pub fn select_to_next_word_end(
12938 &mut self,
12939 _: &SelectToNextWordEnd,
12940 window: &mut Window,
12941 cx: &mut Context<Self>,
12942 ) {
12943 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12944 self.change_selections(Default::default(), window, cx, |s| {
12945 s.move_heads_with(|map, head, _| {
12946 (movement::next_word_end(map, head), SelectionGoal::None)
12947 });
12948 })
12949 }
12950
12951 pub fn select_to_next_subword_end(
12952 &mut self,
12953 _: &SelectToNextSubwordEnd,
12954 window: &mut Window,
12955 cx: &mut Context<Self>,
12956 ) {
12957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12958 self.change_selections(Default::default(), window, cx, |s| {
12959 s.move_heads_with(|map, head, _| {
12960 (movement::next_subword_end(map, head), SelectionGoal::None)
12961 });
12962 })
12963 }
12964
12965 pub fn delete_to_next_word_end(
12966 &mut self,
12967 action: &DeleteToNextWordEnd,
12968 window: &mut Window,
12969 cx: &mut Context<Self>,
12970 ) {
12971 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12972 self.transact(window, cx, |this, window, cx| {
12973 this.change_selections(Default::default(), window, cx, |s| {
12974 s.move_with(|map, selection| {
12975 if selection.is_empty() {
12976 let cursor = if action.ignore_newlines {
12977 movement::next_word_end(map, selection.head())
12978 } else {
12979 movement::next_word_end_or_newline(map, selection.head())
12980 };
12981 selection.set_head(cursor, SelectionGoal::None);
12982 }
12983 });
12984 });
12985 this.insert("", window, cx);
12986 });
12987 }
12988
12989 pub fn delete_to_next_subword_end(
12990 &mut self,
12991 _: &DeleteToNextSubwordEnd,
12992 window: &mut Window,
12993 cx: &mut Context<Self>,
12994 ) {
12995 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12996 self.transact(window, cx, |this, window, cx| {
12997 this.change_selections(Default::default(), window, cx, |s| {
12998 s.move_with(|map, selection| {
12999 if selection.is_empty() {
13000 let cursor = movement::next_subword_end(map, selection.head());
13001 selection.set_head(cursor, SelectionGoal::None);
13002 }
13003 });
13004 });
13005 this.insert("", window, cx);
13006 });
13007 }
13008
13009 pub fn move_to_beginning_of_line(
13010 &mut self,
13011 action: &MoveToBeginningOfLine,
13012 window: &mut Window,
13013 cx: &mut Context<Self>,
13014 ) {
13015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13016 self.change_selections(Default::default(), window, cx, |s| {
13017 s.move_cursors_with(|map, head, _| {
13018 (
13019 movement::indented_line_beginning(
13020 map,
13021 head,
13022 action.stop_at_soft_wraps,
13023 action.stop_at_indent,
13024 ),
13025 SelectionGoal::None,
13026 )
13027 });
13028 })
13029 }
13030
13031 pub fn select_to_beginning_of_line(
13032 &mut self,
13033 action: &SelectToBeginningOfLine,
13034 window: &mut Window,
13035 cx: &mut Context<Self>,
13036 ) {
13037 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13038 self.change_selections(Default::default(), window, cx, |s| {
13039 s.move_heads_with(|map, head, _| {
13040 (
13041 movement::indented_line_beginning(
13042 map,
13043 head,
13044 action.stop_at_soft_wraps,
13045 action.stop_at_indent,
13046 ),
13047 SelectionGoal::None,
13048 )
13049 });
13050 });
13051 }
13052
13053 pub fn delete_to_beginning_of_line(
13054 &mut self,
13055 action: &DeleteToBeginningOfLine,
13056 window: &mut Window,
13057 cx: &mut Context<Self>,
13058 ) {
13059 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13060 self.transact(window, cx, |this, window, cx| {
13061 this.change_selections(Default::default(), window, cx, |s| {
13062 s.move_with(|_, selection| {
13063 selection.reversed = true;
13064 });
13065 });
13066
13067 this.select_to_beginning_of_line(
13068 &SelectToBeginningOfLine {
13069 stop_at_soft_wraps: false,
13070 stop_at_indent: action.stop_at_indent,
13071 },
13072 window,
13073 cx,
13074 );
13075 this.backspace(&Backspace, window, cx);
13076 });
13077 }
13078
13079 pub fn move_to_end_of_line(
13080 &mut self,
13081 action: &MoveToEndOfLine,
13082 window: &mut Window,
13083 cx: &mut Context<Self>,
13084 ) {
13085 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13086 self.change_selections(Default::default(), window, cx, |s| {
13087 s.move_cursors_with(|map, head, _| {
13088 (
13089 movement::line_end(map, head, action.stop_at_soft_wraps),
13090 SelectionGoal::None,
13091 )
13092 });
13093 })
13094 }
13095
13096 pub fn select_to_end_of_line(
13097 &mut self,
13098 action: &SelectToEndOfLine,
13099 window: &mut Window,
13100 cx: &mut Context<Self>,
13101 ) {
13102 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13103 self.change_selections(Default::default(), window, cx, |s| {
13104 s.move_heads_with(|map, head, _| {
13105 (
13106 movement::line_end(map, head, action.stop_at_soft_wraps),
13107 SelectionGoal::None,
13108 )
13109 });
13110 })
13111 }
13112
13113 pub fn delete_to_end_of_line(
13114 &mut self,
13115 _: &DeleteToEndOfLine,
13116 window: &mut Window,
13117 cx: &mut Context<Self>,
13118 ) {
13119 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13120 self.transact(window, cx, |this, window, cx| {
13121 this.select_to_end_of_line(
13122 &SelectToEndOfLine {
13123 stop_at_soft_wraps: false,
13124 },
13125 window,
13126 cx,
13127 );
13128 this.delete(&Delete, window, cx);
13129 });
13130 }
13131
13132 pub fn cut_to_end_of_line(
13133 &mut self,
13134 _: &CutToEndOfLine,
13135 window: &mut Window,
13136 cx: &mut Context<Self>,
13137 ) {
13138 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13139 self.transact(window, cx, |this, window, cx| {
13140 this.select_to_end_of_line(
13141 &SelectToEndOfLine {
13142 stop_at_soft_wraps: false,
13143 },
13144 window,
13145 cx,
13146 );
13147 this.cut(&Cut, window, cx);
13148 });
13149 }
13150
13151 pub fn move_to_start_of_paragraph(
13152 &mut self,
13153 _: &MoveToStartOfParagraph,
13154 window: &mut Window,
13155 cx: &mut Context<Self>,
13156 ) {
13157 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13158 cx.propagate();
13159 return;
13160 }
13161 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13162 self.change_selections(Default::default(), window, cx, |s| {
13163 s.move_with(|map, selection| {
13164 selection.collapse_to(
13165 movement::start_of_paragraph(map, selection.head(), 1),
13166 SelectionGoal::None,
13167 )
13168 });
13169 })
13170 }
13171
13172 pub fn move_to_end_of_paragraph(
13173 &mut self,
13174 _: &MoveToEndOfParagraph,
13175 window: &mut Window,
13176 cx: &mut Context<Self>,
13177 ) {
13178 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13179 cx.propagate();
13180 return;
13181 }
13182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13183 self.change_selections(Default::default(), window, cx, |s| {
13184 s.move_with(|map, selection| {
13185 selection.collapse_to(
13186 movement::end_of_paragraph(map, selection.head(), 1),
13187 SelectionGoal::None,
13188 )
13189 });
13190 })
13191 }
13192
13193 pub fn select_to_start_of_paragraph(
13194 &mut self,
13195 _: &SelectToStartOfParagraph,
13196 window: &mut Window,
13197 cx: &mut Context<Self>,
13198 ) {
13199 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13200 cx.propagate();
13201 return;
13202 }
13203 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13204 self.change_selections(Default::default(), window, cx, |s| {
13205 s.move_heads_with(|map, head, _| {
13206 (
13207 movement::start_of_paragraph(map, head, 1),
13208 SelectionGoal::None,
13209 )
13210 });
13211 })
13212 }
13213
13214 pub fn select_to_end_of_paragraph(
13215 &mut self,
13216 _: &SelectToEndOfParagraph,
13217 window: &mut Window,
13218 cx: &mut Context<Self>,
13219 ) {
13220 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13221 cx.propagate();
13222 return;
13223 }
13224 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13225 self.change_selections(Default::default(), window, cx, |s| {
13226 s.move_heads_with(|map, head, _| {
13227 (
13228 movement::end_of_paragraph(map, head, 1),
13229 SelectionGoal::None,
13230 )
13231 });
13232 })
13233 }
13234
13235 pub fn move_to_start_of_excerpt(
13236 &mut self,
13237 _: &MoveToStartOfExcerpt,
13238 window: &mut Window,
13239 cx: &mut Context<Self>,
13240 ) {
13241 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13242 cx.propagate();
13243 return;
13244 }
13245 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13246 self.change_selections(Default::default(), window, cx, |s| {
13247 s.move_with(|map, selection| {
13248 selection.collapse_to(
13249 movement::start_of_excerpt(
13250 map,
13251 selection.head(),
13252 workspace::searchable::Direction::Prev,
13253 ),
13254 SelectionGoal::None,
13255 )
13256 });
13257 })
13258 }
13259
13260 pub fn move_to_start_of_next_excerpt(
13261 &mut self,
13262 _: &MoveToStartOfNextExcerpt,
13263 window: &mut Window,
13264 cx: &mut Context<Self>,
13265 ) {
13266 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13267 cx.propagate();
13268 return;
13269 }
13270
13271 self.change_selections(Default::default(), window, cx, |s| {
13272 s.move_with(|map, selection| {
13273 selection.collapse_to(
13274 movement::start_of_excerpt(
13275 map,
13276 selection.head(),
13277 workspace::searchable::Direction::Next,
13278 ),
13279 SelectionGoal::None,
13280 )
13281 });
13282 })
13283 }
13284
13285 pub fn move_to_end_of_excerpt(
13286 &mut self,
13287 _: &MoveToEndOfExcerpt,
13288 window: &mut Window,
13289 cx: &mut Context<Self>,
13290 ) {
13291 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13292 cx.propagate();
13293 return;
13294 }
13295 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13296 self.change_selections(Default::default(), window, cx, |s| {
13297 s.move_with(|map, selection| {
13298 selection.collapse_to(
13299 movement::end_of_excerpt(
13300 map,
13301 selection.head(),
13302 workspace::searchable::Direction::Next,
13303 ),
13304 SelectionGoal::None,
13305 )
13306 });
13307 })
13308 }
13309
13310 pub fn move_to_end_of_previous_excerpt(
13311 &mut self,
13312 _: &MoveToEndOfPreviousExcerpt,
13313 window: &mut Window,
13314 cx: &mut Context<Self>,
13315 ) {
13316 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13317 cx.propagate();
13318 return;
13319 }
13320 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13321 self.change_selections(Default::default(), window, cx, |s| {
13322 s.move_with(|map, selection| {
13323 selection.collapse_to(
13324 movement::end_of_excerpt(
13325 map,
13326 selection.head(),
13327 workspace::searchable::Direction::Prev,
13328 ),
13329 SelectionGoal::None,
13330 )
13331 });
13332 })
13333 }
13334
13335 pub fn select_to_start_of_excerpt(
13336 &mut self,
13337 _: &SelectToStartOfExcerpt,
13338 window: &mut Window,
13339 cx: &mut Context<Self>,
13340 ) {
13341 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13342 cx.propagate();
13343 return;
13344 }
13345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13346 self.change_selections(Default::default(), window, cx, |s| {
13347 s.move_heads_with(|map, head, _| {
13348 (
13349 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13350 SelectionGoal::None,
13351 )
13352 });
13353 })
13354 }
13355
13356 pub fn select_to_start_of_next_excerpt(
13357 &mut self,
13358 _: &SelectToStartOfNextExcerpt,
13359 window: &mut Window,
13360 cx: &mut Context<Self>,
13361 ) {
13362 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13363 cx.propagate();
13364 return;
13365 }
13366 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13367 self.change_selections(Default::default(), window, cx, |s| {
13368 s.move_heads_with(|map, head, _| {
13369 (
13370 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13371 SelectionGoal::None,
13372 )
13373 });
13374 })
13375 }
13376
13377 pub fn select_to_end_of_excerpt(
13378 &mut self,
13379 _: &SelectToEndOfExcerpt,
13380 window: &mut Window,
13381 cx: &mut Context<Self>,
13382 ) {
13383 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13384 cx.propagate();
13385 return;
13386 }
13387 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13388 self.change_selections(Default::default(), window, cx, |s| {
13389 s.move_heads_with(|map, head, _| {
13390 (
13391 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13392 SelectionGoal::None,
13393 )
13394 });
13395 })
13396 }
13397
13398 pub fn select_to_end_of_previous_excerpt(
13399 &mut self,
13400 _: &SelectToEndOfPreviousExcerpt,
13401 window: &mut Window,
13402 cx: &mut Context<Self>,
13403 ) {
13404 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13405 cx.propagate();
13406 return;
13407 }
13408 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13409 self.change_selections(Default::default(), window, cx, |s| {
13410 s.move_heads_with(|map, head, _| {
13411 (
13412 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13413 SelectionGoal::None,
13414 )
13415 });
13416 })
13417 }
13418
13419 pub fn move_to_beginning(
13420 &mut self,
13421 _: &MoveToBeginning,
13422 window: &mut Window,
13423 cx: &mut Context<Self>,
13424 ) {
13425 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13426 cx.propagate();
13427 return;
13428 }
13429 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13430 self.change_selections(Default::default(), window, cx, |s| {
13431 s.select_ranges(vec![0..0]);
13432 });
13433 }
13434
13435 pub fn select_to_beginning(
13436 &mut self,
13437 _: &SelectToBeginning,
13438 window: &mut Window,
13439 cx: &mut Context<Self>,
13440 ) {
13441 let mut selection = self.selections.last::<Point>(cx);
13442 selection.set_head(Point::zero(), SelectionGoal::None);
13443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13444 self.change_selections(Default::default(), window, cx, |s| {
13445 s.select(vec![selection]);
13446 });
13447 }
13448
13449 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13450 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13451 cx.propagate();
13452 return;
13453 }
13454 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13455 let cursor = self.buffer.read(cx).read(cx).len();
13456 self.change_selections(Default::default(), window, cx, |s| {
13457 s.select_ranges(vec![cursor..cursor])
13458 });
13459 }
13460
13461 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13462 self.nav_history = nav_history;
13463 }
13464
13465 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13466 self.nav_history.as_ref()
13467 }
13468
13469 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13470 self.push_to_nav_history(
13471 self.selections.newest_anchor().head(),
13472 None,
13473 false,
13474 true,
13475 cx,
13476 );
13477 }
13478
13479 fn push_to_nav_history(
13480 &mut self,
13481 cursor_anchor: Anchor,
13482 new_position: Option<Point>,
13483 is_deactivate: bool,
13484 always: bool,
13485 cx: &mut Context<Self>,
13486 ) {
13487 if let Some(nav_history) = self.nav_history.as_mut() {
13488 let buffer = self.buffer.read(cx).read(cx);
13489 let cursor_position = cursor_anchor.to_point(&buffer);
13490 let scroll_state = self.scroll_manager.anchor();
13491 let scroll_top_row = scroll_state.top_row(&buffer);
13492 drop(buffer);
13493
13494 if let Some(new_position) = new_position {
13495 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13496 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13497 return;
13498 }
13499 }
13500
13501 nav_history.push(
13502 Some(NavigationData {
13503 cursor_anchor,
13504 cursor_position,
13505 scroll_anchor: scroll_state,
13506 scroll_top_row,
13507 }),
13508 cx,
13509 );
13510 cx.emit(EditorEvent::PushedToNavHistory {
13511 anchor: cursor_anchor,
13512 is_deactivate,
13513 })
13514 }
13515 }
13516
13517 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13519 let buffer = self.buffer.read(cx).snapshot(cx);
13520 let mut selection = self.selections.first::<usize>(cx);
13521 selection.set_head(buffer.len(), SelectionGoal::None);
13522 self.change_selections(Default::default(), window, cx, |s| {
13523 s.select(vec![selection]);
13524 });
13525 }
13526
13527 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13528 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13529 let end = self.buffer.read(cx).read(cx).len();
13530 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13531 s.select_ranges(vec![0..end]);
13532 });
13533 }
13534
13535 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13536 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13537 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13538 let mut selections = self.selections.all::<Point>(cx);
13539 let max_point = display_map.buffer_snapshot.max_point();
13540 for selection in &mut selections {
13541 let rows = selection.spanned_rows(true, &display_map);
13542 selection.start = Point::new(rows.start.0, 0);
13543 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13544 selection.reversed = false;
13545 }
13546 self.change_selections(Default::default(), window, cx, |s| {
13547 s.select(selections);
13548 });
13549 }
13550
13551 pub fn split_selection_into_lines(
13552 &mut self,
13553 _: &SplitSelectionIntoLines,
13554 window: &mut Window,
13555 cx: &mut Context<Self>,
13556 ) {
13557 let selections = self
13558 .selections
13559 .all::<Point>(cx)
13560 .into_iter()
13561 .map(|selection| selection.start..selection.end)
13562 .collect::<Vec<_>>();
13563 self.unfold_ranges(&selections, true, true, cx);
13564
13565 let mut new_selection_ranges = Vec::new();
13566 {
13567 let buffer = self.buffer.read(cx).read(cx);
13568 for selection in selections {
13569 for row in selection.start.row..selection.end.row {
13570 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13571 new_selection_ranges.push(cursor..cursor);
13572 }
13573
13574 let is_multiline_selection = selection.start.row != selection.end.row;
13575 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13576 // so this action feels more ergonomic when paired with other selection operations
13577 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13578 if !should_skip_last {
13579 new_selection_ranges.push(selection.end..selection.end);
13580 }
13581 }
13582 }
13583 self.change_selections(Default::default(), window, cx, |s| {
13584 s.select_ranges(new_selection_ranges);
13585 });
13586 }
13587
13588 pub fn add_selection_above(
13589 &mut self,
13590 _: &AddSelectionAbove,
13591 window: &mut Window,
13592 cx: &mut Context<Self>,
13593 ) {
13594 self.add_selection(true, window, cx);
13595 }
13596
13597 pub fn add_selection_below(
13598 &mut self,
13599 _: &AddSelectionBelow,
13600 window: &mut Window,
13601 cx: &mut Context<Self>,
13602 ) {
13603 self.add_selection(false, window, cx);
13604 }
13605
13606 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13607 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13608
13609 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13610 let all_selections = self.selections.all::<Point>(cx);
13611 let text_layout_details = self.text_layout_details(window);
13612
13613 let (mut columnar_selections, new_selections_to_columnarize) = {
13614 if let Some(state) = self.add_selections_state.as_ref() {
13615 let columnar_selection_ids: HashSet<_> = state
13616 .groups
13617 .iter()
13618 .flat_map(|group| group.stack.iter())
13619 .copied()
13620 .collect();
13621
13622 all_selections
13623 .into_iter()
13624 .partition(|s| columnar_selection_ids.contains(&s.id))
13625 } else {
13626 (Vec::new(), all_selections)
13627 }
13628 };
13629
13630 let mut state = self
13631 .add_selections_state
13632 .take()
13633 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13634
13635 for selection in new_selections_to_columnarize {
13636 let range = selection.display_range(&display_map).sorted();
13637 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13638 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13639 let positions = start_x.min(end_x)..start_x.max(end_x);
13640 let mut stack = Vec::new();
13641 for row in range.start.row().0..=range.end.row().0 {
13642 if let Some(selection) = self.selections.build_columnar_selection(
13643 &display_map,
13644 DisplayRow(row),
13645 &positions,
13646 selection.reversed,
13647 &text_layout_details,
13648 ) {
13649 stack.push(selection.id);
13650 columnar_selections.push(selection);
13651 }
13652 }
13653 if !stack.is_empty() {
13654 if above {
13655 stack.reverse();
13656 }
13657 state.groups.push(AddSelectionsGroup { above, stack });
13658 }
13659 }
13660
13661 let mut final_selections = Vec::new();
13662 let end_row = if above {
13663 DisplayRow(0)
13664 } else {
13665 display_map.max_point().row()
13666 };
13667
13668 let mut last_added_item_per_group = HashMap::default();
13669 for group in state.groups.iter_mut() {
13670 if let Some(last_id) = group.stack.last() {
13671 last_added_item_per_group.insert(*last_id, group);
13672 }
13673 }
13674
13675 for selection in columnar_selections {
13676 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13677 if above == group.above {
13678 let range = selection.display_range(&display_map).sorted();
13679 debug_assert_eq!(range.start.row(), range.end.row());
13680 let mut row = range.start.row();
13681 let positions =
13682 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13683 px(start)..px(end)
13684 } else {
13685 let start_x =
13686 display_map.x_for_display_point(range.start, &text_layout_details);
13687 let end_x =
13688 display_map.x_for_display_point(range.end, &text_layout_details);
13689 start_x.min(end_x)..start_x.max(end_x)
13690 };
13691
13692 let mut maybe_new_selection = None;
13693 while row != end_row {
13694 if above {
13695 row.0 -= 1;
13696 } else {
13697 row.0 += 1;
13698 }
13699 if let Some(new_selection) = self.selections.build_columnar_selection(
13700 &display_map,
13701 row,
13702 &positions,
13703 selection.reversed,
13704 &text_layout_details,
13705 ) {
13706 maybe_new_selection = Some(new_selection);
13707 break;
13708 }
13709 }
13710
13711 if let Some(new_selection) = maybe_new_selection {
13712 group.stack.push(new_selection.id);
13713 if above {
13714 final_selections.push(new_selection);
13715 final_selections.push(selection);
13716 } else {
13717 final_selections.push(selection);
13718 final_selections.push(new_selection);
13719 }
13720 } else {
13721 final_selections.push(selection);
13722 }
13723 } else {
13724 group.stack.pop();
13725 }
13726 } else {
13727 final_selections.push(selection);
13728 }
13729 }
13730
13731 self.change_selections(Default::default(), window, cx, |s| {
13732 s.select(final_selections);
13733 });
13734
13735 let final_selection_ids: HashSet<_> = self
13736 .selections
13737 .all::<Point>(cx)
13738 .iter()
13739 .map(|s| s.id)
13740 .collect();
13741 state.groups.retain_mut(|group| {
13742 // selections might get merged above so we remove invalid items from stacks
13743 group.stack.retain(|id| final_selection_ids.contains(id));
13744
13745 // single selection in stack can be treated as initial state
13746 group.stack.len() > 1
13747 });
13748
13749 if !state.groups.is_empty() {
13750 self.add_selections_state = Some(state);
13751 }
13752 }
13753
13754 fn select_match_ranges(
13755 &mut self,
13756 range: Range<usize>,
13757 reversed: bool,
13758 replace_newest: bool,
13759 auto_scroll: Option<Autoscroll>,
13760 window: &mut Window,
13761 cx: &mut Context<Editor>,
13762 ) {
13763 self.unfold_ranges(
13764 std::slice::from_ref(&range),
13765 false,
13766 auto_scroll.is_some(),
13767 cx,
13768 );
13769 let effects = if let Some(scroll) = auto_scroll {
13770 SelectionEffects::scroll(scroll)
13771 } else {
13772 SelectionEffects::no_scroll()
13773 };
13774 self.change_selections(effects, window, cx, |s| {
13775 if replace_newest {
13776 s.delete(s.newest_anchor().id);
13777 }
13778 if reversed {
13779 s.insert_range(range.end..range.start);
13780 } else {
13781 s.insert_range(range);
13782 }
13783 });
13784 }
13785
13786 pub fn select_next_match_internal(
13787 &mut self,
13788 display_map: &DisplaySnapshot,
13789 replace_newest: bool,
13790 autoscroll: Option<Autoscroll>,
13791 window: &mut Window,
13792 cx: &mut Context<Self>,
13793 ) -> Result<()> {
13794 let buffer = &display_map.buffer_snapshot;
13795 let mut selections = self.selections.all::<usize>(cx);
13796 if let Some(mut select_next_state) = self.select_next_state.take() {
13797 let query = &select_next_state.query;
13798 if !select_next_state.done {
13799 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13800 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13801 let mut next_selected_range = None;
13802
13803 let bytes_after_last_selection =
13804 buffer.bytes_in_range(last_selection.end..buffer.len());
13805 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13806 let query_matches = query
13807 .stream_find_iter(bytes_after_last_selection)
13808 .map(|result| (last_selection.end, result))
13809 .chain(
13810 query
13811 .stream_find_iter(bytes_before_first_selection)
13812 .map(|result| (0, result)),
13813 );
13814
13815 for (start_offset, query_match) in query_matches {
13816 let query_match = query_match.unwrap(); // can only fail due to I/O
13817 let offset_range =
13818 start_offset + query_match.start()..start_offset + query_match.end();
13819
13820 if !select_next_state.wordwise
13821 || (!buffer.is_inside_word(offset_range.start, false)
13822 && !buffer.is_inside_word(offset_range.end, false))
13823 {
13824 // TODO: This is n^2, because we might check all the selections
13825 if !selections
13826 .iter()
13827 .any(|selection| selection.range().overlaps(&offset_range))
13828 {
13829 next_selected_range = Some(offset_range);
13830 break;
13831 }
13832 }
13833 }
13834
13835 if let Some(next_selected_range) = next_selected_range {
13836 self.select_match_ranges(
13837 next_selected_range,
13838 last_selection.reversed,
13839 replace_newest,
13840 autoscroll,
13841 window,
13842 cx,
13843 );
13844 } else {
13845 select_next_state.done = true;
13846 }
13847 }
13848
13849 self.select_next_state = Some(select_next_state);
13850 } else {
13851 let mut only_carets = true;
13852 let mut same_text_selected = true;
13853 let mut selected_text = None;
13854
13855 let mut selections_iter = selections.iter().peekable();
13856 while let Some(selection) = selections_iter.next() {
13857 if selection.start != selection.end {
13858 only_carets = false;
13859 }
13860
13861 if same_text_selected {
13862 if selected_text.is_none() {
13863 selected_text =
13864 Some(buffer.text_for_range(selection.range()).collect::<String>());
13865 }
13866
13867 if let Some(next_selection) = selections_iter.peek() {
13868 if next_selection.range().len() == selection.range().len() {
13869 let next_selected_text = buffer
13870 .text_for_range(next_selection.range())
13871 .collect::<String>();
13872 if Some(next_selected_text) != selected_text {
13873 same_text_selected = false;
13874 selected_text = None;
13875 }
13876 } else {
13877 same_text_selected = false;
13878 selected_text = None;
13879 }
13880 }
13881 }
13882 }
13883
13884 if only_carets {
13885 for selection in &mut selections {
13886 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13887 selection.start = word_range.start;
13888 selection.end = word_range.end;
13889 selection.goal = SelectionGoal::None;
13890 selection.reversed = false;
13891 self.select_match_ranges(
13892 selection.start..selection.end,
13893 selection.reversed,
13894 replace_newest,
13895 autoscroll,
13896 window,
13897 cx,
13898 );
13899 }
13900
13901 if selections.len() == 1 {
13902 let selection = selections
13903 .last()
13904 .expect("ensured that there's only one selection");
13905 let query = buffer
13906 .text_for_range(selection.start..selection.end)
13907 .collect::<String>();
13908 let is_empty = query.is_empty();
13909 let select_state = SelectNextState {
13910 query: AhoCorasick::new(&[query])?,
13911 wordwise: true,
13912 done: is_empty,
13913 };
13914 self.select_next_state = Some(select_state);
13915 } else {
13916 self.select_next_state = None;
13917 }
13918 } else if let Some(selected_text) = selected_text {
13919 self.select_next_state = Some(SelectNextState {
13920 query: AhoCorasick::new(&[selected_text])?,
13921 wordwise: false,
13922 done: false,
13923 });
13924 self.select_next_match_internal(
13925 display_map,
13926 replace_newest,
13927 autoscroll,
13928 window,
13929 cx,
13930 )?;
13931 }
13932 }
13933 Ok(())
13934 }
13935
13936 pub fn select_all_matches(
13937 &mut self,
13938 _action: &SelectAllMatches,
13939 window: &mut Window,
13940 cx: &mut Context<Self>,
13941 ) -> Result<()> {
13942 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13943
13944 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13945
13946 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13947 let Some(select_next_state) = self.select_next_state.as_mut() else {
13948 return Ok(());
13949 };
13950 if select_next_state.done {
13951 return Ok(());
13952 }
13953
13954 let mut new_selections = Vec::new();
13955
13956 let reversed = self.selections.oldest::<usize>(cx).reversed;
13957 let buffer = &display_map.buffer_snapshot;
13958 let query_matches = select_next_state
13959 .query
13960 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13961
13962 for query_match in query_matches.into_iter() {
13963 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13964 let offset_range = if reversed {
13965 query_match.end()..query_match.start()
13966 } else {
13967 query_match.start()..query_match.end()
13968 };
13969
13970 if !select_next_state.wordwise
13971 || (!buffer.is_inside_word(offset_range.start, false)
13972 && !buffer.is_inside_word(offset_range.end, false))
13973 {
13974 new_selections.push(offset_range.start..offset_range.end);
13975 }
13976 }
13977
13978 select_next_state.done = true;
13979
13980 if new_selections.is_empty() {
13981 log::error!("bug: new_selections is empty in select_all_matches");
13982 return Ok(());
13983 }
13984
13985 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13986 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13987 selections.select_ranges(new_selections)
13988 });
13989
13990 Ok(())
13991 }
13992
13993 pub fn select_next(
13994 &mut self,
13995 action: &SelectNext,
13996 window: &mut Window,
13997 cx: &mut Context<Self>,
13998 ) -> Result<()> {
13999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14000 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14001 self.select_next_match_internal(
14002 &display_map,
14003 action.replace_newest,
14004 Some(Autoscroll::newest()),
14005 window,
14006 cx,
14007 )?;
14008 Ok(())
14009 }
14010
14011 pub fn select_previous(
14012 &mut self,
14013 action: &SelectPrevious,
14014 window: &mut Window,
14015 cx: &mut Context<Self>,
14016 ) -> Result<()> {
14017 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14018 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14019 let buffer = &display_map.buffer_snapshot;
14020 let mut selections = self.selections.all::<usize>(cx);
14021 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14022 let query = &select_prev_state.query;
14023 if !select_prev_state.done {
14024 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14025 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14026 let mut next_selected_range = None;
14027 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14028 let bytes_before_last_selection =
14029 buffer.reversed_bytes_in_range(0..last_selection.start);
14030 let bytes_after_first_selection =
14031 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14032 let query_matches = query
14033 .stream_find_iter(bytes_before_last_selection)
14034 .map(|result| (last_selection.start, result))
14035 .chain(
14036 query
14037 .stream_find_iter(bytes_after_first_selection)
14038 .map(|result| (buffer.len(), result)),
14039 );
14040 for (end_offset, query_match) in query_matches {
14041 let query_match = query_match.unwrap(); // can only fail due to I/O
14042 let offset_range =
14043 end_offset - query_match.end()..end_offset - query_match.start();
14044
14045 if !select_prev_state.wordwise
14046 || (!buffer.is_inside_word(offset_range.start, false)
14047 && !buffer.is_inside_word(offset_range.end, false))
14048 {
14049 next_selected_range = Some(offset_range);
14050 break;
14051 }
14052 }
14053
14054 if let Some(next_selected_range) = next_selected_range {
14055 self.select_match_ranges(
14056 next_selected_range,
14057 last_selection.reversed,
14058 action.replace_newest,
14059 Some(Autoscroll::newest()),
14060 window,
14061 cx,
14062 );
14063 } else {
14064 select_prev_state.done = true;
14065 }
14066 }
14067
14068 self.select_prev_state = Some(select_prev_state);
14069 } else {
14070 let mut only_carets = true;
14071 let mut same_text_selected = true;
14072 let mut selected_text = None;
14073
14074 let mut selections_iter = selections.iter().peekable();
14075 while let Some(selection) = selections_iter.next() {
14076 if selection.start != selection.end {
14077 only_carets = false;
14078 }
14079
14080 if same_text_selected {
14081 if selected_text.is_none() {
14082 selected_text =
14083 Some(buffer.text_for_range(selection.range()).collect::<String>());
14084 }
14085
14086 if let Some(next_selection) = selections_iter.peek() {
14087 if next_selection.range().len() == selection.range().len() {
14088 let next_selected_text = buffer
14089 .text_for_range(next_selection.range())
14090 .collect::<String>();
14091 if Some(next_selected_text) != selected_text {
14092 same_text_selected = false;
14093 selected_text = None;
14094 }
14095 } else {
14096 same_text_selected = false;
14097 selected_text = None;
14098 }
14099 }
14100 }
14101 }
14102
14103 if only_carets {
14104 for selection in &mut selections {
14105 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14106 selection.start = word_range.start;
14107 selection.end = word_range.end;
14108 selection.goal = SelectionGoal::None;
14109 selection.reversed = false;
14110 self.select_match_ranges(
14111 selection.start..selection.end,
14112 selection.reversed,
14113 action.replace_newest,
14114 Some(Autoscroll::newest()),
14115 window,
14116 cx,
14117 );
14118 }
14119 if selections.len() == 1 {
14120 let selection = selections
14121 .last()
14122 .expect("ensured that there's only one selection");
14123 let query = buffer
14124 .text_for_range(selection.start..selection.end)
14125 .collect::<String>();
14126 let is_empty = query.is_empty();
14127 let select_state = SelectNextState {
14128 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14129 wordwise: true,
14130 done: is_empty,
14131 };
14132 self.select_prev_state = Some(select_state);
14133 } else {
14134 self.select_prev_state = None;
14135 }
14136 } else if let Some(selected_text) = selected_text {
14137 self.select_prev_state = Some(SelectNextState {
14138 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14139 wordwise: false,
14140 done: false,
14141 });
14142 self.select_previous(action, window, cx)?;
14143 }
14144 }
14145 Ok(())
14146 }
14147
14148 pub fn find_next_match(
14149 &mut self,
14150 _: &FindNextMatch,
14151 window: &mut Window,
14152 cx: &mut Context<Self>,
14153 ) -> Result<()> {
14154 let selections = self.selections.disjoint_anchors();
14155 match selections.first() {
14156 Some(first) if selections.len() >= 2 => {
14157 self.change_selections(Default::default(), window, cx, |s| {
14158 s.select_ranges([first.range()]);
14159 });
14160 }
14161 _ => self.select_next(
14162 &SelectNext {
14163 replace_newest: true,
14164 },
14165 window,
14166 cx,
14167 )?,
14168 }
14169 Ok(())
14170 }
14171
14172 pub fn find_previous_match(
14173 &mut self,
14174 _: &FindPreviousMatch,
14175 window: &mut Window,
14176 cx: &mut Context<Self>,
14177 ) -> Result<()> {
14178 let selections = self.selections.disjoint_anchors();
14179 match selections.last() {
14180 Some(last) if selections.len() >= 2 => {
14181 self.change_selections(Default::default(), window, cx, |s| {
14182 s.select_ranges([last.range()]);
14183 });
14184 }
14185 _ => self.select_previous(
14186 &SelectPrevious {
14187 replace_newest: true,
14188 },
14189 window,
14190 cx,
14191 )?,
14192 }
14193 Ok(())
14194 }
14195
14196 pub fn toggle_comments(
14197 &mut self,
14198 action: &ToggleComments,
14199 window: &mut Window,
14200 cx: &mut Context<Self>,
14201 ) {
14202 if self.read_only(cx) {
14203 return;
14204 }
14205 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14206 let text_layout_details = &self.text_layout_details(window);
14207 self.transact(window, cx, |this, window, cx| {
14208 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14209 let mut edits = Vec::new();
14210 let mut selection_edit_ranges = Vec::new();
14211 let mut last_toggled_row = None;
14212 let snapshot = this.buffer.read(cx).read(cx);
14213 let empty_str: Arc<str> = Arc::default();
14214 let mut suffixes_inserted = Vec::new();
14215 let ignore_indent = action.ignore_indent;
14216
14217 fn comment_prefix_range(
14218 snapshot: &MultiBufferSnapshot,
14219 row: MultiBufferRow,
14220 comment_prefix: &str,
14221 comment_prefix_whitespace: &str,
14222 ignore_indent: bool,
14223 ) -> Range<Point> {
14224 let indent_size = if ignore_indent {
14225 0
14226 } else {
14227 snapshot.indent_size_for_line(row).len
14228 };
14229
14230 let start = Point::new(row.0, indent_size);
14231
14232 let mut line_bytes = snapshot
14233 .bytes_in_range(start..snapshot.max_point())
14234 .flatten()
14235 .copied();
14236
14237 // If this line currently begins with the line comment prefix, then record
14238 // the range containing the prefix.
14239 if line_bytes
14240 .by_ref()
14241 .take(comment_prefix.len())
14242 .eq(comment_prefix.bytes())
14243 {
14244 // Include any whitespace that matches the comment prefix.
14245 let matching_whitespace_len = line_bytes
14246 .zip(comment_prefix_whitespace.bytes())
14247 .take_while(|(a, b)| a == b)
14248 .count() as u32;
14249 let end = Point::new(
14250 start.row,
14251 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14252 );
14253 start..end
14254 } else {
14255 start..start
14256 }
14257 }
14258
14259 fn comment_suffix_range(
14260 snapshot: &MultiBufferSnapshot,
14261 row: MultiBufferRow,
14262 comment_suffix: &str,
14263 comment_suffix_has_leading_space: bool,
14264 ) -> Range<Point> {
14265 let end = Point::new(row.0, snapshot.line_len(row));
14266 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14267
14268 let mut line_end_bytes = snapshot
14269 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14270 .flatten()
14271 .copied();
14272
14273 let leading_space_len = if suffix_start_column > 0
14274 && line_end_bytes.next() == Some(b' ')
14275 && comment_suffix_has_leading_space
14276 {
14277 1
14278 } else {
14279 0
14280 };
14281
14282 // If this line currently begins with the line comment prefix, then record
14283 // the range containing the prefix.
14284 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14285 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14286 start..end
14287 } else {
14288 end..end
14289 }
14290 }
14291
14292 // TODO: Handle selections that cross excerpts
14293 for selection in &mut selections {
14294 let start_column = snapshot
14295 .indent_size_for_line(MultiBufferRow(selection.start.row))
14296 .len;
14297 let language = if let Some(language) =
14298 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14299 {
14300 language
14301 } else {
14302 continue;
14303 };
14304
14305 selection_edit_ranges.clear();
14306
14307 // If multiple selections contain a given row, avoid processing that
14308 // row more than once.
14309 let mut start_row = MultiBufferRow(selection.start.row);
14310 if last_toggled_row == Some(start_row) {
14311 start_row = start_row.next_row();
14312 }
14313 let end_row =
14314 if selection.end.row > selection.start.row && selection.end.column == 0 {
14315 MultiBufferRow(selection.end.row - 1)
14316 } else {
14317 MultiBufferRow(selection.end.row)
14318 };
14319 last_toggled_row = Some(end_row);
14320
14321 if start_row > end_row {
14322 continue;
14323 }
14324
14325 // If the language has line comments, toggle those.
14326 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14327
14328 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14329 if ignore_indent {
14330 full_comment_prefixes = full_comment_prefixes
14331 .into_iter()
14332 .map(|s| Arc::from(s.trim_end()))
14333 .collect();
14334 }
14335
14336 if !full_comment_prefixes.is_empty() {
14337 let first_prefix = full_comment_prefixes
14338 .first()
14339 .expect("prefixes is non-empty");
14340 let prefix_trimmed_lengths = full_comment_prefixes
14341 .iter()
14342 .map(|p| p.trim_end_matches(' ').len())
14343 .collect::<SmallVec<[usize; 4]>>();
14344
14345 let mut all_selection_lines_are_comments = true;
14346
14347 for row in start_row.0..=end_row.0 {
14348 let row = MultiBufferRow(row);
14349 if start_row < end_row && snapshot.is_line_blank(row) {
14350 continue;
14351 }
14352
14353 let prefix_range = full_comment_prefixes
14354 .iter()
14355 .zip(prefix_trimmed_lengths.iter().copied())
14356 .map(|(prefix, trimmed_prefix_len)| {
14357 comment_prefix_range(
14358 snapshot.deref(),
14359 row,
14360 &prefix[..trimmed_prefix_len],
14361 &prefix[trimmed_prefix_len..],
14362 ignore_indent,
14363 )
14364 })
14365 .max_by_key(|range| range.end.column - range.start.column)
14366 .expect("prefixes is non-empty");
14367
14368 if prefix_range.is_empty() {
14369 all_selection_lines_are_comments = false;
14370 }
14371
14372 selection_edit_ranges.push(prefix_range);
14373 }
14374
14375 if all_selection_lines_are_comments {
14376 edits.extend(
14377 selection_edit_ranges
14378 .iter()
14379 .cloned()
14380 .map(|range| (range, empty_str.clone())),
14381 );
14382 } else {
14383 let min_column = selection_edit_ranges
14384 .iter()
14385 .map(|range| range.start.column)
14386 .min()
14387 .unwrap_or(0);
14388 edits.extend(selection_edit_ranges.iter().map(|range| {
14389 let position = Point::new(range.start.row, min_column);
14390 (position..position, first_prefix.clone())
14391 }));
14392 }
14393 } else if let Some(BlockCommentConfig {
14394 start: full_comment_prefix,
14395 end: comment_suffix,
14396 ..
14397 }) = language.block_comment()
14398 {
14399 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14400 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14401 let prefix_range = comment_prefix_range(
14402 snapshot.deref(),
14403 start_row,
14404 comment_prefix,
14405 comment_prefix_whitespace,
14406 ignore_indent,
14407 );
14408 let suffix_range = comment_suffix_range(
14409 snapshot.deref(),
14410 end_row,
14411 comment_suffix.trim_start_matches(' '),
14412 comment_suffix.starts_with(' '),
14413 );
14414
14415 if prefix_range.is_empty() || suffix_range.is_empty() {
14416 edits.push((
14417 prefix_range.start..prefix_range.start,
14418 full_comment_prefix.clone(),
14419 ));
14420 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14421 suffixes_inserted.push((end_row, comment_suffix.len()));
14422 } else {
14423 edits.push((prefix_range, empty_str.clone()));
14424 edits.push((suffix_range, empty_str.clone()));
14425 }
14426 } else {
14427 continue;
14428 }
14429 }
14430
14431 drop(snapshot);
14432 this.buffer.update(cx, |buffer, cx| {
14433 buffer.edit(edits, None, cx);
14434 });
14435
14436 // Adjust selections so that they end before any comment suffixes that
14437 // were inserted.
14438 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14439 let mut selections = this.selections.all::<Point>(cx);
14440 let snapshot = this.buffer.read(cx).read(cx);
14441 for selection in &mut selections {
14442 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14443 match row.cmp(&MultiBufferRow(selection.end.row)) {
14444 Ordering::Less => {
14445 suffixes_inserted.next();
14446 continue;
14447 }
14448 Ordering::Greater => break,
14449 Ordering::Equal => {
14450 if selection.end.column == snapshot.line_len(row) {
14451 if selection.is_empty() {
14452 selection.start.column -= suffix_len as u32;
14453 }
14454 selection.end.column -= suffix_len as u32;
14455 }
14456 break;
14457 }
14458 }
14459 }
14460 }
14461
14462 drop(snapshot);
14463 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14464
14465 let selections = this.selections.all::<Point>(cx);
14466 let selections_on_single_row = selections.windows(2).all(|selections| {
14467 selections[0].start.row == selections[1].start.row
14468 && selections[0].end.row == selections[1].end.row
14469 && selections[0].start.row == selections[0].end.row
14470 });
14471 let selections_selecting = selections
14472 .iter()
14473 .any(|selection| selection.start != selection.end);
14474 let advance_downwards = action.advance_downwards
14475 && selections_on_single_row
14476 && !selections_selecting
14477 && !matches!(this.mode, EditorMode::SingleLine { .. });
14478
14479 if advance_downwards {
14480 let snapshot = this.buffer.read(cx).snapshot(cx);
14481
14482 this.change_selections(Default::default(), window, cx, |s| {
14483 s.move_cursors_with(|display_snapshot, display_point, _| {
14484 let mut point = display_point.to_point(display_snapshot);
14485 point.row += 1;
14486 point = snapshot.clip_point(point, Bias::Left);
14487 let display_point = point.to_display_point(display_snapshot);
14488 let goal = SelectionGoal::HorizontalPosition(
14489 display_snapshot
14490 .x_for_display_point(display_point, text_layout_details)
14491 .into(),
14492 );
14493 (display_point, goal)
14494 })
14495 });
14496 }
14497 });
14498 }
14499
14500 pub fn select_enclosing_symbol(
14501 &mut self,
14502 _: &SelectEnclosingSymbol,
14503 window: &mut Window,
14504 cx: &mut Context<Self>,
14505 ) {
14506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14507
14508 let buffer = self.buffer.read(cx).snapshot(cx);
14509 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14510
14511 fn update_selection(
14512 selection: &Selection<usize>,
14513 buffer_snap: &MultiBufferSnapshot,
14514 ) -> Option<Selection<usize>> {
14515 let cursor = selection.head();
14516 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14517 for symbol in symbols.iter().rev() {
14518 let start = symbol.range.start.to_offset(buffer_snap);
14519 let end = symbol.range.end.to_offset(buffer_snap);
14520 let new_range = start..end;
14521 if start < selection.start || end > selection.end {
14522 return Some(Selection {
14523 id: selection.id,
14524 start: new_range.start,
14525 end: new_range.end,
14526 goal: SelectionGoal::None,
14527 reversed: selection.reversed,
14528 });
14529 }
14530 }
14531 None
14532 }
14533
14534 let mut selected_larger_symbol = false;
14535 let new_selections = old_selections
14536 .iter()
14537 .map(|selection| match update_selection(selection, &buffer) {
14538 Some(new_selection) => {
14539 if new_selection.range() != selection.range() {
14540 selected_larger_symbol = true;
14541 }
14542 new_selection
14543 }
14544 None => selection.clone(),
14545 })
14546 .collect::<Vec<_>>();
14547
14548 if selected_larger_symbol {
14549 self.change_selections(Default::default(), window, cx, |s| {
14550 s.select(new_selections);
14551 });
14552 }
14553 }
14554
14555 pub fn select_larger_syntax_node(
14556 &mut self,
14557 _: &SelectLargerSyntaxNode,
14558 window: &mut Window,
14559 cx: &mut Context<Self>,
14560 ) {
14561 let Some(visible_row_count) = self.visible_row_count() else {
14562 return;
14563 };
14564 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14565 if old_selections.is_empty() {
14566 return;
14567 }
14568
14569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14570
14571 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14572 let buffer = self.buffer.read(cx).snapshot(cx);
14573
14574 let mut selected_larger_node = false;
14575 let mut new_selections = old_selections
14576 .iter()
14577 .map(|selection| {
14578 let old_range = selection.start..selection.end;
14579
14580 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14581 // manually select word at selection
14582 if ["string_content", "inline"].contains(&node.kind()) {
14583 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14584 // ignore if word is already selected
14585 if !word_range.is_empty() && old_range != word_range {
14586 let (last_word_range, _) =
14587 buffer.surrounding_word(old_range.end, false);
14588 // only select word if start and end point belongs to same word
14589 if word_range == last_word_range {
14590 selected_larger_node = true;
14591 return Selection {
14592 id: selection.id,
14593 start: word_range.start,
14594 end: word_range.end,
14595 goal: SelectionGoal::None,
14596 reversed: selection.reversed,
14597 };
14598 }
14599 }
14600 }
14601 }
14602
14603 let mut new_range = old_range.clone();
14604 while let Some((_node, containing_range)) =
14605 buffer.syntax_ancestor(new_range.clone())
14606 {
14607 new_range = match containing_range {
14608 MultiOrSingleBufferOffsetRange::Single(_) => break,
14609 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14610 };
14611 if !display_map.intersects_fold(new_range.start)
14612 && !display_map.intersects_fold(new_range.end)
14613 {
14614 break;
14615 }
14616 }
14617
14618 selected_larger_node |= new_range != old_range;
14619 Selection {
14620 id: selection.id,
14621 start: new_range.start,
14622 end: new_range.end,
14623 goal: SelectionGoal::None,
14624 reversed: selection.reversed,
14625 }
14626 })
14627 .collect::<Vec<_>>();
14628
14629 if !selected_larger_node {
14630 return; // don't put this call in the history
14631 }
14632
14633 // scroll based on transformation done to the last selection created by the user
14634 let (last_old, last_new) = old_selections
14635 .last()
14636 .zip(new_selections.last().cloned())
14637 .expect("old_selections isn't empty");
14638
14639 // revert selection
14640 let is_selection_reversed = {
14641 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14642 new_selections.last_mut().expect("checked above").reversed =
14643 should_newest_selection_be_reversed;
14644 should_newest_selection_be_reversed
14645 };
14646
14647 if selected_larger_node {
14648 self.select_syntax_node_history.disable_clearing = true;
14649 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14650 s.select(new_selections.clone());
14651 });
14652 self.select_syntax_node_history.disable_clearing = false;
14653 }
14654
14655 let start_row = last_new.start.to_display_point(&display_map).row().0;
14656 let end_row = last_new.end.to_display_point(&display_map).row().0;
14657 let selection_height = end_row - start_row + 1;
14658 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14659
14660 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14661 let scroll_behavior = if fits_on_the_screen {
14662 self.request_autoscroll(Autoscroll::fit(), cx);
14663 SelectSyntaxNodeScrollBehavior::FitSelection
14664 } else if is_selection_reversed {
14665 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14666 SelectSyntaxNodeScrollBehavior::CursorTop
14667 } else {
14668 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14669 SelectSyntaxNodeScrollBehavior::CursorBottom
14670 };
14671
14672 self.select_syntax_node_history.push((
14673 old_selections,
14674 scroll_behavior,
14675 is_selection_reversed,
14676 ));
14677 }
14678
14679 pub fn select_smaller_syntax_node(
14680 &mut self,
14681 _: &SelectSmallerSyntaxNode,
14682 window: &mut Window,
14683 cx: &mut Context<Self>,
14684 ) {
14685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14686
14687 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14688 self.select_syntax_node_history.pop()
14689 {
14690 if let Some(selection) = selections.last_mut() {
14691 selection.reversed = is_selection_reversed;
14692 }
14693
14694 self.select_syntax_node_history.disable_clearing = true;
14695 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14696 s.select(selections.to_vec());
14697 });
14698 self.select_syntax_node_history.disable_clearing = false;
14699
14700 match scroll_behavior {
14701 SelectSyntaxNodeScrollBehavior::CursorTop => {
14702 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14703 }
14704 SelectSyntaxNodeScrollBehavior::FitSelection => {
14705 self.request_autoscroll(Autoscroll::fit(), cx);
14706 }
14707 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14708 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14709 }
14710 }
14711 }
14712 }
14713
14714 pub fn unwrap_syntax_node(
14715 &mut self,
14716 _: &UnwrapSyntaxNode,
14717 window: &mut Window,
14718 cx: &mut Context<Self>,
14719 ) {
14720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14721
14722 let buffer = self.buffer.read(cx).snapshot(cx);
14723 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14724
14725 let edits = old_selections
14726 .iter()
14727 // only consider the first selection for now
14728 .take(1)
14729 .map(|selection| {
14730 // Only requires two branches once if-let-chains stabilize (#53667)
14731 let selection_range = if !selection.is_empty() {
14732 selection.range()
14733 } else if let Some((_, ancestor_range)) =
14734 buffer.syntax_ancestor(selection.start..selection.end)
14735 {
14736 match ancestor_range {
14737 MultiOrSingleBufferOffsetRange::Single(range) => range,
14738 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14739 }
14740 } else {
14741 selection.range()
14742 };
14743
14744 let mut new_range = selection_range.clone();
14745 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(new_range.clone()) {
14746 new_range = match ancestor_range {
14747 MultiOrSingleBufferOffsetRange::Single(range) => range,
14748 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14749 };
14750 if new_range.start < selection_range.start
14751 || new_range.end > selection_range.end
14752 {
14753 break;
14754 }
14755 }
14756
14757 (selection, selection_range, new_range)
14758 })
14759 .collect::<Vec<_>>();
14760
14761 self.transact(window, cx, |editor, window, cx| {
14762 for (_, child, parent) in &edits {
14763 let text = buffer.text_for_range(child.clone()).collect::<String>();
14764 editor.replace_text_in_range(Some(parent.clone()), &text, window, cx);
14765 }
14766
14767 editor.change_selections(
14768 SelectionEffects::scroll(Autoscroll::fit()),
14769 window,
14770 cx,
14771 |s| {
14772 s.select(
14773 edits
14774 .iter()
14775 .map(|(s, old, new)| Selection {
14776 id: s.id,
14777 start: new.start,
14778 end: new.start + old.len(),
14779 goal: SelectionGoal::None,
14780 reversed: s.reversed,
14781 })
14782 .collect(),
14783 );
14784 },
14785 );
14786 });
14787 }
14788
14789 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14790 if !EditorSettings::get_global(cx).gutter.runnables {
14791 self.clear_tasks();
14792 return Task::ready(());
14793 }
14794 let project = self.project.as_ref().map(Entity::downgrade);
14795 let task_sources = self.lsp_task_sources(cx);
14796 let multi_buffer = self.buffer.downgrade();
14797 cx.spawn_in(window, async move |editor, cx| {
14798 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14799 let Some(project) = project.and_then(|p| p.upgrade()) else {
14800 return;
14801 };
14802 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14803 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14804 }) else {
14805 return;
14806 };
14807
14808 let hide_runnables = project
14809 .update(cx, |project, cx| {
14810 // Do not display any test indicators in non-dev server remote projects.
14811 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14812 })
14813 .unwrap_or(true);
14814 if hide_runnables {
14815 return;
14816 }
14817 let new_rows =
14818 cx.background_spawn({
14819 let snapshot = display_snapshot.clone();
14820 async move {
14821 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14822 }
14823 })
14824 .await;
14825 let Ok(lsp_tasks) =
14826 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14827 else {
14828 return;
14829 };
14830 let lsp_tasks = lsp_tasks.await;
14831
14832 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14833 lsp_tasks
14834 .into_iter()
14835 .flat_map(|(kind, tasks)| {
14836 tasks.into_iter().filter_map(move |(location, task)| {
14837 Some((kind.clone(), location?, task))
14838 })
14839 })
14840 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14841 let buffer = location.target.buffer;
14842 let buffer_snapshot = buffer.read(cx).snapshot();
14843 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14844 |(excerpt_id, snapshot, _)| {
14845 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14846 display_snapshot
14847 .buffer_snapshot
14848 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14849 } else {
14850 None
14851 }
14852 },
14853 );
14854 if let Some(offset) = offset {
14855 let task_buffer_range =
14856 location.target.range.to_point(&buffer_snapshot);
14857 let context_buffer_range =
14858 task_buffer_range.to_offset(&buffer_snapshot);
14859 let context_range = BufferOffset(context_buffer_range.start)
14860 ..BufferOffset(context_buffer_range.end);
14861
14862 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14863 .or_insert_with(|| RunnableTasks {
14864 templates: Vec::new(),
14865 offset,
14866 column: task_buffer_range.start.column,
14867 extra_variables: HashMap::default(),
14868 context_range,
14869 })
14870 .templates
14871 .push((kind, task.original_task().clone()));
14872 }
14873
14874 acc
14875 })
14876 }) else {
14877 return;
14878 };
14879
14880 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14881 buffer.language_settings(cx).tasks.prefer_lsp
14882 }) else {
14883 return;
14884 };
14885
14886 let rows = Self::runnable_rows(
14887 project,
14888 display_snapshot,
14889 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14890 new_rows,
14891 cx.clone(),
14892 )
14893 .await;
14894 editor
14895 .update(cx, |editor, _| {
14896 editor.clear_tasks();
14897 for (key, mut value) in rows {
14898 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14899 value.templates.extend(lsp_tasks.templates);
14900 }
14901
14902 editor.insert_tasks(key, value);
14903 }
14904 for (key, value) in lsp_tasks_by_rows {
14905 editor.insert_tasks(key, value);
14906 }
14907 })
14908 .ok();
14909 })
14910 }
14911 fn fetch_runnable_ranges(
14912 snapshot: &DisplaySnapshot,
14913 range: Range<Anchor>,
14914 ) -> Vec<language::RunnableRange> {
14915 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14916 }
14917
14918 fn runnable_rows(
14919 project: Entity<Project>,
14920 snapshot: DisplaySnapshot,
14921 prefer_lsp: bool,
14922 runnable_ranges: Vec<RunnableRange>,
14923 cx: AsyncWindowContext,
14924 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14925 cx.spawn(async move |cx| {
14926 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14927 for mut runnable in runnable_ranges {
14928 let Some(tasks) = cx
14929 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14930 .ok()
14931 else {
14932 continue;
14933 };
14934 let mut tasks = tasks.await;
14935
14936 if prefer_lsp {
14937 tasks.retain(|(task_kind, _)| {
14938 !matches!(task_kind, TaskSourceKind::Language { .. })
14939 });
14940 }
14941 if tasks.is_empty() {
14942 continue;
14943 }
14944
14945 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14946 let Some(row) = snapshot
14947 .buffer_snapshot
14948 .buffer_line_for_row(MultiBufferRow(point.row))
14949 .map(|(_, range)| range.start.row)
14950 else {
14951 continue;
14952 };
14953
14954 let context_range =
14955 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14956 runnable_rows.push((
14957 (runnable.buffer_id, row),
14958 RunnableTasks {
14959 templates: tasks,
14960 offset: snapshot
14961 .buffer_snapshot
14962 .anchor_before(runnable.run_range.start),
14963 context_range,
14964 column: point.column,
14965 extra_variables: runnable.extra_captures,
14966 },
14967 ));
14968 }
14969 runnable_rows
14970 })
14971 }
14972
14973 fn templates_with_tags(
14974 project: &Entity<Project>,
14975 runnable: &mut Runnable,
14976 cx: &mut App,
14977 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14978 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14979 let (worktree_id, file) = project
14980 .buffer_for_id(runnable.buffer, cx)
14981 .and_then(|buffer| buffer.read(cx).file())
14982 .map(|file| (file.worktree_id(cx), file.clone()))
14983 .unzip();
14984
14985 (
14986 project.task_store().read(cx).task_inventory().cloned(),
14987 worktree_id,
14988 file,
14989 )
14990 });
14991
14992 let tags = mem::take(&mut runnable.tags);
14993 let language = runnable.language.clone();
14994 cx.spawn(async move |cx| {
14995 let mut templates_with_tags = Vec::new();
14996 if let Some(inventory) = inventory {
14997 for RunnableTag(tag) in tags {
14998 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14999 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15000 }) else {
15001 return templates_with_tags;
15002 };
15003 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15004 move |(_, template)| {
15005 template.tags.iter().any(|source_tag| source_tag == &tag)
15006 },
15007 ));
15008 }
15009 }
15010 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15011
15012 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15013 // Strongest source wins; if we have worktree tag binding, prefer that to
15014 // global and language bindings;
15015 // if we have a global binding, prefer that to language binding.
15016 let first_mismatch = templates_with_tags
15017 .iter()
15018 .position(|(tag_source, _)| tag_source != leading_tag_source);
15019 if let Some(index) = first_mismatch {
15020 templates_with_tags.truncate(index);
15021 }
15022 }
15023
15024 templates_with_tags
15025 })
15026 }
15027
15028 pub fn move_to_enclosing_bracket(
15029 &mut self,
15030 _: &MoveToEnclosingBracket,
15031 window: &mut Window,
15032 cx: &mut Context<Self>,
15033 ) {
15034 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15035 self.change_selections(Default::default(), window, cx, |s| {
15036 s.move_offsets_with(|snapshot, selection| {
15037 let Some(enclosing_bracket_ranges) =
15038 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15039 else {
15040 return;
15041 };
15042
15043 let mut best_length = usize::MAX;
15044 let mut best_inside = false;
15045 let mut best_in_bracket_range = false;
15046 let mut best_destination = None;
15047 for (open, close) in enclosing_bracket_ranges {
15048 let close = close.to_inclusive();
15049 let length = close.end() - open.start;
15050 let inside = selection.start >= open.end && selection.end <= *close.start();
15051 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15052 || close.contains(&selection.head());
15053
15054 // If best is next to a bracket and current isn't, skip
15055 if !in_bracket_range && best_in_bracket_range {
15056 continue;
15057 }
15058
15059 // Prefer smaller lengths unless best is inside and current isn't
15060 if length > best_length && (best_inside || !inside) {
15061 continue;
15062 }
15063
15064 best_length = length;
15065 best_inside = inside;
15066 best_in_bracket_range = in_bracket_range;
15067 best_destination = Some(
15068 if close.contains(&selection.start) && close.contains(&selection.end) {
15069 if inside { open.end } else { open.start }
15070 } else if inside {
15071 *close.start()
15072 } else {
15073 *close.end()
15074 },
15075 );
15076 }
15077
15078 if let Some(destination) = best_destination {
15079 selection.collapse_to(destination, SelectionGoal::None);
15080 }
15081 })
15082 });
15083 }
15084
15085 pub fn undo_selection(
15086 &mut self,
15087 _: &UndoSelection,
15088 window: &mut Window,
15089 cx: &mut Context<Self>,
15090 ) {
15091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15092 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15093 self.selection_history.mode = SelectionHistoryMode::Undoing;
15094 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15095 this.end_selection(window, cx);
15096 this.change_selections(
15097 SelectionEffects::scroll(Autoscroll::newest()),
15098 window,
15099 cx,
15100 |s| s.select_anchors(entry.selections.to_vec()),
15101 );
15102 });
15103 self.selection_history.mode = SelectionHistoryMode::Normal;
15104
15105 self.select_next_state = entry.select_next_state;
15106 self.select_prev_state = entry.select_prev_state;
15107 self.add_selections_state = entry.add_selections_state;
15108 }
15109 }
15110
15111 pub fn redo_selection(
15112 &mut self,
15113 _: &RedoSelection,
15114 window: &mut Window,
15115 cx: &mut Context<Self>,
15116 ) {
15117 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15118 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15119 self.selection_history.mode = SelectionHistoryMode::Redoing;
15120 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15121 this.end_selection(window, cx);
15122 this.change_selections(
15123 SelectionEffects::scroll(Autoscroll::newest()),
15124 window,
15125 cx,
15126 |s| s.select_anchors(entry.selections.to_vec()),
15127 );
15128 });
15129 self.selection_history.mode = SelectionHistoryMode::Normal;
15130
15131 self.select_next_state = entry.select_next_state;
15132 self.select_prev_state = entry.select_prev_state;
15133 self.add_selections_state = entry.add_selections_state;
15134 }
15135 }
15136
15137 pub fn expand_excerpts(
15138 &mut self,
15139 action: &ExpandExcerpts,
15140 _: &mut Window,
15141 cx: &mut Context<Self>,
15142 ) {
15143 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15144 }
15145
15146 pub fn expand_excerpts_down(
15147 &mut self,
15148 action: &ExpandExcerptsDown,
15149 _: &mut Window,
15150 cx: &mut Context<Self>,
15151 ) {
15152 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15153 }
15154
15155 pub fn expand_excerpts_up(
15156 &mut self,
15157 action: &ExpandExcerptsUp,
15158 _: &mut Window,
15159 cx: &mut Context<Self>,
15160 ) {
15161 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15162 }
15163
15164 pub fn expand_excerpts_for_direction(
15165 &mut self,
15166 lines: u32,
15167 direction: ExpandExcerptDirection,
15168
15169 cx: &mut Context<Self>,
15170 ) {
15171 let selections = self.selections.disjoint_anchors();
15172
15173 let lines = if lines == 0 {
15174 EditorSettings::get_global(cx).expand_excerpt_lines
15175 } else {
15176 lines
15177 };
15178
15179 self.buffer.update(cx, |buffer, cx| {
15180 let snapshot = buffer.snapshot(cx);
15181 let mut excerpt_ids = selections
15182 .iter()
15183 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15184 .collect::<Vec<_>>();
15185 excerpt_ids.sort();
15186 excerpt_ids.dedup();
15187 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15188 })
15189 }
15190
15191 pub fn expand_excerpt(
15192 &mut self,
15193 excerpt: ExcerptId,
15194 direction: ExpandExcerptDirection,
15195 window: &mut Window,
15196 cx: &mut Context<Self>,
15197 ) {
15198 let current_scroll_position = self.scroll_position(cx);
15199 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15200 let mut should_scroll_up = false;
15201
15202 if direction == ExpandExcerptDirection::Down {
15203 let multi_buffer = self.buffer.read(cx);
15204 let snapshot = multi_buffer.snapshot(cx);
15205 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15206 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15207 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15208 let buffer_snapshot = buffer.read(cx).snapshot();
15209 let excerpt_end_row =
15210 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15211 let last_row = buffer_snapshot.max_point().row;
15212 let lines_below = last_row.saturating_sub(excerpt_end_row);
15213 should_scroll_up = lines_below >= lines_to_expand;
15214 }
15215 }
15216 }
15217 }
15218
15219 self.buffer.update(cx, |buffer, cx| {
15220 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15221 });
15222
15223 if should_scroll_up {
15224 let new_scroll_position =
15225 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15226 self.set_scroll_position(new_scroll_position, window, cx);
15227 }
15228 }
15229
15230 pub fn go_to_singleton_buffer_point(
15231 &mut self,
15232 point: Point,
15233 window: &mut Window,
15234 cx: &mut Context<Self>,
15235 ) {
15236 self.go_to_singleton_buffer_range(point..point, window, cx);
15237 }
15238
15239 pub fn go_to_singleton_buffer_range(
15240 &mut self,
15241 range: Range<Point>,
15242 window: &mut Window,
15243 cx: &mut Context<Self>,
15244 ) {
15245 let multibuffer = self.buffer().read(cx);
15246 let Some(buffer) = multibuffer.as_singleton() else {
15247 return;
15248 };
15249 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15250 return;
15251 };
15252 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15253 return;
15254 };
15255 self.change_selections(
15256 SelectionEffects::default().nav_history(true),
15257 window,
15258 cx,
15259 |s| s.select_anchor_ranges([start..end]),
15260 );
15261 }
15262
15263 pub fn go_to_diagnostic(
15264 &mut self,
15265 action: &GoToDiagnostic,
15266 window: &mut Window,
15267 cx: &mut Context<Self>,
15268 ) {
15269 if !self.diagnostics_enabled() {
15270 return;
15271 }
15272 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15273 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15274 }
15275
15276 pub fn go_to_prev_diagnostic(
15277 &mut self,
15278 action: &GoToPreviousDiagnostic,
15279 window: &mut Window,
15280 cx: &mut Context<Self>,
15281 ) {
15282 if !self.diagnostics_enabled() {
15283 return;
15284 }
15285 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15286 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15287 }
15288
15289 pub fn go_to_diagnostic_impl(
15290 &mut self,
15291 direction: Direction,
15292 severity: GoToDiagnosticSeverityFilter,
15293 window: &mut Window,
15294 cx: &mut Context<Self>,
15295 ) {
15296 let buffer = self.buffer.read(cx).snapshot(cx);
15297 let selection = self.selections.newest::<usize>(cx);
15298
15299 let mut active_group_id = None;
15300 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15301 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15302 active_group_id = Some(active_group.group_id);
15303 }
15304 }
15305
15306 fn filtered(
15307 snapshot: EditorSnapshot,
15308 severity: GoToDiagnosticSeverityFilter,
15309 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15310 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15311 diagnostics
15312 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15313 .filter(|entry| entry.range.start != entry.range.end)
15314 .filter(|entry| !entry.diagnostic.is_unnecessary)
15315 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15316 }
15317
15318 let snapshot = self.snapshot(window, cx);
15319 let before = filtered(
15320 snapshot.clone(),
15321 severity,
15322 buffer
15323 .diagnostics_in_range(0..selection.start)
15324 .filter(|entry| entry.range.start <= selection.start),
15325 );
15326 let after = filtered(
15327 snapshot,
15328 severity,
15329 buffer
15330 .diagnostics_in_range(selection.start..buffer.len())
15331 .filter(|entry| entry.range.start >= selection.start),
15332 );
15333
15334 let mut found: Option<DiagnosticEntry<usize>> = None;
15335 if direction == Direction::Prev {
15336 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15337 {
15338 for diagnostic in prev_diagnostics.into_iter().rev() {
15339 if diagnostic.range.start != selection.start
15340 || active_group_id
15341 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15342 {
15343 found = Some(diagnostic);
15344 break 'outer;
15345 }
15346 }
15347 }
15348 } else {
15349 for diagnostic in after.chain(before) {
15350 if diagnostic.range.start != selection.start
15351 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15352 {
15353 found = Some(diagnostic);
15354 break;
15355 }
15356 }
15357 }
15358 let Some(next_diagnostic) = found else {
15359 return;
15360 };
15361
15362 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15363 return;
15364 };
15365 self.change_selections(Default::default(), window, cx, |s| {
15366 s.select_ranges(vec![
15367 next_diagnostic.range.start..next_diagnostic.range.start,
15368 ])
15369 });
15370 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15371 self.refresh_edit_prediction(false, true, window, cx);
15372 }
15373
15374 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15375 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15376 let snapshot = self.snapshot(window, cx);
15377 let selection = self.selections.newest::<Point>(cx);
15378 self.go_to_hunk_before_or_after_position(
15379 &snapshot,
15380 selection.head(),
15381 Direction::Next,
15382 window,
15383 cx,
15384 );
15385 }
15386
15387 pub fn go_to_hunk_before_or_after_position(
15388 &mut self,
15389 snapshot: &EditorSnapshot,
15390 position: Point,
15391 direction: Direction,
15392 window: &mut Window,
15393 cx: &mut Context<Editor>,
15394 ) {
15395 let row = if direction == Direction::Next {
15396 self.hunk_after_position(snapshot, position)
15397 .map(|hunk| hunk.row_range.start)
15398 } else {
15399 self.hunk_before_position(snapshot, position)
15400 };
15401
15402 if let Some(row) = row {
15403 let destination = Point::new(row.0, 0);
15404 let autoscroll = Autoscroll::center();
15405
15406 self.unfold_ranges(&[destination..destination], false, false, cx);
15407 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15408 s.select_ranges([destination..destination]);
15409 });
15410 }
15411 }
15412
15413 fn hunk_after_position(
15414 &mut self,
15415 snapshot: &EditorSnapshot,
15416 position: Point,
15417 ) -> Option<MultiBufferDiffHunk> {
15418 snapshot
15419 .buffer_snapshot
15420 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15421 .find(|hunk| hunk.row_range.start.0 > position.row)
15422 .or_else(|| {
15423 snapshot
15424 .buffer_snapshot
15425 .diff_hunks_in_range(Point::zero()..position)
15426 .find(|hunk| hunk.row_range.end.0 < position.row)
15427 })
15428 }
15429
15430 fn go_to_prev_hunk(
15431 &mut self,
15432 _: &GoToPreviousHunk,
15433 window: &mut Window,
15434 cx: &mut Context<Self>,
15435 ) {
15436 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15437 let snapshot = self.snapshot(window, cx);
15438 let selection = self.selections.newest::<Point>(cx);
15439 self.go_to_hunk_before_or_after_position(
15440 &snapshot,
15441 selection.head(),
15442 Direction::Prev,
15443 window,
15444 cx,
15445 );
15446 }
15447
15448 fn hunk_before_position(
15449 &mut self,
15450 snapshot: &EditorSnapshot,
15451 position: Point,
15452 ) -> Option<MultiBufferRow> {
15453 snapshot
15454 .buffer_snapshot
15455 .diff_hunk_before(position)
15456 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15457 }
15458
15459 fn go_to_next_change(
15460 &mut self,
15461 _: &GoToNextChange,
15462 window: &mut Window,
15463 cx: &mut Context<Self>,
15464 ) {
15465 if let Some(selections) = self
15466 .change_list
15467 .next_change(1, Direction::Next)
15468 .map(|s| s.to_vec())
15469 {
15470 self.change_selections(Default::default(), window, cx, |s| {
15471 let map = s.display_map();
15472 s.select_display_ranges(selections.iter().map(|a| {
15473 let point = a.to_display_point(&map);
15474 point..point
15475 }))
15476 })
15477 }
15478 }
15479
15480 fn go_to_previous_change(
15481 &mut self,
15482 _: &GoToPreviousChange,
15483 window: &mut Window,
15484 cx: &mut Context<Self>,
15485 ) {
15486 if let Some(selections) = self
15487 .change_list
15488 .next_change(1, Direction::Prev)
15489 .map(|s| s.to_vec())
15490 {
15491 self.change_selections(Default::default(), window, cx, |s| {
15492 let map = s.display_map();
15493 s.select_display_ranges(selections.iter().map(|a| {
15494 let point = a.to_display_point(&map);
15495 point..point
15496 }))
15497 })
15498 }
15499 }
15500
15501 fn go_to_line<T: 'static>(
15502 &mut self,
15503 position: Anchor,
15504 highlight_color: Option<Hsla>,
15505 window: &mut Window,
15506 cx: &mut Context<Self>,
15507 ) {
15508 let snapshot = self.snapshot(window, cx).display_snapshot;
15509 let position = position.to_point(&snapshot.buffer_snapshot);
15510 let start = snapshot
15511 .buffer_snapshot
15512 .clip_point(Point::new(position.row, 0), Bias::Left);
15513 let end = start + Point::new(1, 0);
15514 let start = snapshot.buffer_snapshot.anchor_before(start);
15515 let end = snapshot.buffer_snapshot.anchor_before(end);
15516
15517 self.highlight_rows::<T>(
15518 start..end,
15519 highlight_color
15520 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15521 Default::default(),
15522 cx,
15523 );
15524
15525 if self.buffer.read(cx).is_singleton() {
15526 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15527 }
15528 }
15529
15530 pub fn go_to_definition(
15531 &mut self,
15532 _: &GoToDefinition,
15533 window: &mut Window,
15534 cx: &mut Context<Self>,
15535 ) -> Task<Result<Navigated>> {
15536 let definition =
15537 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15538 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15539 cx.spawn_in(window, async move |editor, cx| {
15540 if definition.await? == Navigated::Yes {
15541 return Ok(Navigated::Yes);
15542 }
15543 match fallback_strategy {
15544 GoToDefinitionFallback::None => Ok(Navigated::No),
15545 GoToDefinitionFallback::FindAllReferences => {
15546 match editor.update_in(cx, |editor, window, cx| {
15547 editor.find_all_references(&FindAllReferences, window, cx)
15548 })? {
15549 Some(references) => references.await,
15550 None => Ok(Navigated::No),
15551 }
15552 }
15553 }
15554 })
15555 }
15556
15557 pub fn go_to_declaration(
15558 &mut self,
15559 _: &GoToDeclaration,
15560 window: &mut Window,
15561 cx: &mut Context<Self>,
15562 ) -> Task<Result<Navigated>> {
15563 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15564 }
15565
15566 pub fn go_to_declaration_split(
15567 &mut self,
15568 _: &GoToDeclaration,
15569 window: &mut Window,
15570 cx: &mut Context<Self>,
15571 ) -> Task<Result<Navigated>> {
15572 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15573 }
15574
15575 pub fn go_to_implementation(
15576 &mut self,
15577 _: &GoToImplementation,
15578 window: &mut Window,
15579 cx: &mut Context<Self>,
15580 ) -> Task<Result<Navigated>> {
15581 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15582 }
15583
15584 pub fn go_to_implementation_split(
15585 &mut self,
15586 _: &GoToImplementationSplit,
15587 window: &mut Window,
15588 cx: &mut Context<Self>,
15589 ) -> Task<Result<Navigated>> {
15590 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15591 }
15592
15593 pub fn go_to_type_definition(
15594 &mut self,
15595 _: &GoToTypeDefinition,
15596 window: &mut Window,
15597 cx: &mut Context<Self>,
15598 ) -> Task<Result<Navigated>> {
15599 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15600 }
15601
15602 pub fn go_to_definition_split(
15603 &mut self,
15604 _: &GoToDefinitionSplit,
15605 window: &mut Window,
15606 cx: &mut Context<Self>,
15607 ) -> Task<Result<Navigated>> {
15608 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15609 }
15610
15611 pub fn go_to_type_definition_split(
15612 &mut self,
15613 _: &GoToTypeDefinitionSplit,
15614 window: &mut Window,
15615 cx: &mut Context<Self>,
15616 ) -> Task<Result<Navigated>> {
15617 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15618 }
15619
15620 fn go_to_definition_of_kind(
15621 &mut self,
15622 kind: GotoDefinitionKind,
15623 split: bool,
15624 window: &mut Window,
15625 cx: &mut Context<Self>,
15626 ) -> Task<Result<Navigated>> {
15627 let Some(provider) = self.semantics_provider.clone() else {
15628 return Task::ready(Ok(Navigated::No));
15629 };
15630 let head = self.selections.newest::<usize>(cx).head();
15631 let buffer = self.buffer.read(cx);
15632 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15633 text_anchor
15634 } else {
15635 return Task::ready(Ok(Navigated::No));
15636 };
15637
15638 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15639 return Task::ready(Ok(Navigated::No));
15640 };
15641
15642 cx.spawn_in(window, async move |editor, cx| {
15643 let definitions = definitions.await?;
15644 let navigated = editor
15645 .update_in(cx, |editor, window, cx| {
15646 editor.navigate_to_hover_links(
15647 Some(kind),
15648 definitions
15649 .into_iter()
15650 .filter(|location| {
15651 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15652 })
15653 .map(HoverLink::Text)
15654 .collect::<Vec<_>>(),
15655 split,
15656 window,
15657 cx,
15658 )
15659 })?
15660 .await?;
15661 anyhow::Ok(navigated)
15662 })
15663 }
15664
15665 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15666 let selection = self.selections.newest_anchor();
15667 let head = selection.head();
15668 let tail = selection.tail();
15669
15670 let Some((buffer, start_position)) =
15671 self.buffer.read(cx).text_anchor_for_position(head, cx)
15672 else {
15673 return;
15674 };
15675
15676 let end_position = if head != tail {
15677 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15678 return;
15679 };
15680 Some(pos)
15681 } else {
15682 None
15683 };
15684
15685 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15686 let url = if let Some(end_pos) = end_position {
15687 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15688 } else {
15689 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15690 };
15691
15692 if let Some(url) = url {
15693 editor.update(cx, |_, cx| {
15694 cx.open_url(&url);
15695 })
15696 } else {
15697 Ok(())
15698 }
15699 });
15700
15701 url_finder.detach();
15702 }
15703
15704 pub fn open_selected_filename(
15705 &mut self,
15706 _: &OpenSelectedFilename,
15707 window: &mut Window,
15708 cx: &mut Context<Self>,
15709 ) {
15710 let Some(workspace) = self.workspace() else {
15711 return;
15712 };
15713
15714 let position = self.selections.newest_anchor().head();
15715
15716 let Some((buffer, buffer_position)) =
15717 self.buffer.read(cx).text_anchor_for_position(position, cx)
15718 else {
15719 return;
15720 };
15721
15722 let project = self.project.clone();
15723
15724 cx.spawn_in(window, async move |_, cx| {
15725 let result = find_file(&buffer, project, buffer_position, cx).await;
15726
15727 if let Some((_, path)) = result {
15728 workspace
15729 .update_in(cx, |workspace, window, cx| {
15730 workspace.open_resolved_path(path, window, cx)
15731 })?
15732 .await?;
15733 }
15734 anyhow::Ok(())
15735 })
15736 .detach();
15737 }
15738
15739 pub(crate) fn navigate_to_hover_links(
15740 &mut self,
15741 kind: Option<GotoDefinitionKind>,
15742 mut definitions: Vec<HoverLink>,
15743 split: bool,
15744 window: &mut Window,
15745 cx: &mut Context<Editor>,
15746 ) -> Task<Result<Navigated>> {
15747 // If there is one definition, just open it directly
15748 if definitions.len() == 1 {
15749 let definition = definitions.pop().unwrap();
15750
15751 enum TargetTaskResult {
15752 Location(Option<Location>),
15753 AlreadyNavigated,
15754 }
15755
15756 let target_task = match definition {
15757 HoverLink::Text(link) => {
15758 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15759 }
15760 HoverLink::InlayHint(lsp_location, server_id) => {
15761 let computation =
15762 self.compute_target_location(lsp_location, server_id, window, cx);
15763 cx.background_spawn(async move {
15764 let location = computation.await?;
15765 Ok(TargetTaskResult::Location(location))
15766 })
15767 }
15768 HoverLink::Url(url) => {
15769 cx.open_url(&url);
15770 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15771 }
15772 HoverLink::File(path) => {
15773 if let Some(workspace) = self.workspace() {
15774 cx.spawn_in(window, async move |_, cx| {
15775 workspace
15776 .update_in(cx, |workspace, window, cx| {
15777 workspace.open_resolved_path(path, window, cx)
15778 })?
15779 .await
15780 .map(|_| TargetTaskResult::AlreadyNavigated)
15781 })
15782 } else {
15783 Task::ready(Ok(TargetTaskResult::Location(None)))
15784 }
15785 }
15786 };
15787 cx.spawn_in(window, async move |editor, cx| {
15788 let target = match target_task.await.context("target resolution task")? {
15789 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15790 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15791 TargetTaskResult::Location(Some(target)) => target,
15792 };
15793
15794 editor.update_in(cx, |editor, window, cx| {
15795 let Some(workspace) = editor.workspace() else {
15796 return Navigated::No;
15797 };
15798 let pane = workspace.read(cx).active_pane().clone();
15799
15800 let range = target.range.to_point(target.buffer.read(cx));
15801 let range = editor.range_for_match(&range);
15802 let range = collapse_multiline_range(range);
15803
15804 if !split
15805 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15806 {
15807 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15808 } else {
15809 window.defer(cx, move |window, cx| {
15810 let target_editor: Entity<Self> =
15811 workspace.update(cx, |workspace, cx| {
15812 let pane = if split {
15813 workspace.adjacent_pane(window, cx)
15814 } else {
15815 workspace.active_pane().clone()
15816 };
15817
15818 workspace.open_project_item(
15819 pane,
15820 target.buffer.clone(),
15821 true,
15822 true,
15823 window,
15824 cx,
15825 )
15826 });
15827 target_editor.update(cx, |target_editor, cx| {
15828 // When selecting a definition in a different buffer, disable the nav history
15829 // to avoid creating a history entry at the previous cursor location.
15830 pane.update(cx, |pane, _| pane.disable_history());
15831 target_editor.go_to_singleton_buffer_range(range, window, cx);
15832 pane.update(cx, |pane, _| pane.enable_history());
15833 });
15834 });
15835 }
15836 Navigated::Yes
15837 })
15838 })
15839 } else if !definitions.is_empty() {
15840 cx.spawn_in(window, async move |editor, cx| {
15841 let (title, location_tasks, workspace) = editor
15842 .update_in(cx, |editor, window, cx| {
15843 let tab_kind = match kind {
15844 Some(GotoDefinitionKind::Implementation) => "Implementations",
15845 _ => "Definitions",
15846 };
15847 let title = definitions
15848 .iter()
15849 .find_map(|definition| match definition {
15850 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15851 let buffer = origin.buffer.read(cx);
15852 format!(
15853 "{} for {}",
15854 tab_kind,
15855 buffer
15856 .text_for_range(origin.range.clone())
15857 .collect::<String>()
15858 )
15859 }),
15860 HoverLink::InlayHint(_, _) => None,
15861 HoverLink::Url(_) => None,
15862 HoverLink::File(_) => None,
15863 })
15864 .unwrap_or(tab_kind.to_string());
15865 let location_tasks = definitions
15866 .into_iter()
15867 .map(|definition| match definition {
15868 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15869 HoverLink::InlayHint(lsp_location, server_id) => editor
15870 .compute_target_location(lsp_location, server_id, window, cx),
15871 HoverLink::Url(_) => Task::ready(Ok(None)),
15872 HoverLink::File(_) => Task::ready(Ok(None)),
15873 })
15874 .collect::<Vec<_>>();
15875 (title, location_tasks, editor.workspace().clone())
15876 })
15877 .context("location tasks preparation")?;
15878
15879 let locations: Vec<Location> = future::join_all(location_tasks)
15880 .await
15881 .into_iter()
15882 .filter_map(|location| location.transpose())
15883 .collect::<Result<_>>()
15884 .context("location tasks")?;
15885
15886 if locations.is_empty() {
15887 return Ok(Navigated::No);
15888 }
15889
15890 let Some(workspace) = workspace else {
15891 return Ok(Navigated::No);
15892 };
15893
15894 let opened = workspace
15895 .update_in(cx, |workspace, window, cx| {
15896 Self::open_locations_in_multibuffer(
15897 workspace,
15898 locations,
15899 title,
15900 split,
15901 MultibufferSelectionMode::First,
15902 window,
15903 cx,
15904 )
15905 })
15906 .ok();
15907
15908 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15909 })
15910 } else {
15911 Task::ready(Ok(Navigated::No))
15912 }
15913 }
15914
15915 fn compute_target_location(
15916 &self,
15917 lsp_location: lsp::Location,
15918 server_id: LanguageServerId,
15919 window: &mut Window,
15920 cx: &mut Context<Self>,
15921 ) -> Task<anyhow::Result<Option<Location>>> {
15922 let Some(project) = self.project.clone() else {
15923 return Task::ready(Ok(None));
15924 };
15925
15926 cx.spawn_in(window, async move |editor, cx| {
15927 let location_task = editor.update(cx, |_, cx| {
15928 project.update(cx, |project, cx| {
15929 let language_server_name = project
15930 .language_server_statuses(cx)
15931 .find(|(id, _)| server_id == *id)
15932 .map(|(_, status)| status.name.clone());
15933 language_server_name.map(|language_server_name| {
15934 project.open_local_buffer_via_lsp(
15935 lsp_location.uri.clone(),
15936 server_id,
15937 language_server_name,
15938 cx,
15939 )
15940 })
15941 })
15942 })?;
15943 let location = match location_task {
15944 Some(task) => Some({
15945 let target_buffer_handle = task.await.context("open local buffer")?;
15946 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15947 let target_start = target_buffer
15948 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15949 let target_end = target_buffer
15950 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15951 target_buffer.anchor_after(target_start)
15952 ..target_buffer.anchor_before(target_end)
15953 })?;
15954 Location {
15955 buffer: target_buffer_handle,
15956 range,
15957 }
15958 }),
15959 None => None,
15960 };
15961 Ok(location)
15962 })
15963 }
15964
15965 pub fn find_all_references(
15966 &mut self,
15967 _: &FindAllReferences,
15968 window: &mut Window,
15969 cx: &mut Context<Self>,
15970 ) -> Option<Task<Result<Navigated>>> {
15971 let selection = self.selections.newest::<usize>(cx);
15972 let multi_buffer = self.buffer.read(cx);
15973 let head = selection.head();
15974
15975 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15976 let head_anchor = multi_buffer_snapshot.anchor_at(
15977 head,
15978 if head < selection.tail() {
15979 Bias::Right
15980 } else {
15981 Bias::Left
15982 },
15983 );
15984
15985 match self
15986 .find_all_references_task_sources
15987 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15988 {
15989 Ok(_) => {
15990 log::info!(
15991 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15992 );
15993 return None;
15994 }
15995 Err(i) => {
15996 self.find_all_references_task_sources.insert(i, head_anchor);
15997 }
15998 }
15999
16000 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16001 let workspace = self.workspace()?;
16002 let project = workspace.read(cx).project().clone();
16003 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16004 Some(cx.spawn_in(window, async move |editor, cx| {
16005 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16006 if let Ok(i) = editor
16007 .find_all_references_task_sources
16008 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16009 {
16010 editor.find_all_references_task_sources.remove(i);
16011 }
16012 });
16013
16014 let locations = references.await?;
16015 if locations.is_empty() {
16016 return anyhow::Ok(Navigated::No);
16017 }
16018
16019 workspace.update_in(cx, |workspace, window, cx| {
16020 let title = locations
16021 .first()
16022 .as_ref()
16023 .map(|location| {
16024 let buffer = location.buffer.read(cx);
16025 format!(
16026 "References to `{}`",
16027 buffer
16028 .text_for_range(location.range.clone())
16029 .collect::<String>()
16030 )
16031 })
16032 .unwrap();
16033 Self::open_locations_in_multibuffer(
16034 workspace,
16035 locations,
16036 title,
16037 false,
16038 MultibufferSelectionMode::First,
16039 window,
16040 cx,
16041 );
16042 Navigated::Yes
16043 })
16044 }))
16045 }
16046
16047 /// Opens a multibuffer with the given project locations in it
16048 pub fn open_locations_in_multibuffer(
16049 workspace: &mut Workspace,
16050 mut locations: Vec<Location>,
16051 title: String,
16052 split: bool,
16053 multibuffer_selection_mode: MultibufferSelectionMode,
16054 window: &mut Window,
16055 cx: &mut Context<Workspace>,
16056 ) {
16057 if locations.is_empty() {
16058 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16059 return;
16060 }
16061
16062 // If there are multiple definitions, open them in a multibuffer
16063 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16064 let mut locations = locations.into_iter().peekable();
16065 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16066 let capability = workspace.project().read(cx).capability();
16067
16068 let excerpt_buffer = cx.new(|cx| {
16069 let mut multibuffer = MultiBuffer::new(capability);
16070 while let Some(location) = locations.next() {
16071 let buffer = location.buffer.read(cx);
16072 let mut ranges_for_buffer = Vec::new();
16073 let range = location.range.to_point(buffer);
16074 ranges_for_buffer.push(range.clone());
16075
16076 while let Some(next_location) = locations.peek() {
16077 if next_location.buffer == location.buffer {
16078 ranges_for_buffer.push(next_location.range.to_point(buffer));
16079 locations.next();
16080 } else {
16081 break;
16082 }
16083 }
16084
16085 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16086 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16087 PathKey::for_buffer(&location.buffer, cx),
16088 location.buffer.clone(),
16089 ranges_for_buffer,
16090 DEFAULT_MULTIBUFFER_CONTEXT,
16091 cx,
16092 );
16093 ranges.extend(new_ranges)
16094 }
16095
16096 multibuffer.with_title(title)
16097 });
16098
16099 let editor = cx.new(|cx| {
16100 Editor::for_multibuffer(
16101 excerpt_buffer,
16102 Some(workspace.project().clone()),
16103 window,
16104 cx,
16105 )
16106 });
16107 editor.update(cx, |editor, cx| {
16108 match multibuffer_selection_mode {
16109 MultibufferSelectionMode::First => {
16110 if let Some(first_range) = ranges.first() {
16111 editor.change_selections(
16112 SelectionEffects::no_scroll(),
16113 window,
16114 cx,
16115 |selections| {
16116 selections.clear_disjoint();
16117 selections
16118 .select_anchor_ranges(std::iter::once(first_range.clone()));
16119 },
16120 );
16121 }
16122 editor.highlight_background::<Self>(
16123 &ranges,
16124 |theme| theme.colors().editor_highlighted_line_background,
16125 cx,
16126 );
16127 }
16128 MultibufferSelectionMode::All => {
16129 editor.change_selections(
16130 SelectionEffects::no_scroll(),
16131 window,
16132 cx,
16133 |selections| {
16134 selections.clear_disjoint();
16135 selections.select_anchor_ranges(ranges);
16136 },
16137 );
16138 }
16139 }
16140 editor.register_buffers_with_language_servers(cx);
16141 });
16142
16143 let item = Box::new(editor);
16144 let item_id = item.item_id();
16145
16146 if split {
16147 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16148 } else {
16149 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16150 let (preview_item_id, preview_item_idx) =
16151 workspace.active_pane().read_with(cx, |pane, _| {
16152 (pane.preview_item_id(), pane.preview_item_idx())
16153 });
16154
16155 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16156
16157 if let Some(preview_item_id) = preview_item_id {
16158 workspace.active_pane().update(cx, |pane, cx| {
16159 pane.remove_item(preview_item_id, false, false, window, cx);
16160 });
16161 }
16162 } else {
16163 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16164 }
16165 }
16166 workspace.active_pane().update(cx, |pane, cx| {
16167 pane.set_preview_item_id(Some(item_id), cx);
16168 });
16169 }
16170
16171 pub fn rename(
16172 &mut self,
16173 _: &Rename,
16174 window: &mut Window,
16175 cx: &mut Context<Self>,
16176 ) -> Option<Task<Result<()>>> {
16177 use language::ToOffset as _;
16178
16179 let provider = self.semantics_provider.clone()?;
16180 let selection = self.selections.newest_anchor().clone();
16181 let (cursor_buffer, cursor_buffer_position) = self
16182 .buffer
16183 .read(cx)
16184 .text_anchor_for_position(selection.head(), cx)?;
16185 let (tail_buffer, cursor_buffer_position_end) = self
16186 .buffer
16187 .read(cx)
16188 .text_anchor_for_position(selection.tail(), cx)?;
16189 if tail_buffer != cursor_buffer {
16190 return None;
16191 }
16192
16193 let snapshot = cursor_buffer.read(cx).snapshot();
16194 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16195 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16196 let prepare_rename = provider
16197 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16198 .unwrap_or_else(|| Task::ready(Ok(None)));
16199 drop(snapshot);
16200
16201 Some(cx.spawn_in(window, async move |this, cx| {
16202 let rename_range = if let Some(range) = prepare_rename.await? {
16203 Some(range)
16204 } else {
16205 this.update(cx, |this, cx| {
16206 let buffer = this.buffer.read(cx).snapshot(cx);
16207 let mut buffer_highlights = this
16208 .document_highlights_for_position(selection.head(), &buffer)
16209 .filter(|highlight| {
16210 highlight.start.excerpt_id == selection.head().excerpt_id
16211 && highlight.end.excerpt_id == selection.head().excerpt_id
16212 });
16213 buffer_highlights
16214 .next()
16215 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16216 })?
16217 };
16218 if let Some(rename_range) = rename_range {
16219 this.update_in(cx, |this, window, cx| {
16220 let snapshot = cursor_buffer.read(cx).snapshot();
16221 let rename_buffer_range = rename_range.to_offset(&snapshot);
16222 let cursor_offset_in_rename_range =
16223 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16224 let cursor_offset_in_rename_range_end =
16225 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16226
16227 this.take_rename(false, window, cx);
16228 let buffer = this.buffer.read(cx).read(cx);
16229 let cursor_offset = selection.head().to_offset(&buffer);
16230 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16231 let rename_end = rename_start + rename_buffer_range.len();
16232 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16233 let mut old_highlight_id = None;
16234 let old_name: Arc<str> = buffer
16235 .chunks(rename_start..rename_end, true)
16236 .map(|chunk| {
16237 if old_highlight_id.is_none() {
16238 old_highlight_id = chunk.syntax_highlight_id;
16239 }
16240 chunk.text
16241 })
16242 .collect::<String>()
16243 .into();
16244
16245 drop(buffer);
16246
16247 // Position the selection in the rename editor so that it matches the current selection.
16248 this.show_local_selections = false;
16249 let rename_editor = cx.new(|cx| {
16250 let mut editor = Editor::single_line(window, cx);
16251 editor.buffer.update(cx, |buffer, cx| {
16252 buffer.edit([(0..0, old_name.clone())], None, cx)
16253 });
16254 let rename_selection_range = match cursor_offset_in_rename_range
16255 .cmp(&cursor_offset_in_rename_range_end)
16256 {
16257 Ordering::Equal => {
16258 editor.select_all(&SelectAll, window, cx);
16259 return editor;
16260 }
16261 Ordering::Less => {
16262 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16263 }
16264 Ordering::Greater => {
16265 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16266 }
16267 };
16268 if rename_selection_range.end > old_name.len() {
16269 editor.select_all(&SelectAll, window, cx);
16270 } else {
16271 editor.change_selections(Default::default(), window, cx, |s| {
16272 s.select_ranges([rename_selection_range]);
16273 });
16274 }
16275 editor
16276 });
16277 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16278 if e == &EditorEvent::Focused {
16279 cx.emit(EditorEvent::FocusedIn)
16280 }
16281 })
16282 .detach();
16283
16284 let write_highlights =
16285 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16286 let read_highlights =
16287 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16288 let ranges = write_highlights
16289 .iter()
16290 .flat_map(|(_, ranges)| ranges.iter())
16291 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16292 .cloned()
16293 .collect();
16294
16295 this.highlight_text::<Rename>(
16296 ranges,
16297 HighlightStyle {
16298 fade_out: Some(0.6),
16299 ..Default::default()
16300 },
16301 cx,
16302 );
16303 let rename_focus_handle = rename_editor.focus_handle(cx);
16304 window.focus(&rename_focus_handle);
16305 let block_id = this.insert_blocks(
16306 [BlockProperties {
16307 style: BlockStyle::Flex,
16308 placement: BlockPlacement::Below(range.start),
16309 height: Some(1),
16310 render: Arc::new({
16311 let rename_editor = rename_editor.clone();
16312 move |cx: &mut BlockContext| {
16313 let mut text_style = cx.editor_style.text.clone();
16314 if let Some(highlight_style) = old_highlight_id
16315 .and_then(|h| h.style(&cx.editor_style.syntax))
16316 {
16317 text_style = text_style.highlight(highlight_style);
16318 }
16319 div()
16320 .block_mouse_except_scroll()
16321 .pl(cx.anchor_x)
16322 .child(EditorElement::new(
16323 &rename_editor,
16324 EditorStyle {
16325 background: cx.theme().system().transparent,
16326 local_player: cx.editor_style.local_player,
16327 text: text_style,
16328 scrollbar_width: cx.editor_style.scrollbar_width,
16329 syntax: cx.editor_style.syntax.clone(),
16330 status: cx.editor_style.status.clone(),
16331 inlay_hints_style: HighlightStyle {
16332 font_weight: Some(FontWeight::BOLD),
16333 ..make_inlay_hints_style(cx.app)
16334 },
16335 edit_prediction_styles: make_suggestion_styles(
16336 cx.app,
16337 ),
16338 ..EditorStyle::default()
16339 },
16340 ))
16341 .into_any_element()
16342 }
16343 }),
16344 priority: 0,
16345 }],
16346 Some(Autoscroll::fit()),
16347 cx,
16348 )[0];
16349 this.pending_rename = Some(RenameState {
16350 range,
16351 old_name,
16352 editor: rename_editor,
16353 block_id,
16354 });
16355 })?;
16356 }
16357
16358 Ok(())
16359 }))
16360 }
16361
16362 pub fn confirm_rename(
16363 &mut self,
16364 _: &ConfirmRename,
16365 window: &mut Window,
16366 cx: &mut Context<Self>,
16367 ) -> Option<Task<Result<()>>> {
16368 let rename = self.take_rename(false, window, cx)?;
16369 let workspace = self.workspace()?.downgrade();
16370 let (buffer, start) = self
16371 .buffer
16372 .read(cx)
16373 .text_anchor_for_position(rename.range.start, cx)?;
16374 let (end_buffer, _) = self
16375 .buffer
16376 .read(cx)
16377 .text_anchor_for_position(rename.range.end, cx)?;
16378 if buffer != end_buffer {
16379 return None;
16380 }
16381
16382 let old_name = rename.old_name;
16383 let new_name = rename.editor.read(cx).text(cx);
16384
16385 let rename = self.semantics_provider.as_ref()?.perform_rename(
16386 &buffer,
16387 start,
16388 new_name.clone(),
16389 cx,
16390 )?;
16391
16392 Some(cx.spawn_in(window, async move |editor, cx| {
16393 let project_transaction = rename.await?;
16394 Self::open_project_transaction(
16395 &editor,
16396 workspace,
16397 project_transaction,
16398 format!("Rename: {} → {}", old_name, new_name),
16399 cx,
16400 )
16401 .await?;
16402
16403 editor.update(cx, |editor, cx| {
16404 editor.refresh_document_highlights(cx);
16405 })?;
16406 Ok(())
16407 }))
16408 }
16409
16410 fn take_rename(
16411 &mut self,
16412 moving_cursor: bool,
16413 window: &mut Window,
16414 cx: &mut Context<Self>,
16415 ) -> Option<RenameState> {
16416 let rename = self.pending_rename.take()?;
16417 if rename.editor.focus_handle(cx).is_focused(window) {
16418 window.focus(&self.focus_handle);
16419 }
16420
16421 self.remove_blocks(
16422 [rename.block_id].into_iter().collect(),
16423 Some(Autoscroll::fit()),
16424 cx,
16425 );
16426 self.clear_highlights::<Rename>(cx);
16427 self.show_local_selections = true;
16428
16429 if moving_cursor {
16430 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16431 editor.selections.newest::<usize>(cx).head()
16432 });
16433
16434 // Update the selection to match the position of the selection inside
16435 // the rename editor.
16436 let snapshot = self.buffer.read(cx).read(cx);
16437 let rename_range = rename.range.to_offset(&snapshot);
16438 let cursor_in_editor = snapshot
16439 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16440 .min(rename_range.end);
16441 drop(snapshot);
16442
16443 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16444 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16445 });
16446 } else {
16447 self.refresh_document_highlights(cx);
16448 }
16449
16450 Some(rename)
16451 }
16452
16453 pub fn pending_rename(&self) -> Option<&RenameState> {
16454 self.pending_rename.as_ref()
16455 }
16456
16457 fn format(
16458 &mut self,
16459 _: &Format,
16460 window: &mut Window,
16461 cx: &mut Context<Self>,
16462 ) -> Option<Task<Result<()>>> {
16463 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16464
16465 let project = match &self.project {
16466 Some(project) => project.clone(),
16467 None => return None,
16468 };
16469
16470 Some(self.perform_format(
16471 project,
16472 FormatTrigger::Manual,
16473 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16474 window,
16475 cx,
16476 ))
16477 }
16478
16479 fn format_selections(
16480 &mut self,
16481 _: &FormatSelections,
16482 window: &mut Window,
16483 cx: &mut Context<Self>,
16484 ) -> Option<Task<Result<()>>> {
16485 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16486
16487 let project = match &self.project {
16488 Some(project) => project.clone(),
16489 None => return None,
16490 };
16491
16492 let ranges = self
16493 .selections
16494 .all_adjusted(cx)
16495 .into_iter()
16496 .map(|selection| selection.range())
16497 .collect_vec();
16498
16499 Some(self.perform_format(
16500 project,
16501 FormatTrigger::Manual,
16502 FormatTarget::Ranges(ranges),
16503 window,
16504 cx,
16505 ))
16506 }
16507
16508 fn perform_format(
16509 &mut self,
16510 project: Entity<Project>,
16511 trigger: FormatTrigger,
16512 target: FormatTarget,
16513 window: &mut Window,
16514 cx: &mut Context<Self>,
16515 ) -> Task<Result<()>> {
16516 let buffer = self.buffer.clone();
16517 let (buffers, target) = match target {
16518 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16519 FormatTarget::Ranges(selection_ranges) => {
16520 let multi_buffer = buffer.read(cx);
16521 let snapshot = multi_buffer.read(cx);
16522 let mut buffers = HashSet::default();
16523 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16524 BTreeMap::new();
16525 for selection_range in selection_ranges {
16526 for (buffer, buffer_range, _) in
16527 snapshot.range_to_buffer_ranges(selection_range)
16528 {
16529 let buffer_id = buffer.remote_id();
16530 let start = buffer.anchor_before(buffer_range.start);
16531 let end = buffer.anchor_after(buffer_range.end);
16532 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16533 buffer_id_to_ranges
16534 .entry(buffer_id)
16535 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16536 .or_insert_with(|| vec![start..end]);
16537 }
16538 }
16539 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16540 }
16541 };
16542
16543 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16544 let selections_prev = transaction_id_prev
16545 .and_then(|transaction_id_prev| {
16546 // default to selections as they were after the last edit, if we have them,
16547 // instead of how they are now.
16548 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16549 // will take you back to where you made the last edit, instead of staying where you scrolled
16550 self.selection_history
16551 .transaction(transaction_id_prev)
16552 .map(|t| t.0.clone())
16553 })
16554 .unwrap_or_else(|| {
16555 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16556 self.selections.disjoint_anchors()
16557 });
16558
16559 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16560 let format = project.update(cx, |project, cx| {
16561 project.format(buffers, target, true, trigger, cx)
16562 });
16563
16564 cx.spawn_in(window, async move |editor, cx| {
16565 let transaction = futures::select_biased! {
16566 transaction = format.log_err().fuse() => transaction,
16567 () = timeout => {
16568 log::warn!("timed out waiting for formatting");
16569 None
16570 }
16571 };
16572
16573 buffer
16574 .update(cx, |buffer, cx| {
16575 if let Some(transaction) = transaction {
16576 if !buffer.is_singleton() {
16577 buffer.push_transaction(&transaction.0, cx);
16578 }
16579 }
16580 cx.notify();
16581 })
16582 .ok();
16583
16584 if let Some(transaction_id_now) =
16585 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16586 {
16587 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16588 if has_new_transaction {
16589 _ = editor.update(cx, |editor, _| {
16590 editor
16591 .selection_history
16592 .insert_transaction(transaction_id_now, selections_prev);
16593 });
16594 }
16595 }
16596
16597 Ok(())
16598 })
16599 }
16600
16601 fn organize_imports(
16602 &mut self,
16603 _: &OrganizeImports,
16604 window: &mut Window,
16605 cx: &mut Context<Self>,
16606 ) -> Option<Task<Result<()>>> {
16607 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16608 let project = match &self.project {
16609 Some(project) => project.clone(),
16610 None => return None,
16611 };
16612 Some(self.perform_code_action_kind(
16613 project,
16614 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16615 window,
16616 cx,
16617 ))
16618 }
16619
16620 fn perform_code_action_kind(
16621 &mut self,
16622 project: Entity<Project>,
16623 kind: CodeActionKind,
16624 window: &mut Window,
16625 cx: &mut Context<Self>,
16626 ) -> Task<Result<()>> {
16627 let buffer = self.buffer.clone();
16628 let buffers = buffer.read(cx).all_buffers();
16629 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16630 let apply_action = project.update(cx, |project, cx| {
16631 project.apply_code_action_kind(buffers, kind, true, cx)
16632 });
16633 cx.spawn_in(window, async move |_, cx| {
16634 let transaction = futures::select_biased! {
16635 () = timeout => {
16636 log::warn!("timed out waiting for executing code action");
16637 None
16638 }
16639 transaction = apply_action.log_err().fuse() => transaction,
16640 };
16641 buffer
16642 .update(cx, |buffer, cx| {
16643 // check if we need this
16644 if let Some(transaction) = transaction {
16645 if !buffer.is_singleton() {
16646 buffer.push_transaction(&transaction.0, cx);
16647 }
16648 }
16649 cx.notify();
16650 })
16651 .ok();
16652 Ok(())
16653 })
16654 }
16655
16656 pub fn restart_language_server(
16657 &mut self,
16658 _: &RestartLanguageServer,
16659 _: &mut Window,
16660 cx: &mut Context<Self>,
16661 ) {
16662 if let Some(project) = self.project.clone() {
16663 self.buffer.update(cx, |multi_buffer, cx| {
16664 project.update(cx, |project, cx| {
16665 project.restart_language_servers_for_buffers(
16666 multi_buffer.all_buffers().into_iter().collect(),
16667 HashSet::default(),
16668 cx,
16669 );
16670 });
16671 })
16672 }
16673 }
16674
16675 pub fn stop_language_server(
16676 &mut self,
16677 _: &StopLanguageServer,
16678 _: &mut Window,
16679 cx: &mut Context<Self>,
16680 ) {
16681 if let Some(project) = self.project.clone() {
16682 self.buffer.update(cx, |multi_buffer, cx| {
16683 project.update(cx, |project, cx| {
16684 project.stop_language_servers_for_buffers(
16685 multi_buffer.all_buffers().into_iter().collect(),
16686 HashSet::default(),
16687 cx,
16688 );
16689 cx.emit(project::Event::RefreshInlayHints);
16690 });
16691 });
16692 }
16693 }
16694
16695 fn cancel_language_server_work(
16696 workspace: &mut Workspace,
16697 _: &actions::CancelLanguageServerWork,
16698 _: &mut Window,
16699 cx: &mut Context<Workspace>,
16700 ) {
16701 let project = workspace.project();
16702 let buffers = workspace
16703 .active_item(cx)
16704 .and_then(|item| item.act_as::<Editor>(cx))
16705 .map_or(HashSet::default(), |editor| {
16706 editor.read(cx).buffer.read(cx).all_buffers()
16707 });
16708 project.update(cx, |project, cx| {
16709 project.cancel_language_server_work_for_buffers(buffers, cx);
16710 });
16711 }
16712
16713 fn show_character_palette(
16714 &mut self,
16715 _: &ShowCharacterPalette,
16716 window: &mut Window,
16717 _: &mut Context<Self>,
16718 ) {
16719 window.show_character_palette();
16720 }
16721
16722 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16723 if !self.diagnostics_enabled() {
16724 return;
16725 }
16726
16727 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16728 let buffer = self.buffer.read(cx).snapshot(cx);
16729 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16730 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16731 let is_valid = buffer
16732 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16733 .any(|entry| {
16734 entry.diagnostic.is_primary
16735 && !entry.range.is_empty()
16736 && entry.range.start == primary_range_start
16737 && entry.diagnostic.message == active_diagnostics.active_message
16738 });
16739
16740 if !is_valid {
16741 self.dismiss_diagnostics(cx);
16742 }
16743 }
16744 }
16745
16746 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16747 match &self.active_diagnostics {
16748 ActiveDiagnostic::Group(group) => Some(group),
16749 _ => None,
16750 }
16751 }
16752
16753 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16754 if !self.diagnostics_enabled() {
16755 return;
16756 }
16757 self.dismiss_diagnostics(cx);
16758 self.active_diagnostics = ActiveDiagnostic::All;
16759 }
16760
16761 fn activate_diagnostics(
16762 &mut self,
16763 buffer_id: BufferId,
16764 diagnostic: DiagnosticEntry<usize>,
16765 window: &mut Window,
16766 cx: &mut Context<Self>,
16767 ) {
16768 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16769 return;
16770 }
16771 self.dismiss_diagnostics(cx);
16772 let snapshot = self.snapshot(window, cx);
16773 let buffer = self.buffer.read(cx).snapshot(cx);
16774 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16775 return;
16776 };
16777
16778 let diagnostic_group = buffer
16779 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16780 .collect::<Vec<_>>();
16781
16782 let blocks =
16783 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16784
16785 let blocks = self.display_map.update(cx, |display_map, cx| {
16786 display_map.insert_blocks(blocks, cx).into_iter().collect()
16787 });
16788 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16789 active_range: buffer.anchor_before(diagnostic.range.start)
16790 ..buffer.anchor_after(diagnostic.range.end),
16791 active_message: diagnostic.diagnostic.message.clone(),
16792 group_id: diagnostic.diagnostic.group_id,
16793 blocks,
16794 });
16795 cx.notify();
16796 }
16797
16798 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16799 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16800 return;
16801 };
16802
16803 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16804 if let ActiveDiagnostic::Group(group) = prev {
16805 self.display_map.update(cx, |display_map, cx| {
16806 display_map.remove_blocks(group.blocks, cx);
16807 });
16808 cx.notify();
16809 }
16810 }
16811
16812 /// Disable inline diagnostics rendering for this editor.
16813 pub fn disable_inline_diagnostics(&mut self) {
16814 self.inline_diagnostics_enabled = false;
16815 self.inline_diagnostics_update = Task::ready(());
16816 self.inline_diagnostics.clear();
16817 }
16818
16819 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16820 self.diagnostics_enabled = false;
16821 self.dismiss_diagnostics(cx);
16822 self.inline_diagnostics_update = Task::ready(());
16823 self.inline_diagnostics.clear();
16824 }
16825
16826 pub fn diagnostics_enabled(&self) -> bool {
16827 self.diagnostics_enabled && self.mode.is_full()
16828 }
16829
16830 pub fn inline_diagnostics_enabled(&self) -> bool {
16831 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16832 }
16833
16834 pub fn show_inline_diagnostics(&self) -> bool {
16835 self.show_inline_diagnostics
16836 }
16837
16838 pub fn toggle_inline_diagnostics(
16839 &mut self,
16840 _: &ToggleInlineDiagnostics,
16841 window: &mut Window,
16842 cx: &mut Context<Editor>,
16843 ) {
16844 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16845 self.refresh_inline_diagnostics(false, window, cx);
16846 }
16847
16848 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16849 self.diagnostics_max_severity = severity;
16850 self.display_map.update(cx, |display_map, _| {
16851 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16852 });
16853 }
16854
16855 pub fn toggle_diagnostics(
16856 &mut self,
16857 _: &ToggleDiagnostics,
16858 window: &mut Window,
16859 cx: &mut Context<Editor>,
16860 ) {
16861 if !self.diagnostics_enabled() {
16862 return;
16863 }
16864
16865 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16866 EditorSettings::get_global(cx)
16867 .diagnostics_max_severity
16868 .filter(|severity| severity != &DiagnosticSeverity::Off)
16869 .unwrap_or(DiagnosticSeverity::Hint)
16870 } else {
16871 DiagnosticSeverity::Off
16872 };
16873 self.set_max_diagnostics_severity(new_severity, cx);
16874 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16875 self.active_diagnostics = ActiveDiagnostic::None;
16876 self.inline_diagnostics_update = Task::ready(());
16877 self.inline_diagnostics.clear();
16878 } else {
16879 self.refresh_inline_diagnostics(false, window, cx);
16880 }
16881
16882 cx.notify();
16883 }
16884
16885 pub fn toggle_minimap(
16886 &mut self,
16887 _: &ToggleMinimap,
16888 window: &mut Window,
16889 cx: &mut Context<Editor>,
16890 ) {
16891 if self.supports_minimap(cx) {
16892 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16893 }
16894 }
16895
16896 fn refresh_inline_diagnostics(
16897 &mut self,
16898 debounce: bool,
16899 window: &mut Window,
16900 cx: &mut Context<Self>,
16901 ) {
16902 let max_severity = ProjectSettings::get_global(cx)
16903 .diagnostics
16904 .inline
16905 .max_severity
16906 .unwrap_or(self.diagnostics_max_severity);
16907
16908 if !self.inline_diagnostics_enabled()
16909 || !self.show_inline_diagnostics
16910 || max_severity == DiagnosticSeverity::Off
16911 {
16912 self.inline_diagnostics_update = Task::ready(());
16913 self.inline_diagnostics.clear();
16914 return;
16915 }
16916
16917 let debounce_ms = ProjectSettings::get_global(cx)
16918 .diagnostics
16919 .inline
16920 .update_debounce_ms;
16921 let debounce = if debounce && debounce_ms > 0 {
16922 Some(Duration::from_millis(debounce_ms))
16923 } else {
16924 None
16925 };
16926 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16927 if let Some(debounce) = debounce {
16928 cx.background_executor().timer(debounce).await;
16929 }
16930 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16931 editor
16932 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16933 .ok()
16934 }) else {
16935 return;
16936 };
16937
16938 let new_inline_diagnostics = cx
16939 .background_spawn(async move {
16940 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16941 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16942 let message = diagnostic_entry
16943 .diagnostic
16944 .message
16945 .split_once('\n')
16946 .map(|(line, _)| line)
16947 .map(SharedString::new)
16948 .unwrap_or_else(|| {
16949 SharedString::from(diagnostic_entry.diagnostic.message)
16950 });
16951 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16952 let (Ok(i) | Err(i)) = inline_diagnostics
16953 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16954 inline_diagnostics.insert(
16955 i,
16956 (
16957 start_anchor,
16958 InlineDiagnostic {
16959 message,
16960 group_id: diagnostic_entry.diagnostic.group_id,
16961 start: diagnostic_entry.range.start.to_point(&snapshot),
16962 is_primary: diagnostic_entry.diagnostic.is_primary,
16963 severity: diagnostic_entry.diagnostic.severity,
16964 },
16965 ),
16966 );
16967 }
16968 inline_diagnostics
16969 })
16970 .await;
16971
16972 editor
16973 .update(cx, |editor, cx| {
16974 editor.inline_diagnostics = new_inline_diagnostics;
16975 cx.notify();
16976 })
16977 .ok();
16978 });
16979 }
16980
16981 fn pull_diagnostics(
16982 &mut self,
16983 buffer_id: Option<BufferId>,
16984 window: &Window,
16985 cx: &mut Context<Self>,
16986 ) -> Option<()> {
16987 if !self.mode().is_full() {
16988 return None;
16989 }
16990 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16991 .diagnostics
16992 .lsp_pull_diagnostics;
16993 if !pull_diagnostics_settings.enabled {
16994 return None;
16995 }
16996 let project = self.project.as_ref()?.downgrade();
16997 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16998 let mut buffers = self.buffer.read(cx).all_buffers();
16999 if let Some(buffer_id) = buffer_id {
17000 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17001 }
17002
17003 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17004 cx.background_executor().timer(debounce).await;
17005
17006 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17007 buffers
17008 .into_iter()
17009 .filter_map(|buffer| {
17010 project
17011 .update(cx, |project, cx| {
17012 project.lsp_store().update(cx, |lsp_store, cx| {
17013 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17014 })
17015 })
17016 .ok()
17017 })
17018 .collect::<FuturesUnordered<_>>()
17019 }) else {
17020 return;
17021 };
17022
17023 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17024 match pull_task {
17025 Ok(()) => {
17026 if editor
17027 .update_in(cx, |editor, window, cx| {
17028 editor.update_diagnostics_state(window, cx);
17029 })
17030 .is_err()
17031 {
17032 return;
17033 }
17034 }
17035 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17036 }
17037 }
17038 });
17039
17040 Some(())
17041 }
17042
17043 pub fn set_selections_from_remote(
17044 &mut self,
17045 selections: Vec<Selection<Anchor>>,
17046 pending_selection: Option<Selection<Anchor>>,
17047 window: &mut Window,
17048 cx: &mut Context<Self>,
17049 ) {
17050 let old_cursor_position = self.selections.newest_anchor().head();
17051 self.selections.change_with(cx, |s| {
17052 s.select_anchors(selections);
17053 if let Some(pending_selection) = pending_selection {
17054 s.set_pending(pending_selection, SelectMode::Character);
17055 } else {
17056 s.clear_pending();
17057 }
17058 });
17059 self.selections_did_change(
17060 false,
17061 &old_cursor_position,
17062 SelectionEffects::default(),
17063 window,
17064 cx,
17065 );
17066 }
17067
17068 pub fn transact(
17069 &mut self,
17070 window: &mut Window,
17071 cx: &mut Context<Self>,
17072 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17073 ) -> Option<TransactionId> {
17074 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17075 this.start_transaction_at(Instant::now(), window, cx);
17076 update(this, window, cx);
17077 this.end_transaction_at(Instant::now(), cx)
17078 })
17079 }
17080
17081 pub fn start_transaction_at(
17082 &mut self,
17083 now: Instant,
17084 window: &mut Window,
17085 cx: &mut Context<Self>,
17086 ) -> Option<TransactionId> {
17087 self.end_selection(window, cx);
17088 if let Some(tx_id) = self
17089 .buffer
17090 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17091 {
17092 self.selection_history
17093 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17094 cx.emit(EditorEvent::TransactionBegun {
17095 transaction_id: tx_id,
17096 });
17097 Some(tx_id)
17098 } else {
17099 None
17100 }
17101 }
17102
17103 pub fn end_transaction_at(
17104 &mut self,
17105 now: Instant,
17106 cx: &mut Context<Self>,
17107 ) -> Option<TransactionId> {
17108 if let Some(transaction_id) = self
17109 .buffer
17110 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17111 {
17112 if let Some((_, end_selections)) =
17113 self.selection_history.transaction_mut(transaction_id)
17114 {
17115 *end_selections = Some(self.selections.disjoint_anchors());
17116 } else {
17117 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17118 }
17119
17120 cx.emit(EditorEvent::Edited { transaction_id });
17121 Some(transaction_id)
17122 } else {
17123 None
17124 }
17125 }
17126
17127 pub fn modify_transaction_selection_history(
17128 &mut self,
17129 transaction_id: TransactionId,
17130 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17131 ) -> bool {
17132 self.selection_history
17133 .transaction_mut(transaction_id)
17134 .map(modify)
17135 .is_some()
17136 }
17137
17138 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17139 if self.selection_mark_mode {
17140 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17141 s.move_with(|_, sel| {
17142 sel.collapse_to(sel.head(), SelectionGoal::None);
17143 });
17144 })
17145 }
17146 self.selection_mark_mode = true;
17147 cx.notify();
17148 }
17149
17150 pub fn swap_selection_ends(
17151 &mut self,
17152 _: &actions::SwapSelectionEnds,
17153 window: &mut Window,
17154 cx: &mut Context<Self>,
17155 ) {
17156 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17157 s.move_with(|_, sel| {
17158 if sel.start != sel.end {
17159 sel.reversed = !sel.reversed
17160 }
17161 });
17162 });
17163 self.request_autoscroll(Autoscroll::newest(), cx);
17164 cx.notify();
17165 }
17166
17167 pub fn toggle_focus(
17168 workspace: &mut Workspace,
17169 _: &actions::ToggleFocus,
17170 window: &mut Window,
17171 cx: &mut Context<Workspace>,
17172 ) {
17173 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17174 return;
17175 };
17176 workspace.activate_item(&item, true, true, window, cx);
17177 }
17178
17179 pub fn toggle_fold(
17180 &mut self,
17181 _: &actions::ToggleFold,
17182 window: &mut Window,
17183 cx: &mut Context<Self>,
17184 ) {
17185 if self.is_singleton(cx) {
17186 let selection = self.selections.newest::<Point>(cx);
17187
17188 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17189 let range = if selection.is_empty() {
17190 let point = selection.head().to_display_point(&display_map);
17191 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17192 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17193 .to_point(&display_map);
17194 start..end
17195 } else {
17196 selection.range()
17197 };
17198 if display_map.folds_in_range(range).next().is_some() {
17199 self.unfold_lines(&Default::default(), window, cx)
17200 } else {
17201 self.fold(&Default::default(), window, cx)
17202 }
17203 } else {
17204 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17205 let buffer_ids: HashSet<_> = self
17206 .selections
17207 .disjoint_anchor_ranges()
17208 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17209 .collect();
17210
17211 let should_unfold = buffer_ids
17212 .iter()
17213 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17214
17215 for buffer_id in buffer_ids {
17216 if should_unfold {
17217 self.unfold_buffer(buffer_id, cx);
17218 } else {
17219 self.fold_buffer(buffer_id, cx);
17220 }
17221 }
17222 }
17223 }
17224
17225 pub fn toggle_fold_recursive(
17226 &mut self,
17227 _: &actions::ToggleFoldRecursive,
17228 window: &mut Window,
17229 cx: &mut Context<Self>,
17230 ) {
17231 let selection = self.selections.newest::<Point>(cx);
17232
17233 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17234 let range = if selection.is_empty() {
17235 let point = selection.head().to_display_point(&display_map);
17236 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17237 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17238 .to_point(&display_map);
17239 start..end
17240 } else {
17241 selection.range()
17242 };
17243 if display_map.folds_in_range(range).next().is_some() {
17244 self.unfold_recursive(&Default::default(), window, cx)
17245 } else {
17246 self.fold_recursive(&Default::default(), window, cx)
17247 }
17248 }
17249
17250 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17251 if self.is_singleton(cx) {
17252 let mut to_fold = Vec::new();
17253 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17254 let selections = self.selections.all_adjusted(cx);
17255
17256 for selection in selections {
17257 let range = selection.range().sorted();
17258 let buffer_start_row = range.start.row;
17259
17260 if range.start.row != range.end.row {
17261 let mut found = false;
17262 let mut row = range.start.row;
17263 while row <= range.end.row {
17264 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17265 {
17266 found = true;
17267 row = crease.range().end.row + 1;
17268 to_fold.push(crease);
17269 } else {
17270 row += 1
17271 }
17272 }
17273 if found {
17274 continue;
17275 }
17276 }
17277
17278 for row in (0..=range.start.row).rev() {
17279 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17280 if crease.range().end.row >= buffer_start_row {
17281 to_fold.push(crease);
17282 if row <= range.start.row {
17283 break;
17284 }
17285 }
17286 }
17287 }
17288 }
17289
17290 self.fold_creases(to_fold, true, window, cx);
17291 } else {
17292 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17293 let buffer_ids = self
17294 .selections
17295 .disjoint_anchor_ranges()
17296 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17297 .collect::<HashSet<_>>();
17298 for buffer_id in buffer_ids {
17299 self.fold_buffer(buffer_id, cx);
17300 }
17301 }
17302 }
17303
17304 pub fn toggle_fold_all(
17305 &mut self,
17306 _: &actions::ToggleFoldAll,
17307 window: &mut Window,
17308 cx: &mut Context<Self>,
17309 ) {
17310 if self.buffer.read(cx).is_singleton() {
17311 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17312 let has_folds = display_map
17313 .folds_in_range(0..display_map.buffer_snapshot.len())
17314 .next()
17315 .is_some();
17316
17317 if has_folds {
17318 self.unfold_all(&actions::UnfoldAll, window, cx);
17319 } else {
17320 self.fold_all(&actions::FoldAll, window, cx);
17321 }
17322 } else {
17323 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17324 let should_unfold = buffer_ids
17325 .iter()
17326 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17327
17328 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17329 editor
17330 .update_in(cx, |editor, _, cx| {
17331 for buffer_id in buffer_ids {
17332 if should_unfold {
17333 editor.unfold_buffer(buffer_id, cx);
17334 } else {
17335 editor.fold_buffer(buffer_id, cx);
17336 }
17337 }
17338 })
17339 .ok();
17340 });
17341 }
17342 }
17343
17344 fn fold_at_level(
17345 &mut self,
17346 fold_at: &FoldAtLevel,
17347 window: &mut Window,
17348 cx: &mut Context<Self>,
17349 ) {
17350 if !self.buffer.read(cx).is_singleton() {
17351 return;
17352 }
17353
17354 let fold_at_level = fold_at.0;
17355 let snapshot = self.buffer.read(cx).snapshot(cx);
17356 let mut to_fold = Vec::new();
17357 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17358
17359 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17360 while start_row < end_row {
17361 match self
17362 .snapshot(window, cx)
17363 .crease_for_buffer_row(MultiBufferRow(start_row))
17364 {
17365 Some(crease) => {
17366 let nested_start_row = crease.range().start.row + 1;
17367 let nested_end_row = crease.range().end.row;
17368
17369 if current_level < fold_at_level {
17370 stack.push((nested_start_row, nested_end_row, current_level + 1));
17371 } else if current_level == fold_at_level {
17372 to_fold.push(crease);
17373 }
17374
17375 start_row = nested_end_row + 1;
17376 }
17377 None => start_row += 1,
17378 }
17379 }
17380 }
17381
17382 self.fold_creases(to_fold, true, window, cx);
17383 }
17384
17385 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17386 if self.buffer.read(cx).is_singleton() {
17387 let mut fold_ranges = Vec::new();
17388 let snapshot = self.buffer.read(cx).snapshot(cx);
17389
17390 for row in 0..snapshot.max_row().0 {
17391 if let Some(foldable_range) = self
17392 .snapshot(window, cx)
17393 .crease_for_buffer_row(MultiBufferRow(row))
17394 {
17395 fold_ranges.push(foldable_range);
17396 }
17397 }
17398
17399 self.fold_creases(fold_ranges, true, window, cx);
17400 } else {
17401 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17402 editor
17403 .update_in(cx, |editor, _, cx| {
17404 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17405 editor.fold_buffer(buffer_id, cx);
17406 }
17407 })
17408 .ok();
17409 });
17410 }
17411 }
17412
17413 pub fn fold_function_bodies(
17414 &mut self,
17415 _: &actions::FoldFunctionBodies,
17416 window: &mut Window,
17417 cx: &mut Context<Self>,
17418 ) {
17419 let snapshot = self.buffer.read(cx).snapshot(cx);
17420
17421 let ranges = snapshot
17422 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17423 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17424 .collect::<Vec<_>>();
17425
17426 let creases = ranges
17427 .into_iter()
17428 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17429 .collect();
17430
17431 self.fold_creases(creases, true, window, cx);
17432 }
17433
17434 pub fn fold_recursive(
17435 &mut self,
17436 _: &actions::FoldRecursive,
17437 window: &mut Window,
17438 cx: &mut Context<Self>,
17439 ) {
17440 let mut to_fold = Vec::new();
17441 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17442 let selections = self.selections.all_adjusted(cx);
17443
17444 for selection in selections {
17445 let range = selection.range().sorted();
17446 let buffer_start_row = range.start.row;
17447
17448 if range.start.row != range.end.row {
17449 let mut found = false;
17450 for row in range.start.row..=range.end.row {
17451 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17452 found = true;
17453 to_fold.push(crease);
17454 }
17455 }
17456 if found {
17457 continue;
17458 }
17459 }
17460
17461 for row in (0..=range.start.row).rev() {
17462 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17463 if crease.range().end.row >= buffer_start_row {
17464 to_fold.push(crease);
17465 } else {
17466 break;
17467 }
17468 }
17469 }
17470 }
17471
17472 self.fold_creases(to_fold, true, window, cx);
17473 }
17474
17475 pub fn fold_at(
17476 &mut self,
17477 buffer_row: MultiBufferRow,
17478 window: &mut Window,
17479 cx: &mut Context<Self>,
17480 ) {
17481 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17482
17483 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17484 let autoscroll = self
17485 .selections
17486 .all::<Point>(cx)
17487 .iter()
17488 .any(|selection| crease.range().overlaps(&selection.range()));
17489
17490 self.fold_creases(vec![crease], autoscroll, window, cx);
17491 }
17492 }
17493
17494 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17495 if self.is_singleton(cx) {
17496 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17497 let buffer = &display_map.buffer_snapshot;
17498 let selections = self.selections.all::<Point>(cx);
17499 let ranges = selections
17500 .iter()
17501 .map(|s| {
17502 let range = s.display_range(&display_map).sorted();
17503 let mut start = range.start.to_point(&display_map);
17504 let mut end = range.end.to_point(&display_map);
17505 start.column = 0;
17506 end.column = buffer.line_len(MultiBufferRow(end.row));
17507 start..end
17508 })
17509 .collect::<Vec<_>>();
17510
17511 self.unfold_ranges(&ranges, true, true, cx);
17512 } else {
17513 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17514 let buffer_ids = self
17515 .selections
17516 .disjoint_anchor_ranges()
17517 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17518 .collect::<HashSet<_>>();
17519 for buffer_id in buffer_ids {
17520 self.unfold_buffer(buffer_id, cx);
17521 }
17522 }
17523 }
17524
17525 pub fn unfold_recursive(
17526 &mut self,
17527 _: &UnfoldRecursive,
17528 _window: &mut Window,
17529 cx: &mut Context<Self>,
17530 ) {
17531 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17532 let selections = self.selections.all::<Point>(cx);
17533 let ranges = selections
17534 .iter()
17535 .map(|s| {
17536 let mut range = s.display_range(&display_map).sorted();
17537 *range.start.column_mut() = 0;
17538 *range.end.column_mut() = display_map.line_len(range.end.row());
17539 let start = range.start.to_point(&display_map);
17540 let end = range.end.to_point(&display_map);
17541 start..end
17542 })
17543 .collect::<Vec<_>>();
17544
17545 self.unfold_ranges(&ranges, true, true, cx);
17546 }
17547
17548 pub fn unfold_at(
17549 &mut self,
17550 buffer_row: MultiBufferRow,
17551 _window: &mut Window,
17552 cx: &mut Context<Self>,
17553 ) {
17554 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17555
17556 let intersection_range = Point::new(buffer_row.0, 0)
17557 ..Point::new(
17558 buffer_row.0,
17559 display_map.buffer_snapshot.line_len(buffer_row),
17560 );
17561
17562 let autoscroll = self
17563 .selections
17564 .all::<Point>(cx)
17565 .iter()
17566 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17567
17568 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17569 }
17570
17571 pub fn unfold_all(
17572 &mut self,
17573 _: &actions::UnfoldAll,
17574 _window: &mut Window,
17575 cx: &mut Context<Self>,
17576 ) {
17577 if self.buffer.read(cx).is_singleton() {
17578 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17579 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17580 } else {
17581 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17582 editor
17583 .update(cx, |editor, cx| {
17584 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17585 editor.unfold_buffer(buffer_id, cx);
17586 }
17587 })
17588 .ok();
17589 });
17590 }
17591 }
17592
17593 pub fn fold_selected_ranges(
17594 &mut self,
17595 _: &FoldSelectedRanges,
17596 window: &mut Window,
17597 cx: &mut Context<Self>,
17598 ) {
17599 let selections = self.selections.all_adjusted(cx);
17600 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17601 let ranges = selections
17602 .into_iter()
17603 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17604 .collect::<Vec<_>>();
17605 self.fold_creases(ranges, true, window, cx);
17606 }
17607
17608 pub fn fold_ranges<T: ToOffset + Clone>(
17609 &mut self,
17610 ranges: Vec<Range<T>>,
17611 auto_scroll: bool,
17612 window: &mut Window,
17613 cx: &mut Context<Self>,
17614 ) {
17615 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17616 let ranges = ranges
17617 .into_iter()
17618 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17619 .collect::<Vec<_>>();
17620 self.fold_creases(ranges, auto_scroll, window, cx);
17621 }
17622
17623 pub fn fold_creases<T: ToOffset + Clone>(
17624 &mut self,
17625 creases: Vec<Crease<T>>,
17626 auto_scroll: bool,
17627 _window: &mut Window,
17628 cx: &mut Context<Self>,
17629 ) {
17630 if creases.is_empty() {
17631 return;
17632 }
17633
17634 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17635
17636 if auto_scroll {
17637 self.request_autoscroll(Autoscroll::fit(), cx);
17638 }
17639
17640 cx.notify();
17641
17642 self.scrollbar_marker_state.dirty = true;
17643 self.folds_did_change(cx);
17644 }
17645
17646 /// Removes any folds whose ranges intersect any of the given ranges.
17647 pub fn unfold_ranges<T: ToOffset + Clone>(
17648 &mut self,
17649 ranges: &[Range<T>],
17650 inclusive: bool,
17651 auto_scroll: bool,
17652 cx: &mut Context<Self>,
17653 ) {
17654 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17655 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17656 });
17657 self.folds_did_change(cx);
17658 }
17659
17660 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17661 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17662 return;
17663 }
17664 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17665 self.display_map.update(cx, |display_map, cx| {
17666 display_map.fold_buffers([buffer_id], cx)
17667 });
17668 cx.emit(EditorEvent::BufferFoldToggled {
17669 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17670 folded: true,
17671 });
17672 cx.notify();
17673 }
17674
17675 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17676 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17677 return;
17678 }
17679 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17680 self.display_map.update(cx, |display_map, cx| {
17681 display_map.unfold_buffers([buffer_id], cx);
17682 });
17683 cx.emit(EditorEvent::BufferFoldToggled {
17684 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17685 folded: false,
17686 });
17687 cx.notify();
17688 }
17689
17690 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17691 self.display_map.read(cx).is_buffer_folded(buffer)
17692 }
17693
17694 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17695 self.display_map.read(cx).folded_buffers()
17696 }
17697
17698 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17699 self.display_map.update(cx, |display_map, cx| {
17700 display_map.disable_header_for_buffer(buffer_id, cx);
17701 });
17702 cx.notify();
17703 }
17704
17705 /// Removes any folds with the given ranges.
17706 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17707 &mut self,
17708 ranges: &[Range<T>],
17709 type_id: TypeId,
17710 auto_scroll: bool,
17711 cx: &mut Context<Self>,
17712 ) {
17713 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17714 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17715 });
17716 self.folds_did_change(cx);
17717 }
17718
17719 fn remove_folds_with<T: ToOffset + Clone>(
17720 &mut self,
17721 ranges: &[Range<T>],
17722 auto_scroll: bool,
17723 cx: &mut Context<Self>,
17724 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17725 ) {
17726 if ranges.is_empty() {
17727 return;
17728 }
17729
17730 let mut buffers_affected = HashSet::default();
17731 let multi_buffer = self.buffer().read(cx);
17732 for range in ranges {
17733 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17734 buffers_affected.insert(buffer.read(cx).remote_id());
17735 };
17736 }
17737
17738 self.display_map.update(cx, update);
17739
17740 if auto_scroll {
17741 self.request_autoscroll(Autoscroll::fit(), cx);
17742 }
17743
17744 cx.notify();
17745 self.scrollbar_marker_state.dirty = true;
17746 self.active_indent_guides_state.dirty = true;
17747 }
17748
17749 pub fn update_renderer_widths(
17750 &mut self,
17751 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17752 cx: &mut Context<Self>,
17753 ) -> bool {
17754 self.display_map
17755 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17756 }
17757
17758 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17759 self.display_map.read(cx).fold_placeholder.clone()
17760 }
17761
17762 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17763 self.buffer.update(cx, |buffer, cx| {
17764 buffer.set_all_diff_hunks_expanded(cx);
17765 });
17766 }
17767
17768 pub fn expand_all_diff_hunks(
17769 &mut self,
17770 _: &ExpandAllDiffHunks,
17771 _window: &mut Window,
17772 cx: &mut Context<Self>,
17773 ) {
17774 self.buffer.update(cx, |buffer, cx| {
17775 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17776 });
17777 }
17778
17779 pub fn toggle_selected_diff_hunks(
17780 &mut self,
17781 _: &ToggleSelectedDiffHunks,
17782 _window: &mut Window,
17783 cx: &mut Context<Self>,
17784 ) {
17785 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17786 self.toggle_diff_hunks_in_ranges(ranges, cx);
17787 }
17788
17789 pub fn diff_hunks_in_ranges<'a>(
17790 &'a self,
17791 ranges: &'a [Range<Anchor>],
17792 buffer: &'a MultiBufferSnapshot,
17793 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17794 ranges.iter().flat_map(move |range| {
17795 let end_excerpt_id = range.end.excerpt_id;
17796 let range = range.to_point(buffer);
17797 let mut peek_end = range.end;
17798 if range.end.row < buffer.max_row().0 {
17799 peek_end = Point::new(range.end.row + 1, 0);
17800 }
17801 buffer
17802 .diff_hunks_in_range(range.start..peek_end)
17803 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17804 })
17805 }
17806
17807 pub fn has_stageable_diff_hunks_in_ranges(
17808 &self,
17809 ranges: &[Range<Anchor>],
17810 snapshot: &MultiBufferSnapshot,
17811 ) -> bool {
17812 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17813 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17814 }
17815
17816 pub fn toggle_staged_selected_diff_hunks(
17817 &mut self,
17818 _: &::git::ToggleStaged,
17819 _: &mut Window,
17820 cx: &mut Context<Self>,
17821 ) {
17822 let snapshot = self.buffer.read(cx).snapshot(cx);
17823 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17824 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17825 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17826 }
17827
17828 pub fn set_render_diff_hunk_controls(
17829 &mut self,
17830 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17831 cx: &mut Context<Self>,
17832 ) {
17833 self.render_diff_hunk_controls = render_diff_hunk_controls;
17834 cx.notify();
17835 }
17836
17837 pub fn stage_and_next(
17838 &mut self,
17839 _: &::git::StageAndNext,
17840 window: &mut Window,
17841 cx: &mut Context<Self>,
17842 ) {
17843 self.do_stage_or_unstage_and_next(true, window, cx);
17844 }
17845
17846 pub fn unstage_and_next(
17847 &mut self,
17848 _: &::git::UnstageAndNext,
17849 window: &mut Window,
17850 cx: &mut Context<Self>,
17851 ) {
17852 self.do_stage_or_unstage_and_next(false, window, cx);
17853 }
17854
17855 pub fn stage_or_unstage_diff_hunks(
17856 &mut self,
17857 stage: bool,
17858 ranges: Vec<Range<Anchor>>,
17859 cx: &mut Context<Self>,
17860 ) {
17861 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17862 cx.spawn(async move |this, cx| {
17863 task.await?;
17864 this.update(cx, |this, cx| {
17865 let snapshot = this.buffer.read(cx).snapshot(cx);
17866 let chunk_by = this
17867 .diff_hunks_in_ranges(&ranges, &snapshot)
17868 .chunk_by(|hunk| hunk.buffer_id);
17869 for (buffer_id, hunks) in &chunk_by {
17870 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17871 }
17872 })
17873 })
17874 .detach_and_log_err(cx);
17875 }
17876
17877 fn save_buffers_for_ranges_if_needed(
17878 &mut self,
17879 ranges: &[Range<Anchor>],
17880 cx: &mut Context<Editor>,
17881 ) -> Task<Result<()>> {
17882 let multibuffer = self.buffer.read(cx);
17883 let snapshot = multibuffer.read(cx);
17884 let buffer_ids: HashSet<_> = ranges
17885 .iter()
17886 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17887 .collect();
17888 drop(snapshot);
17889
17890 let mut buffers = HashSet::default();
17891 for buffer_id in buffer_ids {
17892 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17893 let buffer = buffer_entity.read(cx);
17894 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17895 {
17896 buffers.insert(buffer_entity);
17897 }
17898 }
17899 }
17900
17901 if let Some(project) = &self.project {
17902 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17903 } else {
17904 Task::ready(Ok(()))
17905 }
17906 }
17907
17908 fn do_stage_or_unstage_and_next(
17909 &mut self,
17910 stage: bool,
17911 window: &mut Window,
17912 cx: &mut Context<Self>,
17913 ) {
17914 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17915
17916 if ranges.iter().any(|range| range.start != range.end) {
17917 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17918 return;
17919 }
17920
17921 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17922 let snapshot = self.snapshot(window, cx);
17923 let position = self.selections.newest::<Point>(cx).head();
17924 let mut row = snapshot
17925 .buffer_snapshot
17926 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17927 .find(|hunk| hunk.row_range.start.0 > position.row)
17928 .map(|hunk| hunk.row_range.start);
17929
17930 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17931 // Outside of the project diff editor, wrap around to the beginning.
17932 if !all_diff_hunks_expanded {
17933 row = row.or_else(|| {
17934 snapshot
17935 .buffer_snapshot
17936 .diff_hunks_in_range(Point::zero()..position)
17937 .find(|hunk| hunk.row_range.end.0 < position.row)
17938 .map(|hunk| hunk.row_range.start)
17939 });
17940 }
17941
17942 if let Some(row) = row {
17943 let destination = Point::new(row.0, 0);
17944 let autoscroll = Autoscroll::center();
17945
17946 self.unfold_ranges(&[destination..destination], false, false, cx);
17947 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17948 s.select_ranges([destination..destination]);
17949 });
17950 }
17951 }
17952
17953 fn do_stage_or_unstage(
17954 &self,
17955 stage: bool,
17956 buffer_id: BufferId,
17957 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17958 cx: &mut App,
17959 ) -> Option<()> {
17960 let project = self.project.as_ref()?;
17961 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17962 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17963 let buffer_snapshot = buffer.read(cx).snapshot();
17964 let file_exists = buffer_snapshot
17965 .file()
17966 .is_some_and(|file| file.disk_state().exists());
17967 diff.update(cx, |diff, cx| {
17968 diff.stage_or_unstage_hunks(
17969 stage,
17970 &hunks
17971 .map(|hunk| buffer_diff::DiffHunk {
17972 buffer_range: hunk.buffer_range,
17973 diff_base_byte_range: hunk.diff_base_byte_range,
17974 secondary_status: hunk.secondary_status,
17975 range: Point::zero()..Point::zero(), // unused
17976 })
17977 .collect::<Vec<_>>(),
17978 &buffer_snapshot,
17979 file_exists,
17980 cx,
17981 )
17982 });
17983 None
17984 }
17985
17986 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17987 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17988 self.buffer
17989 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17990 }
17991
17992 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17993 self.buffer.update(cx, |buffer, cx| {
17994 let ranges = vec![Anchor::min()..Anchor::max()];
17995 if !buffer.all_diff_hunks_expanded()
17996 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17997 {
17998 buffer.collapse_diff_hunks(ranges, cx);
17999 true
18000 } else {
18001 false
18002 }
18003 })
18004 }
18005
18006 fn toggle_diff_hunks_in_ranges(
18007 &mut self,
18008 ranges: Vec<Range<Anchor>>,
18009 cx: &mut Context<Editor>,
18010 ) {
18011 self.buffer.update(cx, |buffer, cx| {
18012 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18013 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18014 })
18015 }
18016
18017 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18018 self.buffer.update(cx, |buffer, cx| {
18019 let snapshot = buffer.snapshot(cx);
18020 let excerpt_id = range.end.excerpt_id;
18021 let point_range = range.to_point(&snapshot);
18022 let expand = !buffer.single_hunk_is_expanded(range, cx);
18023 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18024 })
18025 }
18026
18027 pub(crate) fn apply_all_diff_hunks(
18028 &mut self,
18029 _: &ApplyAllDiffHunks,
18030 window: &mut Window,
18031 cx: &mut Context<Self>,
18032 ) {
18033 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18034
18035 let buffers = self.buffer.read(cx).all_buffers();
18036 for branch_buffer in buffers {
18037 branch_buffer.update(cx, |branch_buffer, cx| {
18038 branch_buffer.merge_into_base(Vec::new(), cx);
18039 });
18040 }
18041
18042 if let Some(project) = self.project.clone() {
18043 self.save(
18044 SaveOptions {
18045 format: true,
18046 autosave: false,
18047 },
18048 project,
18049 window,
18050 cx,
18051 )
18052 .detach_and_log_err(cx);
18053 }
18054 }
18055
18056 pub(crate) fn apply_selected_diff_hunks(
18057 &mut self,
18058 _: &ApplyDiffHunk,
18059 window: &mut Window,
18060 cx: &mut Context<Self>,
18061 ) {
18062 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18063 let snapshot = self.snapshot(window, cx);
18064 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18065 let mut ranges_by_buffer = HashMap::default();
18066 self.transact(window, cx, |editor, _window, cx| {
18067 for hunk in hunks {
18068 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18069 ranges_by_buffer
18070 .entry(buffer.clone())
18071 .or_insert_with(Vec::new)
18072 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18073 }
18074 }
18075
18076 for (buffer, ranges) in ranges_by_buffer {
18077 buffer.update(cx, |buffer, cx| {
18078 buffer.merge_into_base(ranges, cx);
18079 });
18080 }
18081 });
18082
18083 if let Some(project) = self.project.clone() {
18084 self.save(
18085 SaveOptions {
18086 format: true,
18087 autosave: false,
18088 },
18089 project,
18090 window,
18091 cx,
18092 )
18093 .detach_and_log_err(cx);
18094 }
18095 }
18096
18097 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18098 if hovered != self.gutter_hovered {
18099 self.gutter_hovered = hovered;
18100 cx.notify();
18101 }
18102 }
18103
18104 pub fn insert_blocks(
18105 &mut self,
18106 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18107 autoscroll: Option<Autoscroll>,
18108 cx: &mut Context<Self>,
18109 ) -> Vec<CustomBlockId> {
18110 let blocks = self
18111 .display_map
18112 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18113 if let Some(autoscroll) = autoscroll {
18114 self.request_autoscroll(autoscroll, cx);
18115 }
18116 cx.notify();
18117 blocks
18118 }
18119
18120 pub fn resize_blocks(
18121 &mut self,
18122 heights: HashMap<CustomBlockId, u32>,
18123 autoscroll: Option<Autoscroll>,
18124 cx: &mut Context<Self>,
18125 ) {
18126 self.display_map
18127 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18128 if let Some(autoscroll) = autoscroll {
18129 self.request_autoscroll(autoscroll, cx);
18130 }
18131 cx.notify();
18132 }
18133
18134 pub fn replace_blocks(
18135 &mut self,
18136 renderers: HashMap<CustomBlockId, RenderBlock>,
18137 autoscroll: Option<Autoscroll>,
18138 cx: &mut Context<Self>,
18139 ) {
18140 self.display_map
18141 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18142 if let Some(autoscroll) = autoscroll {
18143 self.request_autoscroll(autoscroll, cx);
18144 }
18145 cx.notify();
18146 }
18147
18148 pub fn remove_blocks(
18149 &mut self,
18150 block_ids: HashSet<CustomBlockId>,
18151 autoscroll: Option<Autoscroll>,
18152 cx: &mut Context<Self>,
18153 ) {
18154 self.display_map.update(cx, |display_map, cx| {
18155 display_map.remove_blocks(block_ids, cx)
18156 });
18157 if let Some(autoscroll) = autoscroll {
18158 self.request_autoscroll(autoscroll, cx);
18159 }
18160 cx.notify();
18161 }
18162
18163 pub fn row_for_block(
18164 &self,
18165 block_id: CustomBlockId,
18166 cx: &mut Context<Self>,
18167 ) -> Option<DisplayRow> {
18168 self.display_map
18169 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18170 }
18171
18172 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18173 self.focused_block = Some(focused_block);
18174 }
18175
18176 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18177 self.focused_block.take()
18178 }
18179
18180 pub fn insert_creases(
18181 &mut self,
18182 creases: impl IntoIterator<Item = Crease<Anchor>>,
18183 cx: &mut Context<Self>,
18184 ) -> Vec<CreaseId> {
18185 self.display_map
18186 .update(cx, |map, cx| map.insert_creases(creases, cx))
18187 }
18188
18189 pub fn remove_creases(
18190 &mut self,
18191 ids: impl IntoIterator<Item = CreaseId>,
18192 cx: &mut Context<Self>,
18193 ) -> Vec<(CreaseId, Range<Anchor>)> {
18194 self.display_map
18195 .update(cx, |map, cx| map.remove_creases(ids, cx))
18196 }
18197
18198 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18199 self.display_map
18200 .update(cx, |map, cx| map.snapshot(cx))
18201 .longest_row()
18202 }
18203
18204 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18205 self.display_map
18206 .update(cx, |map, cx| map.snapshot(cx))
18207 .max_point()
18208 }
18209
18210 pub fn text(&self, cx: &App) -> String {
18211 self.buffer.read(cx).read(cx).text()
18212 }
18213
18214 pub fn is_empty(&self, cx: &App) -> bool {
18215 self.buffer.read(cx).read(cx).is_empty()
18216 }
18217
18218 pub fn text_option(&self, cx: &App) -> Option<String> {
18219 let text = self.text(cx);
18220 let text = text.trim();
18221
18222 if text.is_empty() {
18223 return None;
18224 }
18225
18226 Some(text.to_string())
18227 }
18228
18229 pub fn set_text(
18230 &mut self,
18231 text: impl Into<Arc<str>>,
18232 window: &mut Window,
18233 cx: &mut Context<Self>,
18234 ) {
18235 self.transact(window, cx, |this, _, cx| {
18236 this.buffer
18237 .read(cx)
18238 .as_singleton()
18239 .expect("you can only call set_text on editors for singleton buffers")
18240 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18241 });
18242 }
18243
18244 pub fn display_text(&self, cx: &mut App) -> String {
18245 self.display_map
18246 .update(cx, |map, cx| map.snapshot(cx))
18247 .text()
18248 }
18249
18250 fn create_minimap(
18251 &self,
18252 minimap_settings: MinimapSettings,
18253 window: &mut Window,
18254 cx: &mut Context<Self>,
18255 ) -> Option<Entity<Self>> {
18256 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18257 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18258 }
18259
18260 fn initialize_new_minimap(
18261 &self,
18262 minimap_settings: MinimapSettings,
18263 window: &mut Window,
18264 cx: &mut Context<Self>,
18265 ) -> Entity<Self> {
18266 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18267
18268 let mut minimap = Editor::new_internal(
18269 EditorMode::Minimap {
18270 parent: cx.weak_entity(),
18271 },
18272 self.buffer.clone(),
18273 None,
18274 Some(self.display_map.clone()),
18275 window,
18276 cx,
18277 );
18278 minimap.scroll_manager.clone_state(&self.scroll_manager);
18279 minimap.set_text_style_refinement(TextStyleRefinement {
18280 font_size: Some(MINIMAP_FONT_SIZE),
18281 font_weight: Some(MINIMAP_FONT_WEIGHT),
18282 ..Default::default()
18283 });
18284 minimap.update_minimap_configuration(minimap_settings, cx);
18285 cx.new(|_| minimap)
18286 }
18287
18288 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18289 let current_line_highlight = minimap_settings
18290 .current_line_highlight
18291 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18292 self.set_current_line_highlight(Some(current_line_highlight));
18293 }
18294
18295 pub fn minimap(&self) -> Option<&Entity<Self>> {
18296 self.minimap
18297 .as_ref()
18298 .filter(|_| self.minimap_visibility.visible())
18299 }
18300
18301 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18302 let mut wrap_guides = smallvec![];
18303
18304 if self.show_wrap_guides == Some(false) {
18305 return wrap_guides;
18306 }
18307
18308 let settings = self.buffer.read(cx).language_settings(cx);
18309 if settings.show_wrap_guides {
18310 match self.soft_wrap_mode(cx) {
18311 SoftWrap::Column(soft_wrap) => {
18312 wrap_guides.push((soft_wrap as usize, true));
18313 }
18314 SoftWrap::Bounded(soft_wrap) => {
18315 wrap_guides.push((soft_wrap as usize, true));
18316 }
18317 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18318 }
18319 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18320 }
18321
18322 wrap_guides
18323 }
18324
18325 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18326 let settings = self.buffer.read(cx).language_settings(cx);
18327 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18328 match mode {
18329 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18330 SoftWrap::None
18331 }
18332 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18333 language_settings::SoftWrap::PreferredLineLength => {
18334 SoftWrap::Column(settings.preferred_line_length)
18335 }
18336 language_settings::SoftWrap::Bounded => {
18337 SoftWrap::Bounded(settings.preferred_line_length)
18338 }
18339 }
18340 }
18341
18342 pub fn set_soft_wrap_mode(
18343 &mut self,
18344 mode: language_settings::SoftWrap,
18345
18346 cx: &mut Context<Self>,
18347 ) {
18348 self.soft_wrap_mode_override = Some(mode);
18349 cx.notify();
18350 }
18351
18352 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18353 self.hard_wrap = hard_wrap;
18354 cx.notify();
18355 }
18356
18357 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18358 self.text_style_refinement = Some(style);
18359 }
18360
18361 /// called by the Element so we know what style we were most recently rendered with.
18362 pub(crate) fn set_style(
18363 &mut self,
18364 style: EditorStyle,
18365 window: &mut Window,
18366 cx: &mut Context<Self>,
18367 ) {
18368 // We intentionally do not inform the display map about the minimap style
18369 // so that wrapping is not recalculated and stays consistent for the editor
18370 // and its linked minimap.
18371 if !self.mode.is_minimap() {
18372 let rem_size = window.rem_size();
18373 self.display_map.update(cx, |map, cx| {
18374 map.set_font(
18375 style.text.font(),
18376 style.text.font_size.to_pixels(rem_size),
18377 cx,
18378 )
18379 });
18380 }
18381 self.style = Some(style);
18382 }
18383
18384 pub fn style(&self) -> Option<&EditorStyle> {
18385 self.style.as_ref()
18386 }
18387
18388 // Called by the element. This method is not designed to be called outside of the editor
18389 // element's layout code because it does not notify when rewrapping is computed synchronously.
18390 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18391 self.display_map
18392 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18393 }
18394
18395 pub fn set_soft_wrap(&mut self) {
18396 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18397 }
18398
18399 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18400 if self.soft_wrap_mode_override.is_some() {
18401 self.soft_wrap_mode_override.take();
18402 } else {
18403 let soft_wrap = match self.soft_wrap_mode(cx) {
18404 SoftWrap::GitDiff => return,
18405 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18406 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18407 language_settings::SoftWrap::None
18408 }
18409 };
18410 self.soft_wrap_mode_override = Some(soft_wrap);
18411 }
18412 cx.notify();
18413 }
18414
18415 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18416 let Some(workspace) = self.workspace() else {
18417 return;
18418 };
18419 let fs = workspace.read(cx).app_state().fs.clone();
18420 let current_show = TabBarSettings::get_global(cx).show;
18421 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18422 setting.show = Some(!current_show);
18423 });
18424 }
18425
18426 pub fn toggle_indent_guides(
18427 &mut self,
18428 _: &ToggleIndentGuides,
18429 _: &mut Window,
18430 cx: &mut Context<Self>,
18431 ) {
18432 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18433 self.buffer
18434 .read(cx)
18435 .language_settings(cx)
18436 .indent_guides
18437 .enabled
18438 });
18439 self.show_indent_guides = Some(!currently_enabled);
18440 cx.notify();
18441 }
18442
18443 fn should_show_indent_guides(&self) -> Option<bool> {
18444 self.show_indent_guides
18445 }
18446
18447 pub fn toggle_line_numbers(
18448 &mut self,
18449 _: &ToggleLineNumbers,
18450 _: &mut Window,
18451 cx: &mut Context<Self>,
18452 ) {
18453 let mut editor_settings = EditorSettings::get_global(cx).clone();
18454 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18455 EditorSettings::override_global(editor_settings, cx);
18456 }
18457
18458 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18459 if let Some(show_line_numbers) = self.show_line_numbers {
18460 return show_line_numbers;
18461 }
18462 EditorSettings::get_global(cx).gutter.line_numbers
18463 }
18464
18465 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18466 self.use_relative_line_numbers
18467 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18468 }
18469
18470 pub fn toggle_relative_line_numbers(
18471 &mut self,
18472 _: &ToggleRelativeLineNumbers,
18473 _: &mut Window,
18474 cx: &mut Context<Self>,
18475 ) {
18476 let is_relative = self.should_use_relative_line_numbers(cx);
18477 self.set_relative_line_number(Some(!is_relative), cx)
18478 }
18479
18480 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18481 self.use_relative_line_numbers = is_relative;
18482 cx.notify();
18483 }
18484
18485 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18486 self.show_gutter = show_gutter;
18487 cx.notify();
18488 }
18489
18490 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18491 self.show_scrollbars = ScrollbarAxes {
18492 horizontal: show,
18493 vertical: show,
18494 };
18495 cx.notify();
18496 }
18497
18498 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18499 self.show_scrollbars.vertical = show;
18500 cx.notify();
18501 }
18502
18503 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18504 self.show_scrollbars.horizontal = show;
18505 cx.notify();
18506 }
18507
18508 pub fn set_minimap_visibility(
18509 &mut self,
18510 minimap_visibility: MinimapVisibility,
18511 window: &mut Window,
18512 cx: &mut Context<Self>,
18513 ) {
18514 if self.minimap_visibility != minimap_visibility {
18515 if minimap_visibility.visible() && self.minimap.is_none() {
18516 let minimap_settings = EditorSettings::get_global(cx).minimap;
18517 self.minimap =
18518 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18519 }
18520 self.minimap_visibility = minimap_visibility;
18521 cx.notify();
18522 }
18523 }
18524
18525 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18526 self.set_show_scrollbars(false, cx);
18527 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18528 }
18529
18530 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18531 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18532 }
18533
18534 /// Normally the text in full mode and auto height editors is padded on the
18535 /// left side by roughly half a character width for improved hit testing.
18536 ///
18537 /// Use this method to disable this for cases where this is not wanted (e.g.
18538 /// if you want to align the editor text with some other text above or below)
18539 /// or if you want to add this padding to single-line editors.
18540 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18541 self.offset_content = offset_content;
18542 cx.notify();
18543 }
18544
18545 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18546 self.show_line_numbers = Some(show_line_numbers);
18547 cx.notify();
18548 }
18549
18550 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18551 self.disable_expand_excerpt_buttons = true;
18552 cx.notify();
18553 }
18554
18555 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18556 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18557 cx.notify();
18558 }
18559
18560 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18561 self.show_code_actions = Some(show_code_actions);
18562 cx.notify();
18563 }
18564
18565 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18566 self.show_runnables = Some(show_runnables);
18567 cx.notify();
18568 }
18569
18570 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18571 self.show_breakpoints = Some(show_breakpoints);
18572 cx.notify();
18573 }
18574
18575 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18576 if self.display_map.read(cx).masked != masked {
18577 self.display_map.update(cx, |map, _| map.masked = masked);
18578 }
18579 cx.notify()
18580 }
18581
18582 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18583 self.show_wrap_guides = Some(show_wrap_guides);
18584 cx.notify();
18585 }
18586
18587 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18588 self.show_indent_guides = Some(show_indent_guides);
18589 cx.notify();
18590 }
18591
18592 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18593 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18594 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18595 if let Some(dir) = file.abs_path(cx).parent() {
18596 return Some(dir.to_owned());
18597 }
18598 }
18599
18600 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18601 return Some(project_path.path.to_path_buf());
18602 }
18603 }
18604
18605 None
18606 }
18607
18608 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18609 self.active_excerpt(cx)?
18610 .1
18611 .read(cx)
18612 .file()
18613 .and_then(|f| f.as_local())
18614 }
18615
18616 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18617 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18618 let buffer = buffer.read(cx);
18619 if let Some(project_path) = buffer.project_path(cx) {
18620 let project = self.project.as_ref()?.read(cx);
18621 project.absolute_path(&project_path, cx)
18622 } else {
18623 buffer
18624 .file()
18625 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18626 }
18627 })
18628 }
18629
18630 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18631 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18632 let project_path = buffer.read(cx).project_path(cx)?;
18633 let project = self.project.as_ref()?.read(cx);
18634 let entry = project.entry_for_path(&project_path, cx)?;
18635 let path = entry.path.to_path_buf();
18636 Some(path)
18637 })
18638 }
18639
18640 pub fn reveal_in_finder(
18641 &mut self,
18642 _: &RevealInFileManager,
18643 _window: &mut Window,
18644 cx: &mut Context<Self>,
18645 ) {
18646 if let Some(target) = self.target_file(cx) {
18647 cx.reveal_path(&target.abs_path(cx));
18648 }
18649 }
18650
18651 pub fn copy_path(
18652 &mut self,
18653 _: &zed_actions::workspace::CopyPath,
18654 _window: &mut Window,
18655 cx: &mut Context<Self>,
18656 ) {
18657 if let Some(path) = self.target_file_abs_path(cx) {
18658 if let Some(path) = path.to_str() {
18659 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18660 }
18661 }
18662 }
18663
18664 pub fn copy_relative_path(
18665 &mut self,
18666 _: &zed_actions::workspace::CopyRelativePath,
18667 _window: &mut Window,
18668 cx: &mut Context<Self>,
18669 ) {
18670 if let Some(path) = self.target_file_path(cx) {
18671 if let Some(path) = path.to_str() {
18672 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18673 }
18674 }
18675 }
18676
18677 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18678 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18679 buffer.read(cx).project_path(cx)
18680 } else {
18681 None
18682 }
18683 }
18684
18685 // Returns true if the editor handled a go-to-line request
18686 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18687 maybe!({
18688 let breakpoint_store = self.breakpoint_store.as_ref()?;
18689
18690 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18691 else {
18692 self.clear_row_highlights::<ActiveDebugLine>();
18693 return None;
18694 };
18695
18696 let position = active_stack_frame.position;
18697 let buffer_id = position.buffer_id?;
18698 let snapshot = self
18699 .project
18700 .as_ref()?
18701 .read(cx)
18702 .buffer_for_id(buffer_id, cx)?
18703 .read(cx)
18704 .snapshot();
18705
18706 let mut handled = false;
18707 for (id, ExcerptRange { context, .. }) in
18708 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18709 {
18710 if context.start.cmp(&position, &snapshot).is_ge()
18711 || context.end.cmp(&position, &snapshot).is_lt()
18712 {
18713 continue;
18714 }
18715 let snapshot = self.buffer.read(cx).snapshot(cx);
18716 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18717
18718 handled = true;
18719 self.clear_row_highlights::<ActiveDebugLine>();
18720
18721 self.go_to_line::<ActiveDebugLine>(
18722 multibuffer_anchor,
18723 Some(cx.theme().colors().editor_debugger_active_line_background),
18724 window,
18725 cx,
18726 );
18727
18728 cx.notify();
18729 }
18730
18731 handled.then_some(())
18732 })
18733 .is_some()
18734 }
18735
18736 pub fn copy_file_name_without_extension(
18737 &mut self,
18738 _: &CopyFileNameWithoutExtension,
18739 _: &mut Window,
18740 cx: &mut Context<Self>,
18741 ) {
18742 if let Some(file) = self.target_file(cx) {
18743 if let Some(file_stem) = file.path().file_stem() {
18744 if let Some(name) = file_stem.to_str() {
18745 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18746 }
18747 }
18748 }
18749 }
18750
18751 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18752 if let Some(file) = self.target_file(cx) {
18753 if let Some(file_name) = file.path().file_name() {
18754 if let Some(name) = file_name.to_str() {
18755 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18756 }
18757 }
18758 }
18759 }
18760
18761 pub fn toggle_git_blame(
18762 &mut self,
18763 _: &::git::Blame,
18764 window: &mut Window,
18765 cx: &mut Context<Self>,
18766 ) {
18767 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18768
18769 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18770 self.start_git_blame(true, window, cx);
18771 }
18772
18773 cx.notify();
18774 }
18775
18776 pub fn toggle_git_blame_inline(
18777 &mut self,
18778 _: &ToggleGitBlameInline,
18779 window: &mut Window,
18780 cx: &mut Context<Self>,
18781 ) {
18782 self.toggle_git_blame_inline_internal(true, window, cx);
18783 cx.notify();
18784 }
18785
18786 pub fn open_git_blame_commit(
18787 &mut self,
18788 _: &OpenGitBlameCommit,
18789 window: &mut Window,
18790 cx: &mut Context<Self>,
18791 ) {
18792 self.open_git_blame_commit_internal(window, cx);
18793 }
18794
18795 fn open_git_blame_commit_internal(
18796 &mut self,
18797 window: &mut Window,
18798 cx: &mut Context<Self>,
18799 ) -> Option<()> {
18800 let blame = self.blame.as_ref()?;
18801 let snapshot = self.snapshot(window, cx);
18802 let cursor = self.selections.newest::<Point>(cx).head();
18803 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18804 let blame_entry = blame
18805 .update(cx, |blame, cx| {
18806 blame
18807 .blame_for_rows(
18808 &[RowInfo {
18809 buffer_id: Some(buffer.remote_id()),
18810 buffer_row: Some(point.row),
18811 ..Default::default()
18812 }],
18813 cx,
18814 )
18815 .next()
18816 })
18817 .flatten()?;
18818 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18819 let repo = blame.read(cx).repository(cx)?;
18820 let workspace = self.workspace()?.downgrade();
18821 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18822 None
18823 }
18824
18825 pub fn git_blame_inline_enabled(&self) -> bool {
18826 self.git_blame_inline_enabled
18827 }
18828
18829 pub fn toggle_selection_menu(
18830 &mut self,
18831 _: &ToggleSelectionMenu,
18832 _: &mut Window,
18833 cx: &mut Context<Self>,
18834 ) {
18835 self.show_selection_menu = self
18836 .show_selection_menu
18837 .map(|show_selections_menu| !show_selections_menu)
18838 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18839
18840 cx.notify();
18841 }
18842
18843 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18844 self.show_selection_menu
18845 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18846 }
18847
18848 fn start_git_blame(
18849 &mut self,
18850 user_triggered: bool,
18851 window: &mut Window,
18852 cx: &mut Context<Self>,
18853 ) {
18854 if let Some(project) = self.project.as_ref() {
18855 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18856 return;
18857 };
18858
18859 if buffer.read(cx).file().is_none() {
18860 return;
18861 }
18862
18863 let focused = self.focus_handle(cx).contains_focused(window, cx);
18864
18865 let project = project.clone();
18866 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18867 self.blame_subscription =
18868 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18869 self.blame = Some(blame);
18870 }
18871 }
18872
18873 fn toggle_git_blame_inline_internal(
18874 &mut self,
18875 user_triggered: bool,
18876 window: &mut Window,
18877 cx: &mut Context<Self>,
18878 ) {
18879 if self.git_blame_inline_enabled {
18880 self.git_blame_inline_enabled = false;
18881 self.show_git_blame_inline = false;
18882 self.show_git_blame_inline_delay_task.take();
18883 } else {
18884 self.git_blame_inline_enabled = true;
18885 self.start_git_blame_inline(user_triggered, window, cx);
18886 }
18887
18888 cx.notify();
18889 }
18890
18891 fn start_git_blame_inline(
18892 &mut self,
18893 user_triggered: bool,
18894 window: &mut Window,
18895 cx: &mut Context<Self>,
18896 ) {
18897 self.start_git_blame(user_triggered, window, cx);
18898
18899 if ProjectSettings::get_global(cx)
18900 .git
18901 .inline_blame_delay()
18902 .is_some()
18903 {
18904 self.start_inline_blame_timer(window, cx);
18905 } else {
18906 self.show_git_blame_inline = true
18907 }
18908 }
18909
18910 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18911 self.blame.as_ref()
18912 }
18913
18914 pub fn show_git_blame_gutter(&self) -> bool {
18915 self.show_git_blame_gutter
18916 }
18917
18918 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18919 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18920 }
18921
18922 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18923 self.show_git_blame_inline
18924 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18925 && !self.newest_selection_head_on_empty_line(cx)
18926 && self.has_blame_entries(cx)
18927 }
18928
18929 fn has_blame_entries(&self, cx: &App) -> bool {
18930 self.blame()
18931 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18932 }
18933
18934 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18935 let cursor_anchor = self.selections.newest_anchor().head();
18936
18937 let snapshot = self.buffer.read(cx).snapshot(cx);
18938 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18939
18940 snapshot.line_len(buffer_row) == 0
18941 }
18942
18943 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18944 let buffer_and_selection = maybe!({
18945 let selection = self.selections.newest::<Point>(cx);
18946 let selection_range = selection.range();
18947
18948 let multi_buffer = self.buffer().read(cx);
18949 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18950 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18951
18952 let (buffer, range, _) = if selection.reversed {
18953 buffer_ranges.first()
18954 } else {
18955 buffer_ranges.last()
18956 }?;
18957
18958 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18959 ..text::ToPoint::to_point(&range.end, &buffer).row;
18960 Some((
18961 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18962 selection,
18963 ))
18964 });
18965
18966 let Some((buffer, selection)) = buffer_and_selection else {
18967 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18968 };
18969
18970 let Some(project) = self.project.as_ref() else {
18971 return Task::ready(Err(anyhow!("editor does not have project")));
18972 };
18973
18974 project.update(cx, |project, cx| {
18975 project.get_permalink_to_line(&buffer, selection, cx)
18976 })
18977 }
18978
18979 pub fn copy_permalink_to_line(
18980 &mut self,
18981 _: &CopyPermalinkToLine,
18982 window: &mut Window,
18983 cx: &mut Context<Self>,
18984 ) {
18985 let permalink_task = self.get_permalink_to_line(cx);
18986 let workspace = self.workspace();
18987
18988 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18989 Ok(permalink) => {
18990 cx.update(|_, cx| {
18991 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18992 })
18993 .ok();
18994 }
18995 Err(err) => {
18996 let message = format!("Failed to copy permalink: {err}");
18997
18998 anyhow::Result::<()>::Err(err).log_err();
18999
19000 if let Some(workspace) = workspace {
19001 workspace
19002 .update_in(cx, |workspace, _, cx| {
19003 struct CopyPermalinkToLine;
19004
19005 workspace.show_toast(
19006 Toast::new(
19007 NotificationId::unique::<CopyPermalinkToLine>(),
19008 message,
19009 ),
19010 cx,
19011 )
19012 })
19013 .ok();
19014 }
19015 }
19016 })
19017 .detach();
19018 }
19019
19020 pub fn copy_file_location(
19021 &mut self,
19022 _: &CopyFileLocation,
19023 _: &mut Window,
19024 cx: &mut Context<Self>,
19025 ) {
19026 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19027 if let Some(file) = self.target_file(cx) {
19028 if let Some(path) = file.path().to_str() {
19029 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19030 }
19031 }
19032 }
19033
19034 pub fn open_permalink_to_line(
19035 &mut self,
19036 _: &OpenPermalinkToLine,
19037 window: &mut Window,
19038 cx: &mut Context<Self>,
19039 ) {
19040 let permalink_task = self.get_permalink_to_line(cx);
19041 let workspace = self.workspace();
19042
19043 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19044 Ok(permalink) => {
19045 cx.update(|_, cx| {
19046 cx.open_url(permalink.as_ref());
19047 })
19048 .ok();
19049 }
19050 Err(err) => {
19051 let message = format!("Failed to open permalink: {err}");
19052
19053 anyhow::Result::<()>::Err(err).log_err();
19054
19055 if let Some(workspace) = workspace {
19056 workspace
19057 .update(cx, |workspace, cx| {
19058 struct OpenPermalinkToLine;
19059
19060 workspace.show_toast(
19061 Toast::new(
19062 NotificationId::unique::<OpenPermalinkToLine>(),
19063 message,
19064 ),
19065 cx,
19066 )
19067 })
19068 .ok();
19069 }
19070 }
19071 })
19072 .detach();
19073 }
19074
19075 pub fn insert_uuid_v4(
19076 &mut self,
19077 _: &InsertUuidV4,
19078 window: &mut Window,
19079 cx: &mut Context<Self>,
19080 ) {
19081 self.insert_uuid(UuidVersion::V4, window, cx);
19082 }
19083
19084 pub fn insert_uuid_v7(
19085 &mut self,
19086 _: &InsertUuidV7,
19087 window: &mut Window,
19088 cx: &mut Context<Self>,
19089 ) {
19090 self.insert_uuid(UuidVersion::V7, window, cx);
19091 }
19092
19093 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19094 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19095 self.transact(window, cx, |this, window, cx| {
19096 let edits = this
19097 .selections
19098 .all::<Point>(cx)
19099 .into_iter()
19100 .map(|selection| {
19101 let uuid = match version {
19102 UuidVersion::V4 => uuid::Uuid::new_v4(),
19103 UuidVersion::V7 => uuid::Uuid::now_v7(),
19104 };
19105
19106 (selection.range(), uuid.to_string())
19107 });
19108 this.edit(edits, cx);
19109 this.refresh_edit_prediction(true, false, window, cx);
19110 });
19111 }
19112
19113 pub fn open_selections_in_multibuffer(
19114 &mut self,
19115 _: &OpenSelectionsInMultibuffer,
19116 window: &mut Window,
19117 cx: &mut Context<Self>,
19118 ) {
19119 let multibuffer = self.buffer.read(cx);
19120
19121 let Some(buffer) = multibuffer.as_singleton() else {
19122 return;
19123 };
19124
19125 let Some(workspace) = self.workspace() else {
19126 return;
19127 };
19128
19129 let title = multibuffer.title(cx).to_string();
19130
19131 let locations = self
19132 .selections
19133 .all_anchors(cx)
19134 .into_iter()
19135 .map(|selection| Location {
19136 buffer: buffer.clone(),
19137 range: selection.start.text_anchor..selection.end.text_anchor,
19138 })
19139 .collect::<Vec<_>>();
19140
19141 cx.spawn_in(window, async move |_, cx| {
19142 workspace.update_in(cx, |workspace, window, cx| {
19143 Self::open_locations_in_multibuffer(
19144 workspace,
19145 locations,
19146 format!("Selections for '{title}'"),
19147 false,
19148 MultibufferSelectionMode::All,
19149 window,
19150 cx,
19151 );
19152 })
19153 })
19154 .detach();
19155 }
19156
19157 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19158 /// last highlight added will be used.
19159 ///
19160 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19161 pub fn highlight_rows<T: 'static>(
19162 &mut self,
19163 range: Range<Anchor>,
19164 color: Hsla,
19165 options: RowHighlightOptions,
19166 cx: &mut Context<Self>,
19167 ) {
19168 let snapshot = self.buffer().read(cx).snapshot(cx);
19169 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19170 let ix = row_highlights.binary_search_by(|highlight| {
19171 Ordering::Equal
19172 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19173 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19174 });
19175
19176 if let Err(mut ix) = ix {
19177 let index = post_inc(&mut self.highlight_order);
19178
19179 // If this range intersects with the preceding highlight, then merge it with
19180 // the preceding highlight. Otherwise insert a new highlight.
19181 let mut merged = false;
19182 if ix > 0 {
19183 let prev_highlight = &mut row_highlights[ix - 1];
19184 if prev_highlight
19185 .range
19186 .end
19187 .cmp(&range.start, &snapshot)
19188 .is_ge()
19189 {
19190 ix -= 1;
19191 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19192 prev_highlight.range.end = range.end;
19193 }
19194 merged = true;
19195 prev_highlight.index = index;
19196 prev_highlight.color = color;
19197 prev_highlight.options = options;
19198 }
19199 }
19200
19201 if !merged {
19202 row_highlights.insert(
19203 ix,
19204 RowHighlight {
19205 range: range.clone(),
19206 index,
19207 color,
19208 options,
19209 type_id: TypeId::of::<T>(),
19210 },
19211 );
19212 }
19213
19214 // If any of the following highlights intersect with this one, merge them.
19215 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19216 let highlight = &row_highlights[ix];
19217 if next_highlight
19218 .range
19219 .start
19220 .cmp(&highlight.range.end, &snapshot)
19221 .is_le()
19222 {
19223 if next_highlight
19224 .range
19225 .end
19226 .cmp(&highlight.range.end, &snapshot)
19227 .is_gt()
19228 {
19229 row_highlights[ix].range.end = next_highlight.range.end;
19230 }
19231 row_highlights.remove(ix + 1);
19232 } else {
19233 break;
19234 }
19235 }
19236 }
19237 }
19238
19239 /// Remove any highlighted row ranges of the given type that intersect the
19240 /// given ranges.
19241 pub fn remove_highlighted_rows<T: 'static>(
19242 &mut self,
19243 ranges_to_remove: Vec<Range<Anchor>>,
19244 cx: &mut Context<Self>,
19245 ) {
19246 let snapshot = self.buffer().read(cx).snapshot(cx);
19247 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19248 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19249 row_highlights.retain(|highlight| {
19250 while let Some(range_to_remove) = ranges_to_remove.peek() {
19251 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19252 Ordering::Less | Ordering::Equal => {
19253 ranges_to_remove.next();
19254 }
19255 Ordering::Greater => {
19256 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19257 Ordering::Less | Ordering::Equal => {
19258 return false;
19259 }
19260 Ordering::Greater => break,
19261 }
19262 }
19263 }
19264 }
19265
19266 true
19267 })
19268 }
19269
19270 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19271 pub fn clear_row_highlights<T: 'static>(&mut self) {
19272 self.highlighted_rows.remove(&TypeId::of::<T>());
19273 }
19274
19275 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19276 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19277 self.highlighted_rows
19278 .get(&TypeId::of::<T>())
19279 .map_or(&[] as &[_], |vec| vec.as_slice())
19280 .iter()
19281 .map(|highlight| (highlight.range.clone(), highlight.color))
19282 }
19283
19284 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19285 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19286 /// Allows to ignore certain kinds of highlights.
19287 pub fn highlighted_display_rows(
19288 &self,
19289 window: &mut Window,
19290 cx: &mut App,
19291 ) -> BTreeMap<DisplayRow, LineHighlight> {
19292 let snapshot = self.snapshot(window, cx);
19293 let mut used_highlight_orders = HashMap::default();
19294 self.highlighted_rows
19295 .iter()
19296 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19297 .fold(
19298 BTreeMap::<DisplayRow, LineHighlight>::new(),
19299 |mut unique_rows, highlight| {
19300 let start = highlight.range.start.to_display_point(&snapshot);
19301 let end = highlight.range.end.to_display_point(&snapshot);
19302 let start_row = start.row().0;
19303 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19304 && end.column() == 0
19305 {
19306 end.row().0.saturating_sub(1)
19307 } else {
19308 end.row().0
19309 };
19310 for row in start_row..=end_row {
19311 let used_index =
19312 used_highlight_orders.entry(row).or_insert(highlight.index);
19313 if highlight.index >= *used_index {
19314 *used_index = highlight.index;
19315 unique_rows.insert(
19316 DisplayRow(row),
19317 LineHighlight {
19318 include_gutter: highlight.options.include_gutter,
19319 border: None,
19320 background: highlight.color.into(),
19321 type_id: Some(highlight.type_id),
19322 },
19323 );
19324 }
19325 }
19326 unique_rows
19327 },
19328 )
19329 }
19330
19331 pub fn highlighted_display_row_for_autoscroll(
19332 &self,
19333 snapshot: &DisplaySnapshot,
19334 ) -> Option<DisplayRow> {
19335 self.highlighted_rows
19336 .values()
19337 .flat_map(|highlighted_rows| highlighted_rows.iter())
19338 .filter_map(|highlight| {
19339 if highlight.options.autoscroll {
19340 Some(highlight.range.start.to_display_point(snapshot).row())
19341 } else {
19342 None
19343 }
19344 })
19345 .min()
19346 }
19347
19348 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19349 self.highlight_background::<SearchWithinRange>(
19350 ranges,
19351 |colors| colors.colors().editor_document_highlight_read_background,
19352 cx,
19353 )
19354 }
19355
19356 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19357 self.breadcrumb_header = Some(new_header);
19358 }
19359
19360 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19361 self.clear_background_highlights::<SearchWithinRange>(cx);
19362 }
19363
19364 pub fn highlight_background<T: 'static>(
19365 &mut self,
19366 ranges: &[Range<Anchor>],
19367 color_fetcher: fn(&Theme) -> Hsla,
19368 cx: &mut Context<Self>,
19369 ) {
19370 self.background_highlights.insert(
19371 HighlightKey::Type(TypeId::of::<T>()),
19372 (color_fetcher, Arc::from(ranges)),
19373 );
19374 self.scrollbar_marker_state.dirty = true;
19375 cx.notify();
19376 }
19377
19378 pub fn highlight_background_key<T: 'static>(
19379 &mut self,
19380 key: usize,
19381 ranges: &[Range<Anchor>],
19382 color_fetcher: fn(&Theme) -> Hsla,
19383 cx: &mut Context<Self>,
19384 ) {
19385 self.background_highlights.insert(
19386 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19387 (color_fetcher, Arc::from(ranges)),
19388 );
19389 self.scrollbar_marker_state.dirty = true;
19390 cx.notify();
19391 }
19392
19393 pub fn clear_background_highlights<T: 'static>(
19394 &mut self,
19395 cx: &mut Context<Self>,
19396 ) -> Option<BackgroundHighlight> {
19397 let text_highlights = self
19398 .background_highlights
19399 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19400 if !text_highlights.1.is_empty() {
19401 self.scrollbar_marker_state.dirty = true;
19402 cx.notify();
19403 }
19404 Some(text_highlights)
19405 }
19406
19407 pub fn highlight_gutter<T: 'static>(
19408 &mut self,
19409 ranges: impl Into<Vec<Range<Anchor>>>,
19410 color_fetcher: fn(&App) -> Hsla,
19411 cx: &mut Context<Self>,
19412 ) {
19413 self.gutter_highlights
19414 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19415 cx.notify();
19416 }
19417
19418 pub fn clear_gutter_highlights<T: 'static>(
19419 &mut self,
19420 cx: &mut Context<Self>,
19421 ) -> Option<GutterHighlight> {
19422 cx.notify();
19423 self.gutter_highlights.remove(&TypeId::of::<T>())
19424 }
19425
19426 pub fn insert_gutter_highlight<T: 'static>(
19427 &mut self,
19428 range: Range<Anchor>,
19429 color_fetcher: fn(&App) -> Hsla,
19430 cx: &mut Context<Self>,
19431 ) {
19432 let snapshot = self.buffer().read(cx).snapshot(cx);
19433 let mut highlights = self
19434 .gutter_highlights
19435 .remove(&TypeId::of::<T>())
19436 .map(|(_, highlights)| highlights)
19437 .unwrap_or_default();
19438 let ix = highlights.binary_search_by(|highlight| {
19439 Ordering::Equal
19440 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19441 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19442 });
19443 if let Err(ix) = ix {
19444 highlights.insert(ix, range);
19445 }
19446 self.gutter_highlights
19447 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19448 }
19449
19450 pub fn remove_gutter_highlights<T: 'static>(
19451 &mut self,
19452 ranges_to_remove: Vec<Range<Anchor>>,
19453 cx: &mut Context<Self>,
19454 ) {
19455 let snapshot = self.buffer().read(cx).snapshot(cx);
19456 let Some((color_fetcher, mut gutter_highlights)) =
19457 self.gutter_highlights.remove(&TypeId::of::<T>())
19458 else {
19459 return;
19460 };
19461 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19462 gutter_highlights.retain(|highlight| {
19463 while let Some(range_to_remove) = ranges_to_remove.peek() {
19464 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19465 Ordering::Less | Ordering::Equal => {
19466 ranges_to_remove.next();
19467 }
19468 Ordering::Greater => {
19469 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19470 Ordering::Less | Ordering::Equal => {
19471 return false;
19472 }
19473 Ordering::Greater => break,
19474 }
19475 }
19476 }
19477 }
19478
19479 true
19480 });
19481 self.gutter_highlights
19482 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19483 }
19484
19485 #[cfg(feature = "test-support")]
19486 pub fn all_text_highlights(
19487 &self,
19488 window: &mut Window,
19489 cx: &mut Context<Self>,
19490 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19491 let snapshot = self.snapshot(window, cx);
19492 self.display_map.update(cx, |display_map, _| {
19493 display_map
19494 .all_text_highlights()
19495 .map(|highlight| {
19496 let (style, ranges) = highlight.as_ref();
19497 (
19498 *style,
19499 ranges
19500 .iter()
19501 .map(|range| range.clone().to_display_points(&snapshot))
19502 .collect(),
19503 )
19504 })
19505 .collect()
19506 })
19507 }
19508
19509 #[cfg(feature = "test-support")]
19510 pub fn all_text_background_highlights(
19511 &self,
19512 window: &mut Window,
19513 cx: &mut Context<Self>,
19514 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19515 let snapshot = self.snapshot(window, cx);
19516 let buffer = &snapshot.buffer_snapshot;
19517 let start = buffer.anchor_before(0);
19518 let end = buffer.anchor_after(buffer.len());
19519 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19520 }
19521
19522 #[cfg(feature = "test-support")]
19523 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19524 let snapshot = self.buffer().read(cx).snapshot(cx);
19525
19526 let highlights = self
19527 .background_highlights
19528 .get(&HighlightKey::Type(TypeId::of::<
19529 items::BufferSearchHighlights,
19530 >()));
19531
19532 if let Some((_color, ranges)) = highlights {
19533 ranges
19534 .iter()
19535 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19536 .collect_vec()
19537 } else {
19538 vec![]
19539 }
19540 }
19541
19542 fn document_highlights_for_position<'a>(
19543 &'a self,
19544 position: Anchor,
19545 buffer: &'a MultiBufferSnapshot,
19546 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19547 let read_highlights = self
19548 .background_highlights
19549 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19550 .map(|h| &h.1);
19551 let write_highlights = self
19552 .background_highlights
19553 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19554 .map(|h| &h.1);
19555 let left_position = position.bias_left(buffer);
19556 let right_position = position.bias_right(buffer);
19557 read_highlights
19558 .into_iter()
19559 .chain(write_highlights)
19560 .flat_map(move |ranges| {
19561 let start_ix = match ranges.binary_search_by(|probe| {
19562 let cmp = probe.end.cmp(&left_position, buffer);
19563 if cmp.is_ge() {
19564 Ordering::Greater
19565 } else {
19566 Ordering::Less
19567 }
19568 }) {
19569 Ok(i) | Err(i) => i,
19570 };
19571
19572 ranges[start_ix..]
19573 .iter()
19574 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19575 })
19576 }
19577
19578 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19579 self.background_highlights
19580 .get(&HighlightKey::Type(TypeId::of::<T>()))
19581 .map_or(false, |(_, highlights)| !highlights.is_empty())
19582 }
19583
19584 pub fn background_highlights_in_range(
19585 &self,
19586 search_range: Range<Anchor>,
19587 display_snapshot: &DisplaySnapshot,
19588 theme: &Theme,
19589 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19590 let mut results = Vec::new();
19591 for (color_fetcher, ranges) in self.background_highlights.values() {
19592 let color = color_fetcher(theme);
19593 let start_ix = match ranges.binary_search_by(|probe| {
19594 let cmp = probe
19595 .end
19596 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19597 if cmp.is_gt() {
19598 Ordering::Greater
19599 } else {
19600 Ordering::Less
19601 }
19602 }) {
19603 Ok(i) | Err(i) => i,
19604 };
19605 for range in &ranges[start_ix..] {
19606 if range
19607 .start
19608 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19609 .is_ge()
19610 {
19611 break;
19612 }
19613
19614 let start = range.start.to_display_point(display_snapshot);
19615 let end = range.end.to_display_point(display_snapshot);
19616 results.push((start..end, color))
19617 }
19618 }
19619 results
19620 }
19621
19622 pub fn background_highlight_row_ranges<T: 'static>(
19623 &self,
19624 search_range: Range<Anchor>,
19625 display_snapshot: &DisplaySnapshot,
19626 count: usize,
19627 ) -> Vec<RangeInclusive<DisplayPoint>> {
19628 let mut results = Vec::new();
19629 let Some((_, ranges)) = self
19630 .background_highlights
19631 .get(&HighlightKey::Type(TypeId::of::<T>()))
19632 else {
19633 return vec![];
19634 };
19635
19636 let start_ix = match ranges.binary_search_by(|probe| {
19637 let cmp = probe
19638 .end
19639 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19640 if cmp.is_gt() {
19641 Ordering::Greater
19642 } else {
19643 Ordering::Less
19644 }
19645 }) {
19646 Ok(i) | Err(i) => i,
19647 };
19648 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19649 if let (Some(start_display), Some(end_display)) = (start, end) {
19650 results.push(
19651 start_display.to_display_point(display_snapshot)
19652 ..=end_display.to_display_point(display_snapshot),
19653 );
19654 }
19655 };
19656 let mut start_row: Option<Point> = None;
19657 let mut end_row: Option<Point> = None;
19658 if ranges.len() > count {
19659 return Vec::new();
19660 }
19661 for range in &ranges[start_ix..] {
19662 if range
19663 .start
19664 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19665 .is_ge()
19666 {
19667 break;
19668 }
19669 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19670 if let Some(current_row) = &end_row {
19671 if end.row == current_row.row {
19672 continue;
19673 }
19674 }
19675 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19676 if start_row.is_none() {
19677 assert_eq!(end_row, None);
19678 start_row = Some(start);
19679 end_row = Some(end);
19680 continue;
19681 }
19682 if let Some(current_end) = end_row.as_mut() {
19683 if start.row > current_end.row + 1 {
19684 push_region(start_row, end_row);
19685 start_row = Some(start);
19686 end_row = Some(end);
19687 } else {
19688 // Merge two hunks.
19689 *current_end = end;
19690 }
19691 } else {
19692 unreachable!();
19693 }
19694 }
19695 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19696 push_region(start_row, end_row);
19697 results
19698 }
19699
19700 pub fn gutter_highlights_in_range(
19701 &self,
19702 search_range: Range<Anchor>,
19703 display_snapshot: &DisplaySnapshot,
19704 cx: &App,
19705 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19706 let mut results = Vec::new();
19707 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19708 let color = color_fetcher(cx);
19709 let start_ix = match ranges.binary_search_by(|probe| {
19710 let cmp = probe
19711 .end
19712 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19713 if cmp.is_gt() {
19714 Ordering::Greater
19715 } else {
19716 Ordering::Less
19717 }
19718 }) {
19719 Ok(i) | Err(i) => i,
19720 };
19721 for range in &ranges[start_ix..] {
19722 if range
19723 .start
19724 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19725 .is_ge()
19726 {
19727 break;
19728 }
19729
19730 let start = range.start.to_display_point(display_snapshot);
19731 let end = range.end.to_display_point(display_snapshot);
19732 results.push((start..end, color))
19733 }
19734 }
19735 results
19736 }
19737
19738 /// Get the text ranges corresponding to the redaction query
19739 pub fn redacted_ranges(
19740 &self,
19741 search_range: Range<Anchor>,
19742 display_snapshot: &DisplaySnapshot,
19743 cx: &App,
19744 ) -> Vec<Range<DisplayPoint>> {
19745 display_snapshot
19746 .buffer_snapshot
19747 .redacted_ranges(search_range, |file| {
19748 if let Some(file) = file {
19749 file.is_private()
19750 && EditorSettings::get(
19751 Some(SettingsLocation {
19752 worktree_id: file.worktree_id(cx),
19753 path: file.path().as_ref(),
19754 }),
19755 cx,
19756 )
19757 .redact_private_values
19758 } else {
19759 false
19760 }
19761 })
19762 .map(|range| {
19763 range.start.to_display_point(display_snapshot)
19764 ..range.end.to_display_point(display_snapshot)
19765 })
19766 .collect()
19767 }
19768
19769 pub fn highlight_text_key<T: 'static>(
19770 &mut self,
19771 key: usize,
19772 ranges: Vec<Range<Anchor>>,
19773 style: HighlightStyle,
19774 cx: &mut Context<Self>,
19775 ) {
19776 self.display_map.update(cx, |map, _| {
19777 map.highlight_text(
19778 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19779 ranges,
19780 style,
19781 );
19782 });
19783 cx.notify();
19784 }
19785
19786 pub fn highlight_text<T: 'static>(
19787 &mut self,
19788 ranges: Vec<Range<Anchor>>,
19789 style: HighlightStyle,
19790 cx: &mut Context<Self>,
19791 ) {
19792 self.display_map.update(cx, |map, _| {
19793 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19794 });
19795 cx.notify();
19796 }
19797
19798 pub(crate) fn highlight_inlays<T: 'static>(
19799 &mut self,
19800 highlights: Vec<InlayHighlight>,
19801 style: HighlightStyle,
19802 cx: &mut Context<Self>,
19803 ) {
19804 self.display_map.update(cx, |map, _| {
19805 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19806 });
19807 cx.notify();
19808 }
19809
19810 pub fn text_highlights<'a, T: 'static>(
19811 &'a self,
19812 cx: &'a App,
19813 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19814 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19815 }
19816
19817 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19818 let cleared = self
19819 .display_map
19820 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19821 if cleared {
19822 cx.notify();
19823 }
19824 }
19825
19826 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19827 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19828 && self.focus_handle.is_focused(window)
19829 }
19830
19831 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19832 self.show_cursor_when_unfocused = is_enabled;
19833 cx.notify();
19834 }
19835
19836 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19837 cx.notify();
19838 }
19839
19840 fn on_debug_session_event(
19841 &mut self,
19842 _session: Entity<Session>,
19843 event: &SessionEvent,
19844 cx: &mut Context<Self>,
19845 ) {
19846 match event {
19847 SessionEvent::InvalidateInlineValue => {
19848 self.refresh_inline_values(cx);
19849 }
19850 _ => {}
19851 }
19852 }
19853
19854 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19855 let Some(project) = self.project.clone() else {
19856 return;
19857 };
19858
19859 if !self.inline_value_cache.enabled {
19860 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19861 self.splice_inlays(&inlays, Vec::new(), cx);
19862 return;
19863 }
19864
19865 let current_execution_position = self
19866 .highlighted_rows
19867 .get(&TypeId::of::<ActiveDebugLine>())
19868 .and_then(|lines| lines.last().map(|line| line.range.end));
19869
19870 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19871 let inline_values = editor
19872 .update(cx, |editor, cx| {
19873 let Some(current_execution_position) = current_execution_position else {
19874 return Some(Task::ready(Ok(Vec::new())));
19875 };
19876
19877 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19878 let snapshot = buffer.snapshot(cx);
19879
19880 let excerpt = snapshot.excerpt_containing(
19881 current_execution_position..current_execution_position,
19882 )?;
19883
19884 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19885 })?;
19886
19887 let range =
19888 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19889
19890 project.inline_values(buffer, range, cx)
19891 })
19892 .ok()
19893 .flatten()?
19894 .await
19895 .context("refreshing debugger inlays")
19896 .log_err()?;
19897
19898 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19899
19900 for (buffer_id, inline_value) in inline_values
19901 .into_iter()
19902 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19903 {
19904 buffer_inline_values
19905 .entry(buffer_id)
19906 .or_default()
19907 .push(inline_value);
19908 }
19909
19910 editor
19911 .update(cx, |editor, cx| {
19912 let snapshot = editor.buffer.read(cx).snapshot(cx);
19913 let mut new_inlays = Vec::default();
19914
19915 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19916 let buffer_id = buffer_snapshot.remote_id();
19917 buffer_inline_values
19918 .get(&buffer_id)
19919 .into_iter()
19920 .flatten()
19921 .for_each(|hint| {
19922 let inlay = Inlay::debugger(
19923 post_inc(&mut editor.next_inlay_id),
19924 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19925 hint.text(),
19926 );
19927 if !inlay.text.chars().contains(&'\n') {
19928 new_inlays.push(inlay);
19929 }
19930 });
19931 }
19932
19933 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19934 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19935
19936 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19937 })
19938 .ok()?;
19939 Some(())
19940 });
19941 }
19942
19943 fn on_buffer_event(
19944 &mut self,
19945 multibuffer: &Entity<MultiBuffer>,
19946 event: &multi_buffer::Event,
19947 window: &mut Window,
19948 cx: &mut Context<Self>,
19949 ) {
19950 match event {
19951 multi_buffer::Event::Edited {
19952 singleton_buffer_edited,
19953 edited_buffer,
19954 } => {
19955 self.scrollbar_marker_state.dirty = true;
19956 self.active_indent_guides_state.dirty = true;
19957 self.refresh_active_diagnostics(cx);
19958 self.refresh_code_actions(window, cx);
19959 self.refresh_selected_text_highlights(true, window, cx);
19960 self.refresh_single_line_folds(window, cx);
19961 refresh_matching_bracket_highlights(self, window, cx);
19962 if self.has_active_edit_prediction() {
19963 self.update_visible_edit_prediction(window, cx);
19964 }
19965 if let Some(project) = self.project.as_ref() {
19966 if let Some(edited_buffer) = edited_buffer {
19967 project.update(cx, |project, cx| {
19968 self.registered_buffers
19969 .entry(edited_buffer.read(cx).remote_id())
19970 .or_insert_with(|| {
19971 project
19972 .register_buffer_with_language_servers(&edited_buffer, cx)
19973 });
19974 });
19975 }
19976 }
19977 cx.emit(EditorEvent::BufferEdited);
19978 cx.emit(SearchEvent::MatchesInvalidated);
19979
19980 if let Some(buffer) = edited_buffer {
19981 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19982 }
19983
19984 if *singleton_buffer_edited {
19985 if let Some(buffer) = edited_buffer {
19986 if buffer.read(cx).file().is_none() {
19987 cx.emit(EditorEvent::TitleChanged);
19988 }
19989 }
19990 if let Some(project) = &self.project {
19991 #[allow(clippy::mutable_key_type)]
19992 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19993 multibuffer
19994 .all_buffers()
19995 .into_iter()
19996 .filter_map(|buffer| {
19997 buffer.update(cx, |buffer, cx| {
19998 let language = buffer.language()?;
19999 let should_discard = project.update(cx, |project, cx| {
20000 project.is_local()
20001 && !project.has_language_servers_for(buffer, cx)
20002 });
20003 should_discard.not().then_some(language.clone())
20004 })
20005 })
20006 .collect::<HashSet<_>>()
20007 });
20008 if !languages_affected.is_empty() {
20009 self.refresh_inlay_hints(
20010 InlayHintRefreshReason::BufferEdited(languages_affected),
20011 cx,
20012 );
20013 }
20014 }
20015 }
20016
20017 let Some(project) = &self.project else { return };
20018 let (telemetry, is_via_ssh) = {
20019 let project = project.read(cx);
20020 let telemetry = project.client().telemetry().clone();
20021 let is_via_ssh = project.is_via_ssh();
20022 (telemetry, is_via_ssh)
20023 };
20024 refresh_linked_ranges(self, window, cx);
20025 telemetry.log_edit_event("editor", is_via_ssh);
20026 }
20027 multi_buffer::Event::ExcerptsAdded {
20028 buffer,
20029 predecessor,
20030 excerpts,
20031 } => {
20032 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20033 let buffer_id = buffer.read(cx).remote_id();
20034 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
20035 if let Some(project) = &self.project {
20036 update_uncommitted_diff_for_buffer(
20037 cx.entity(),
20038 project,
20039 [buffer.clone()],
20040 self.buffer.clone(),
20041 cx,
20042 )
20043 .detach();
20044 }
20045 }
20046 self.update_lsp_data(false, Some(buffer_id), window, cx);
20047 cx.emit(EditorEvent::ExcerptsAdded {
20048 buffer: buffer.clone(),
20049 predecessor: *predecessor,
20050 excerpts: excerpts.clone(),
20051 });
20052 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20053 }
20054 multi_buffer::Event::ExcerptsRemoved {
20055 ids,
20056 removed_buffer_ids,
20057 } => {
20058 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20059 let buffer = self.buffer.read(cx);
20060 self.registered_buffers
20061 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20062 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20063 cx.emit(EditorEvent::ExcerptsRemoved {
20064 ids: ids.clone(),
20065 removed_buffer_ids: removed_buffer_ids.clone(),
20066 });
20067 }
20068 multi_buffer::Event::ExcerptsEdited {
20069 excerpt_ids,
20070 buffer_ids,
20071 } => {
20072 self.display_map.update(cx, |map, cx| {
20073 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20074 });
20075 cx.emit(EditorEvent::ExcerptsEdited {
20076 ids: excerpt_ids.clone(),
20077 });
20078 }
20079 multi_buffer::Event::ExcerptsExpanded { ids } => {
20080 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20081 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20082 }
20083 multi_buffer::Event::Reparsed(buffer_id) => {
20084 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20085 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20086
20087 cx.emit(EditorEvent::Reparsed(*buffer_id));
20088 }
20089 multi_buffer::Event::DiffHunksToggled => {
20090 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20091 }
20092 multi_buffer::Event::LanguageChanged(buffer_id) => {
20093 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20094 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20095 cx.emit(EditorEvent::Reparsed(*buffer_id));
20096 cx.notify();
20097 }
20098 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20099 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20100 multi_buffer::Event::FileHandleChanged
20101 | multi_buffer::Event::Reloaded
20102 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20103 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20104 multi_buffer::Event::DiagnosticsUpdated => {
20105 self.update_diagnostics_state(window, cx);
20106 }
20107 _ => {}
20108 };
20109 }
20110
20111 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20112 if !self.diagnostics_enabled() {
20113 return;
20114 }
20115 self.refresh_active_diagnostics(cx);
20116 self.refresh_inline_diagnostics(true, window, cx);
20117 self.scrollbar_marker_state.dirty = true;
20118 cx.notify();
20119 }
20120
20121 pub fn start_temporary_diff_override(&mut self) {
20122 self.load_diff_task.take();
20123 self.temporary_diff_override = true;
20124 }
20125
20126 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20127 self.temporary_diff_override = false;
20128 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20129 self.buffer.update(cx, |buffer, cx| {
20130 buffer.set_all_diff_hunks_collapsed(cx);
20131 });
20132
20133 if let Some(project) = self.project.clone() {
20134 self.load_diff_task = Some(
20135 update_uncommitted_diff_for_buffer(
20136 cx.entity(),
20137 &project,
20138 self.buffer.read(cx).all_buffers(),
20139 self.buffer.clone(),
20140 cx,
20141 )
20142 .shared(),
20143 );
20144 }
20145 }
20146
20147 fn on_display_map_changed(
20148 &mut self,
20149 _: Entity<DisplayMap>,
20150 _: &mut Window,
20151 cx: &mut Context<Self>,
20152 ) {
20153 cx.notify();
20154 }
20155
20156 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20157 if self.diagnostics_enabled() {
20158 let new_severity = EditorSettings::get_global(cx)
20159 .diagnostics_max_severity
20160 .unwrap_or(DiagnosticSeverity::Hint);
20161 self.set_max_diagnostics_severity(new_severity, cx);
20162 }
20163 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20164 self.update_edit_prediction_settings(cx);
20165 self.refresh_edit_prediction(true, false, window, cx);
20166 self.refresh_inline_values(cx);
20167 self.refresh_inlay_hints(
20168 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20169 self.selections.newest_anchor().head(),
20170 &self.buffer.read(cx).snapshot(cx),
20171 cx,
20172 )),
20173 cx,
20174 );
20175
20176 let old_cursor_shape = self.cursor_shape;
20177
20178 {
20179 let editor_settings = EditorSettings::get_global(cx);
20180 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20181 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20182 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20183 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20184 }
20185
20186 if old_cursor_shape != self.cursor_shape {
20187 cx.emit(EditorEvent::CursorShapeChanged);
20188 }
20189
20190 let project_settings = ProjectSettings::get_global(cx);
20191 self.serialize_dirty_buffers =
20192 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20193
20194 if self.mode.is_full() {
20195 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20196 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20197 if self.show_inline_diagnostics != show_inline_diagnostics {
20198 self.show_inline_diagnostics = show_inline_diagnostics;
20199 self.refresh_inline_diagnostics(false, window, cx);
20200 }
20201
20202 if self.git_blame_inline_enabled != inline_blame_enabled {
20203 self.toggle_git_blame_inline_internal(false, window, cx);
20204 }
20205
20206 let minimap_settings = EditorSettings::get_global(cx).minimap;
20207 if self.minimap_visibility != MinimapVisibility::Disabled {
20208 if self.minimap_visibility.settings_visibility()
20209 != minimap_settings.minimap_enabled()
20210 {
20211 self.set_minimap_visibility(
20212 MinimapVisibility::for_mode(self.mode(), cx),
20213 window,
20214 cx,
20215 );
20216 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20217 minimap_entity.update(cx, |minimap_editor, cx| {
20218 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20219 })
20220 }
20221 }
20222 }
20223
20224 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20225 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20226 }) {
20227 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20228 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20229 }
20230 self.refresh_colors(false, None, window, cx);
20231 }
20232
20233 cx.notify();
20234 }
20235
20236 pub fn set_searchable(&mut self, searchable: bool) {
20237 self.searchable = searchable;
20238 }
20239
20240 pub fn searchable(&self) -> bool {
20241 self.searchable
20242 }
20243
20244 fn open_proposed_changes_editor(
20245 &mut self,
20246 _: &OpenProposedChangesEditor,
20247 window: &mut Window,
20248 cx: &mut Context<Self>,
20249 ) {
20250 let Some(workspace) = self.workspace() else {
20251 cx.propagate();
20252 return;
20253 };
20254
20255 let selections = self.selections.all::<usize>(cx);
20256 let multi_buffer = self.buffer.read(cx);
20257 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20258 let mut new_selections_by_buffer = HashMap::default();
20259 for selection in selections {
20260 for (buffer, range, _) in
20261 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20262 {
20263 let mut range = range.to_point(buffer);
20264 range.start.column = 0;
20265 range.end.column = buffer.line_len(range.end.row);
20266 new_selections_by_buffer
20267 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20268 .or_insert(Vec::new())
20269 .push(range)
20270 }
20271 }
20272
20273 let proposed_changes_buffers = new_selections_by_buffer
20274 .into_iter()
20275 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20276 .collect::<Vec<_>>();
20277 let proposed_changes_editor = cx.new(|cx| {
20278 ProposedChangesEditor::new(
20279 "Proposed changes",
20280 proposed_changes_buffers,
20281 self.project.clone(),
20282 window,
20283 cx,
20284 )
20285 });
20286
20287 window.defer(cx, move |window, cx| {
20288 workspace.update(cx, |workspace, cx| {
20289 workspace.active_pane().update(cx, |pane, cx| {
20290 pane.add_item(
20291 Box::new(proposed_changes_editor),
20292 true,
20293 true,
20294 None,
20295 window,
20296 cx,
20297 );
20298 });
20299 });
20300 });
20301 }
20302
20303 pub fn open_excerpts_in_split(
20304 &mut self,
20305 _: &OpenExcerptsSplit,
20306 window: &mut Window,
20307 cx: &mut Context<Self>,
20308 ) {
20309 self.open_excerpts_common(None, true, window, cx)
20310 }
20311
20312 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20313 self.open_excerpts_common(None, false, window, cx)
20314 }
20315
20316 fn open_excerpts_common(
20317 &mut self,
20318 jump_data: Option<JumpData>,
20319 split: bool,
20320 window: &mut Window,
20321 cx: &mut Context<Self>,
20322 ) {
20323 let Some(workspace) = self.workspace() else {
20324 cx.propagate();
20325 return;
20326 };
20327
20328 if self.buffer.read(cx).is_singleton() {
20329 cx.propagate();
20330 return;
20331 }
20332
20333 let mut new_selections_by_buffer = HashMap::default();
20334 match &jump_data {
20335 Some(JumpData::MultiBufferPoint {
20336 excerpt_id,
20337 position,
20338 anchor,
20339 line_offset_from_top,
20340 }) => {
20341 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20342 if let Some(buffer) = multi_buffer_snapshot
20343 .buffer_id_for_excerpt(*excerpt_id)
20344 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20345 {
20346 let buffer_snapshot = buffer.read(cx).snapshot();
20347 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20348 language::ToPoint::to_point(anchor, &buffer_snapshot)
20349 } else {
20350 buffer_snapshot.clip_point(*position, Bias::Left)
20351 };
20352 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20353 new_selections_by_buffer.insert(
20354 buffer,
20355 (
20356 vec![jump_to_offset..jump_to_offset],
20357 Some(*line_offset_from_top),
20358 ),
20359 );
20360 }
20361 }
20362 Some(JumpData::MultiBufferRow {
20363 row,
20364 line_offset_from_top,
20365 }) => {
20366 let point = MultiBufferPoint::new(row.0, 0);
20367 if let Some((buffer, buffer_point, _)) =
20368 self.buffer.read(cx).point_to_buffer_point(point, cx)
20369 {
20370 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20371 new_selections_by_buffer
20372 .entry(buffer)
20373 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20374 .0
20375 .push(buffer_offset..buffer_offset)
20376 }
20377 }
20378 None => {
20379 let selections = self.selections.all::<usize>(cx);
20380 let multi_buffer = self.buffer.read(cx);
20381 for selection in selections {
20382 for (snapshot, range, _, anchor) in multi_buffer
20383 .snapshot(cx)
20384 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20385 {
20386 if let Some(anchor) = anchor {
20387 // selection is in a deleted hunk
20388 let Some(buffer_id) = anchor.buffer_id else {
20389 continue;
20390 };
20391 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20392 continue;
20393 };
20394 let offset = text::ToOffset::to_offset(
20395 &anchor.text_anchor,
20396 &buffer_handle.read(cx).snapshot(),
20397 );
20398 let range = offset..offset;
20399 new_selections_by_buffer
20400 .entry(buffer_handle)
20401 .or_insert((Vec::new(), None))
20402 .0
20403 .push(range)
20404 } else {
20405 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20406 else {
20407 continue;
20408 };
20409 new_selections_by_buffer
20410 .entry(buffer_handle)
20411 .or_insert((Vec::new(), None))
20412 .0
20413 .push(range)
20414 }
20415 }
20416 }
20417 }
20418 }
20419
20420 new_selections_by_buffer
20421 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20422
20423 if new_selections_by_buffer.is_empty() {
20424 return;
20425 }
20426
20427 // We defer the pane interaction because we ourselves are a workspace item
20428 // and activating a new item causes the pane to call a method on us reentrantly,
20429 // which panics if we're on the stack.
20430 window.defer(cx, move |window, cx| {
20431 workspace.update(cx, |workspace, cx| {
20432 let pane = if split {
20433 workspace.adjacent_pane(window, cx)
20434 } else {
20435 workspace.active_pane().clone()
20436 };
20437
20438 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20439 let editor = buffer
20440 .read(cx)
20441 .file()
20442 .is_none()
20443 .then(|| {
20444 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20445 // so `workspace.open_project_item` will never find them, always opening a new editor.
20446 // Instead, we try to activate the existing editor in the pane first.
20447 let (editor, pane_item_index) =
20448 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20449 let editor = item.downcast::<Editor>()?;
20450 let singleton_buffer =
20451 editor.read(cx).buffer().read(cx).as_singleton()?;
20452 if singleton_buffer == buffer {
20453 Some((editor, i))
20454 } else {
20455 None
20456 }
20457 })?;
20458 pane.update(cx, |pane, cx| {
20459 pane.activate_item(pane_item_index, true, true, window, cx)
20460 });
20461 Some(editor)
20462 })
20463 .flatten()
20464 .unwrap_or_else(|| {
20465 workspace.open_project_item::<Self>(
20466 pane.clone(),
20467 buffer,
20468 true,
20469 true,
20470 window,
20471 cx,
20472 )
20473 });
20474
20475 editor.update(cx, |editor, cx| {
20476 let autoscroll = match scroll_offset {
20477 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20478 None => Autoscroll::newest(),
20479 };
20480 let nav_history = editor.nav_history.take();
20481 editor.change_selections(
20482 SelectionEffects::scroll(autoscroll),
20483 window,
20484 cx,
20485 |s| {
20486 s.select_ranges(ranges);
20487 },
20488 );
20489 editor.nav_history = nav_history;
20490 });
20491 }
20492 })
20493 });
20494 }
20495
20496 // For now, don't allow opening excerpts in buffers that aren't backed by
20497 // regular project files.
20498 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20499 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20500 }
20501
20502 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20503 let snapshot = self.buffer.read(cx).read(cx);
20504 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20505 Some(
20506 ranges
20507 .iter()
20508 .map(move |range| {
20509 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20510 })
20511 .collect(),
20512 )
20513 }
20514
20515 fn selection_replacement_ranges(
20516 &self,
20517 range: Range<OffsetUtf16>,
20518 cx: &mut App,
20519 ) -> Vec<Range<OffsetUtf16>> {
20520 let selections = self.selections.all::<OffsetUtf16>(cx);
20521 let newest_selection = selections
20522 .iter()
20523 .max_by_key(|selection| selection.id)
20524 .unwrap();
20525 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20526 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20527 let snapshot = self.buffer.read(cx).read(cx);
20528 selections
20529 .into_iter()
20530 .map(|mut selection| {
20531 selection.start.0 =
20532 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20533 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20534 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20535 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20536 })
20537 .collect()
20538 }
20539
20540 fn report_editor_event(
20541 &self,
20542 event_type: &'static str,
20543 file_extension: Option<String>,
20544 cx: &App,
20545 ) {
20546 if cfg!(any(test, feature = "test-support")) {
20547 return;
20548 }
20549
20550 let Some(project) = &self.project else { return };
20551
20552 // If None, we are in a file without an extension
20553 let file = self
20554 .buffer
20555 .read(cx)
20556 .as_singleton()
20557 .and_then(|b| b.read(cx).file());
20558 let file_extension = file_extension.or(file
20559 .as_ref()
20560 .and_then(|file| Path::new(file.file_name(cx)).extension())
20561 .and_then(|e| e.to_str())
20562 .map(|a| a.to_string()));
20563
20564 let vim_mode = vim_enabled(cx);
20565
20566 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20567 let copilot_enabled = edit_predictions_provider
20568 == language::language_settings::EditPredictionProvider::Copilot;
20569 let copilot_enabled_for_language = self
20570 .buffer
20571 .read(cx)
20572 .language_settings(cx)
20573 .show_edit_predictions;
20574
20575 let project = project.read(cx);
20576 telemetry::event!(
20577 event_type,
20578 file_extension,
20579 vim_mode,
20580 copilot_enabled,
20581 copilot_enabled_for_language,
20582 edit_predictions_provider,
20583 is_via_ssh = project.is_via_ssh(),
20584 );
20585 }
20586
20587 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20588 /// with each line being an array of {text, highlight} objects.
20589 fn copy_highlight_json(
20590 &mut self,
20591 _: &CopyHighlightJson,
20592 window: &mut Window,
20593 cx: &mut Context<Self>,
20594 ) {
20595 #[derive(Serialize)]
20596 struct Chunk<'a> {
20597 text: String,
20598 highlight: Option<&'a str>,
20599 }
20600
20601 let snapshot = self.buffer.read(cx).snapshot(cx);
20602 let range = self
20603 .selected_text_range(false, window, cx)
20604 .and_then(|selection| {
20605 if selection.range.is_empty() {
20606 None
20607 } else {
20608 Some(selection.range)
20609 }
20610 })
20611 .unwrap_or_else(|| 0..snapshot.len());
20612
20613 let chunks = snapshot.chunks(range, true);
20614 let mut lines = Vec::new();
20615 let mut line: VecDeque<Chunk> = VecDeque::new();
20616
20617 let Some(style) = self.style.as_ref() else {
20618 return;
20619 };
20620
20621 for chunk in chunks {
20622 let highlight = chunk
20623 .syntax_highlight_id
20624 .and_then(|id| id.name(&style.syntax));
20625 let mut chunk_lines = chunk.text.split('\n').peekable();
20626 while let Some(text) = chunk_lines.next() {
20627 let mut merged_with_last_token = false;
20628 if let Some(last_token) = line.back_mut() {
20629 if last_token.highlight == highlight {
20630 last_token.text.push_str(text);
20631 merged_with_last_token = true;
20632 }
20633 }
20634
20635 if !merged_with_last_token {
20636 line.push_back(Chunk {
20637 text: text.into(),
20638 highlight,
20639 });
20640 }
20641
20642 if chunk_lines.peek().is_some() {
20643 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20644 line.pop_front();
20645 }
20646 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20647 line.pop_back();
20648 }
20649
20650 lines.push(mem::take(&mut line));
20651 }
20652 }
20653 }
20654
20655 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20656 return;
20657 };
20658 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20659 }
20660
20661 pub fn open_context_menu(
20662 &mut self,
20663 _: &OpenContextMenu,
20664 window: &mut Window,
20665 cx: &mut Context<Self>,
20666 ) {
20667 self.request_autoscroll(Autoscroll::newest(), cx);
20668 let position = self.selections.newest_display(cx).start;
20669 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20670 }
20671
20672 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20673 &self.inlay_hint_cache
20674 }
20675
20676 pub fn replay_insert_event(
20677 &mut self,
20678 text: &str,
20679 relative_utf16_range: Option<Range<isize>>,
20680 window: &mut Window,
20681 cx: &mut Context<Self>,
20682 ) {
20683 if !self.input_enabled {
20684 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20685 return;
20686 }
20687 if let Some(relative_utf16_range) = relative_utf16_range {
20688 let selections = self.selections.all::<OffsetUtf16>(cx);
20689 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20690 let new_ranges = selections.into_iter().map(|range| {
20691 let start = OffsetUtf16(
20692 range
20693 .head()
20694 .0
20695 .saturating_add_signed(relative_utf16_range.start),
20696 );
20697 let end = OffsetUtf16(
20698 range
20699 .head()
20700 .0
20701 .saturating_add_signed(relative_utf16_range.end),
20702 );
20703 start..end
20704 });
20705 s.select_ranges(new_ranges);
20706 });
20707 }
20708
20709 self.handle_input(text, window, cx);
20710 }
20711
20712 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20713 let Some(provider) = self.semantics_provider.as_ref() else {
20714 return false;
20715 };
20716
20717 let mut supports = false;
20718 self.buffer().update(cx, |this, cx| {
20719 this.for_each_buffer(|buffer| {
20720 supports |= provider.supports_inlay_hints(buffer, cx);
20721 });
20722 });
20723
20724 supports
20725 }
20726
20727 pub fn is_focused(&self, window: &Window) -> bool {
20728 self.focus_handle.is_focused(window)
20729 }
20730
20731 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20732 cx.emit(EditorEvent::Focused);
20733
20734 if let Some(descendant) = self
20735 .last_focused_descendant
20736 .take()
20737 .and_then(|descendant| descendant.upgrade())
20738 {
20739 window.focus(&descendant);
20740 } else {
20741 if let Some(blame) = self.blame.as_ref() {
20742 blame.update(cx, GitBlame::focus)
20743 }
20744
20745 self.blink_manager.update(cx, BlinkManager::enable);
20746 self.show_cursor_names(window, cx);
20747 self.buffer.update(cx, |buffer, cx| {
20748 buffer.finalize_last_transaction(cx);
20749 if self.leader_id.is_none() {
20750 buffer.set_active_selections(
20751 &self.selections.disjoint_anchors(),
20752 self.selections.line_mode,
20753 self.cursor_shape,
20754 cx,
20755 );
20756 }
20757 });
20758 }
20759 }
20760
20761 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20762 cx.emit(EditorEvent::FocusedIn)
20763 }
20764
20765 fn handle_focus_out(
20766 &mut self,
20767 event: FocusOutEvent,
20768 _window: &mut Window,
20769 cx: &mut Context<Self>,
20770 ) {
20771 if event.blurred != self.focus_handle {
20772 self.last_focused_descendant = Some(event.blurred);
20773 }
20774 self.selection_drag_state = SelectionDragState::None;
20775 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20776 }
20777
20778 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20779 self.blink_manager.update(cx, BlinkManager::disable);
20780 self.buffer
20781 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20782
20783 if let Some(blame) = self.blame.as_ref() {
20784 blame.update(cx, GitBlame::blur)
20785 }
20786 if !self.hover_state.focused(window, cx) {
20787 hide_hover(self, cx);
20788 }
20789 if !self
20790 .context_menu
20791 .borrow()
20792 .as_ref()
20793 .is_some_and(|context_menu| context_menu.focused(window, cx))
20794 {
20795 self.hide_context_menu(window, cx);
20796 }
20797 self.discard_edit_prediction(false, cx);
20798 cx.emit(EditorEvent::Blurred);
20799 cx.notify();
20800 }
20801
20802 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20803 let mut pending: String = window
20804 .pending_input_keystrokes()
20805 .into_iter()
20806 .flatten()
20807 .filter_map(|keystroke| {
20808 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20809 keystroke.key_char.clone()
20810 } else {
20811 None
20812 }
20813 })
20814 .collect();
20815
20816 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20817 pending = "".to_string();
20818 }
20819
20820 let existing_pending = self
20821 .text_highlights::<PendingInput>(cx)
20822 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20823 if existing_pending.is_none() && pending.is_empty() {
20824 return;
20825 }
20826 let transaction =
20827 self.transact(window, cx, |this, window, cx| {
20828 let selections = this.selections.all::<usize>(cx);
20829 let edits = selections
20830 .iter()
20831 .map(|selection| (selection.end..selection.end, pending.clone()));
20832 this.edit(edits, cx);
20833 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20834 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20835 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20836 }));
20837 });
20838 if let Some(existing_ranges) = existing_pending {
20839 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20840 this.edit(edits, cx);
20841 }
20842 });
20843
20844 let snapshot = self.snapshot(window, cx);
20845 let ranges = self
20846 .selections
20847 .all::<usize>(cx)
20848 .into_iter()
20849 .map(|selection| {
20850 snapshot.buffer_snapshot.anchor_after(selection.end)
20851 ..snapshot
20852 .buffer_snapshot
20853 .anchor_before(selection.end + pending.len())
20854 })
20855 .collect();
20856
20857 if pending.is_empty() {
20858 self.clear_highlights::<PendingInput>(cx);
20859 } else {
20860 self.highlight_text::<PendingInput>(
20861 ranges,
20862 HighlightStyle {
20863 underline: Some(UnderlineStyle {
20864 thickness: px(1.),
20865 color: None,
20866 wavy: false,
20867 }),
20868 ..Default::default()
20869 },
20870 cx,
20871 );
20872 }
20873
20874 self.ime_transaction = self.ime_transaction.or(transaction);
20875 if let Some(transaction) = self.ime_transaction {
20876 self.buffer.update(cx, |buffer, cx| {
20877 buffer.group_until_transaction(transaction, cx);
20878 });
20879 }
20880
20881 if self.text_highlights::<PendingInput>(cx).is_none() {
20882 self.ime_transaction.take();
20883 }
20884 }
20885
20886 pub fn register_action_renderer(
20887 &mut self,
20888 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20889 ) -> Subscription {
20890 let id = self.next_editor_action_id.post_inc();
20891 self.editor_actions
20892 .borrow_mut()
20893 .insert(id, Box::new(listener));
20894
20895 let editor_actions = self.editor_actions.clone();
20896 Subscription::new(move || {
20897 editor_actions.borrow_mut().remove(&id);
20898 })
20899 }
20900
20901 pub fn register_action<A: Action>(
20902 &mut self,
20903 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20904 ) -> Subscription {
20905 let id = self.next_editor_action_id.post_inc();
20906 let listener = Arc::new(listener);
20907 self.editor_actions.borrow_mut().insert(
20908 id,
20909 Box::new(move |_, window, _| {
20910 let listener = listener.clone();
20911 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20912 let action = action.downcast_ref().unwrap();
20913 if phase == DispatchPhase::Bubble {
20914 listener(action, window, cx)
20915 }
20916 })
20917 }),
20918 );
20919
20920 let editor_actions = self.editor_actions.clone();
20921 Subscription::new(move || {
20922 editor_actions.borrow_mut().remove(&id);
20923 })
20924 }
20925
20926 pub fn file_header_size(&self) -> u32 {
20927 FILE_HEADER_HEIGHT
20928 }
20929
20930 pub fn restore(
20931 &mut self,
20932 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20933 window: &mut Window,
20934 cx: &mut Context<Self>,
20935 ) {
20936 let workspace = self.workspace();
20937 let project = self.project.as_ref();
20938 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20939 let mut tasks = Vec::new();
20940 for (buffer_id, changes) in revert_changes {
20941 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20942 buffer.update(cx, |buffer, cx| {
20943 buffer.edit(
20944 changes
20945 .into_iter()
20946 .map(|(range, text)| (range, text.to_string())),
20947 None,
20948 cx,
20949 );
20950 });
20951
20952 if let Some(project) =
20953 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20954 {
20955 project.update(cx, |project, cx| {
20956 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20957 })
20958 }
20959 }
20960 }
20961 tasks
20962 });
20963 cx.spawn_in(window, async move |_, cx| {
20964 for (buffer, task) in save_tasks {
20965 let result = task.await;
20966 if result.is_err() {
20967 let Some(path) = buffer
20968 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20969 .ok()
20970 else {
20971 continue;
20972 };
20973 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20974 let Some(task) = cx
20975 .update_window_entity(&workspace, |workspace, window, cx| {
20976 workspace
20977 .open_path_preview(path, None, false, false, false, window, cx)
20978 })
20979 .ok()
20980 else {
20981 continue;
20982 };
20983 task.await.log_err();
20984 }
20985 }
20986 }
20987 })
20988 .detach();
20989 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20990 selections.refresh()
20991 });
20992 }
20993
20994 pub fn to_pixel_point(
20995 &self,
20996 source: multi_buffer::Anchor,
20997 editor_snapshot: &EditorSnapshot,
20998 window: &mut Window,
20999 ) -> Option<gpui::Point<Pixels>> {
21000 let source_point = source.to_display_point(editor_snapshot);
21001 self.display_to_pixel_point(source_point, editor_snapshot, window)
21002 }
21003
21004 pub fn display_to_pixel_point(
21005 &self,
21006 source: DisplayPoint,
21007 editor_snapshot: &EditorSnapshot,
21008 window: &mut Window,
21009 ) -> Option<gpui::Point<Pixels>> {
21010 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21011 let text_layout_details = self.text_layout_details(window);
21012 let scroll_top = text_layout_details
21013 .scroll_anchor
21014 .scroll_position(editor_snapshot)
21015 .y;
21016
21017 if source.row().as_f32() < scroll_top.floor() {
21018 return None;
21019 }
21020 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21021 let source_y = line_height * (source.row().as_f32() - scroll_top);
21022 Some(gpui::Point::new(source_x, source_y))
21023 }
21024
21025 pub fn has_visible_completions_menu(&self) -> bool {
21026 !self.edit_prediction_preview_is_active()
21027 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
21028 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21029 })
21030 }
21031
21032 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21033 if self.mode.is_minimap() {
21034 return;
21035 }
21036 self.addons
21037 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21038 }
21039
21040 pub fn unregister_addon<T: Addon>(&mut self) {
21041 self.addons.remove(&std::any::TypeId::of::<T>());
21042 }
21043
21044 pub fn addon<T: Addon>(&self) -> Option<&T> {
21045 let type_id = std::any::TypeId::of::<T>();
21046 self.addons
21047 .get(&type_id)
21048 .and_then(|item| item.to_any().downcast_ref::<T>())
21049 }
21050
21051 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21052 let type_id = std::any::TypeId::of::<T>();
21053 self.addons
21054 .get_mut(&type_id)
21055 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21056 }
21057
21058 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21059 let text_layout_details = self.text_layout_details(window);
21060 let style = &text_layout_details.editor_style;
21061 let font_id = window.text_system().resolve_font(&style.text.font());
21062 let font_size = style.text.font_size.to_pixels(window.rem_size());
21063 let line_height = style.text.line_height_in_pixels(window.rem_size());
21064 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21065 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21066
21067 CharacterDimensions {
21068 em_width,
21069 em_advance,
21070 line_height,
21071 }
21072 }
21073
21074 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21075 self.load_diff_task.clone()
21076 }
21077
21078 fn read_metadata_from_db(
21079 &mut self,
21080 item_id: u64,
21081 workspace_id: WorkspaceId,
21082 window: &mut Window,
21083 cx: &mut Context<Editor>,
21084 ) {
21085 if self.is_singleton(cx)
21086 && !self.mode.is_minimap()
21087 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21088 {
21089 let buffer_snapshot = OnceCell::new();
21090
21091 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21092 if !folds.is_empty() {
21093 let snapshot =
21094 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21095 self.fold_ranges(
21096 folds
21097 .into_iter()
21098 .map(|(start, end)| {
21099 snapshot.clip_offset(start, Bias::Left)
21100 ..snapshot.clip_offset(end, Bias::Right)
21101 })
21102 .collect(),
21103 false,
21104 window,
21105 cx,
21106 );
21107 }
21108 }
21109
21110 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21111 if !selections.is_empty() {
21112 let snapshot =
21113 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21114 // skip adding the initial selection to selection history
21115 self.selection_history.mode = SelectionHistoryMode::Skipping;
21116 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21117 s.select_ranges(selections.into_iter().map(|(start, end)| {
21118 snapshot.clip_offset(start, Bias::Left)
21119 ..snapshot.clip_offset(end, Bias::Right)
21120 }));
21121 });
21122 self.selection_history.mode = SelectionHistoryMode::Normal;
21123 }
21124 };
21125 }
21126
21127 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21128 }
21129
21130 fn update_lsp_data(
21131 &mut self,
21132 ignore_cache: bool,
21133 for_buffer: Option<BufferId>,
21134 window: &mut Window,
21135 cx: &mut Context<'_, Self>,
21136 ) {
21137 self.pull_diagnostics(for_buffer, window, cx);
21138 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21139 }
21140}
21141
21142fn vim_enabled(cx: &App) -> bool {
21143 cx.global::<SettingsStore>()
21144 .raw_user_settings()
21145 .get("vim_mode")
21146 == Some(&serde_json::Value::Bool(true))
21147}
21148
21149fn process_completion_for_edit(
21150 completion: &Completion,
21151 intent: CompletionIntent,
21152 buffer: &Entity<Buffer>,
21153 cursor_position: &text::Anchor,
21154 cx: &mut Context<Editor>,
21155) -> CompletionEdit {
21156 let buffer = buffer.read(cx);
21157 let buffer_snapshot = buffer.snapshot();
21158 let (snippet, new_text) = if completion.is_snippet() {
21159 // Workaround for typescript language server issues so that methods don't expand within
21160 // strings and functions with type expressions. The previous point is used because the query
21161 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21162 let mut snippet_source = completion.new_text.clone();
21163 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21164 previous_point.column = previous_point.column.saturating_sub(1);
21165 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21166 if scope.prefers_label_for_snippet_in_completion() {
21167 if let Some(label) = completion.label() {
21168 if matches!(
21169 completion.kind(),
21170 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21171 ) {
21172 snippet_source = label;
21173 }
21174 }
21175 }
21176 }
21177 match Snippet::parse(&snippet_source).log_err() {
21178 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21179 None => (None, completion.new_text.clone()),
21180 }
21181 } else {
21182 (None, completion.new_text.clone())
21183 };
21184
21185 let mut range_to_replace = {
21186 let replace_range = &completion.replace_range;
21187 if let CompletionSource::Lsp {
21188 insert_range: Some(insert_range),
21189 ..
21190 } = &completion.source
21191 {
21192 debug_assert_eq!(
21193 insert_range.start, replace_range.start,
21194 "insert_range and replace_range should start at the same position"
21195 );
21196 debug_assert!(
21197 insert_range
21198 .start
21199 .cmp(&cursor_position, &buffer_snapshot)
21200 .is_le(),
21201 "insert_range should start before or at cursor position"
21202 );
21203 debug_assert!(
21204 replace_range
21205 .start
21206 .cmp(&cursor_position, &buffer_snapshot)
21207 .is_le(),
21208 "replace_range should start before or at cursor position"
21209 );
21210
21211 let should_replace = match intent {
21212 CompletionIntent::CompleteWithInsert => false,
21213 CompletionIntent::CompleteWithReplace => true,
21214 CompletionIntent::Complete | CompletionIntent::Compose => {
21215 let insert_mode =
21216 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21217 .completions
21218 .lsp_insert_mode;
21219 match insert_mode {
21220 LspInsertMode::Insert => false,
21221 LspInsertMode::Replace => true,
21222 LspInsertMode::ReplaceSubsequence => {
21223 let mut text_to_replace = buffer.chars_for_range(
21224 buffer.anchor_before(replace_range.start)
21225 ..buffer.anchor_after(replace_range.end),
21226 );
21227 let mut current_needle = text_to_replace.next();
21228 for haystack_ch in completion.label.text.chars() {
21229 if let Some(needle_ch) = current_needle {
21230 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21231 current_needle = text_to_replace.next();
21232 }
21233 }
21234 }
21235 current_needle.is_none()
21236 }
21237 LspInsertMode::ReplaceSuffix => {
21238 if replace_range
21239 .end
21240 .cmp(&cursor_position, &buffer_snapshot)
21241 .is_gt()
21242 {
21243 let range_after_cursor = *cursor_position..replace_range.end;
21244 let text_after_cursor = buffer
21245 .text_for_range(
21246 buffer.anchor_before(range_after_cursor.start)
21247 ..buffer.anchor_after(range_after_cursor.end),
21248 )
21249 .collect::<String>()
21250 .to_ascii_lowercase();
21251 completion
21252 .label
21253 .text
21254 .to_ascii_lowercase()
21255 .ends_with(&text_after_cursor)
21256 } else {
21257 true
21258 }
21259 }
21260 }
21261 }
21262 };
21263
21264 if should_replace {
21265 replace_range.clone()
21266 } else {
21267 insert_range.clone()
21268 }
21269 } else {
21270 replace_range.clone()
21271 }
21272 };
21273
21274 if range_to_replace
21275 .end
21276 .cmp(&cursor_position, &buffer_snapshot)
21277 .is_lt()
21278 {
21279 range_to_replace.end = *cursor_position;
21280 }
21281
21282 CompletionEdit {
21283 new_text,
21284 replace_range: range_to_replace.to_offset(&buffer),
21285 snippet,
21286 }
21287}
21288
21289struct CompletionEdit {
21290 new_text: String,
21291 replace_range: Range<usize>,
21292 snippet: Option<Snippet>,
21293}
21294
21295fn insert_extra_newline_brackets(
21296 buffer: &MultiBufferSnapshot,
21297 range: Range<usize>,
21298 language: &language::LanguageScope,
21299) -> bool {
21300 let leading_whitespace_len = buffer
21301 .reversed_chars_at(range.start)
21302 .take_while(|c| c.is_whitespace() && *c != '\n')
21303 .map(|c| c.len_utf8())
21304 .sum::<usize>();
21305 let trailing_whitespace_len = buffer
21306 .chars_at(range.end)
21307 .take_while(|c| c.is_whitespace() && *c != '\n')
21308 .map(|c| c.len_utf8())
21309 .sum::<usize>();
21310 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21311
21312 language.brackets().any(|(pair, enabled)| {
21313 let pair_start = pair.start.trim_end();
21314 let pair_end = pair.end.trim_start();
21315
21316 enabled
21317 && pair.newline
21318 && buffer.contains_str_at(range.end, pair_end)
21319 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21320 })
21321}
21322
21323fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21324 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21325 [(buffer, range, _)] => (*buffer, range.clone()),
21326 _ => return false,
21327 };
21328 let pair = {
21329 let mut result: Option<BracketMatch> = None;
21330
21331 for pair in buffer
21332 .all_bracket_ranges(range.clone())
21333 .filter(move |pair| {
21334 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21335 })
21336 {
21337 let len = pair.close_range.end - pair.open_range.start;
21338
21339 if let Some(existing) = &result {
21340 let existing_len = existing.close_range.end - existing.open_range.start;
21341 if len > existing_len {
21342 continue;
21343 }
21344 }
21345
21346 result = Some(pair);
21347 }
21348
21349 result
21350 };
21351 let Some(pair) = pair else {
21352 return false;
21353 };
21354 pair.newline_only
21355 && buffer
21356 .chars_for_range(pair.open_range.end..range.start)
21357 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21358 .all(|c| c.is_whitespace() && c != '\n')
21359}
21360
21361fn update_uncommitted_diff_for_buffer(
21362 editor: Entity<Editor>,
21363 project: &Entity<Project>,
21364 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21365 buffer: Entity<MultiBuffer>,
21366 cx: &mut App,
21367) -> Task<()> {
21368 let mut tasks = Vec::new();
21369 project.update(cx, |project, cx| {
21370 for buffer in buffers {
21371 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21372 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21373 }
21374 }
21375 });
21376 cx.spawn(async move |cx| {
21377 let diffs = future::join_all(tasks).await;
21378 if editor
21379 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21380 .unwrap_or(false)
21381 {
21382 return;
21383 }
21384
21385 buffer
21386 .update(cx, |buffer, cx| {
21387 for diff in diffs.into_iter().flatten() {
21388 buffer.add_diff(diff, cx);
21389 }
21390 })
21391 .ok();
21392 })
21393}
21394
21395fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21396 let tab_size = tab_size.get() as usize;
21397 let mut width = offset;
21398
21399 for ch in text.chars() {
21400 width += if ch == '\t' {
21401 tab_size - (width % tab_size)
21402 } else {
21403 1
21404 };
21405 }
21406
21407 width - offset
21408}
21409
21410#[cfg(test)]
21411mod tests {
21412 use super::*;
21413
21414 #[test]
21415 fn test_string_size_with_expanded_tabs() {
21416 let nz = |val| NonZeroU32::new(val).unwrap();
21417 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21418 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21419 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21420 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21421 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21422 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21423 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21424 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21425 }
21426}
21427
21428/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21429struct WordBreakingTokenizer<'a> {
21430 input: &'a str,
21431}
21432
21433impl<'a> WordBreakingTokenizer<'a> {
21434 fn new(input: &'a str) -> Self {
21435 Self { input }
21436 }
21437}
21438
21439fn is_char_ideographic(ch: char) -> bool {
21440 use unicode_script::Script::*;
21441 use unicode_script::UnicodeScript;
21442 matches!(ch.script(), Han | Tangut | Yi)
21443}
21444
21445fn is_grapheme_ideographic(text: &str) -> bool {
21446 text.chars().any(is_char_ideographic)
21447}
21448
21449fn is_grapheme_whitespace(text: &str) -> bool {
21450 text.chars().any(|x| x.is_whitespace())
21451}
21452
21453fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21454 text.chars().next().map_or(false, |ch| {
21455 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21456 })
21457}
21458
21459#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21460enum WordBreakToken<'a> {
21461 Word { token: &'a str, grapheme_len: usize },
21462 InlineWhitespace { token: &'a str, grapheme_len: usize },
21463 Newline,
21464}
21465
21466impl<'a> Iterator for WordBreakingTokenizer<'a> {
21467 /// Yields a span, the count of graphemes in the token, and whether it was
21468 /// whitespace. Note that it also breaks at word boundaries.
21469 type Item = WordBreakToken<'a>;
21470
21471 fn next(&mut self) -> Option<Self::Item> {
21472 use unicode_segmentation::UnicodeSegmentation;
21473 if self.input.is_empty() {
21474 return None;
21475 }
21476
21477 let mut iter = self.input.graphemes(true).peekable();
21478 let mut offset = 0;
21479 let mut grapheme_len = 0;
21480 if let Some(first_grapheme) = iter.next() {
21481 let is_newline = first_grapheme == "\n";
21482 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21483 offset += first_grapheme.len();
21484 grapheme_len += 1;
21485 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21486 if let Some(grapheme) = iter.peek().copied() {
21487 if should_stay_with_preceding_ideograph(grapheme) {
21488 offset += grapheme.len();
21489 grapheme_len += 1;
21490 }
21491 }
21492 } else {
21493 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21494 let mut next_word_bound = words.peek().copied();
21495 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21496 next_word_bound = words.next();
21497 }
21498 while let Some(grapheme) = iter.peek().copied() {
21499 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21500 break;
21501 };
21502 if is_grapheme_whitespace(grapheme) != is_whitespace
21503 || (grapheme == "\n") != is_newline
21504 {
21505 break;
21506 };
21507 offset += grapheme.len();
21508 grapheme_len += 1;
21509 iter.next();
21510 }
21511 }
21512 let token = &self.input[..offset];
21513 self.input = &self.input[offset..];
21514 if token == "\n" {
21515 Some(WordBreakToken::Newline)
21516 } else if is_whitespace {
21517 Some(WordBreakToken::InlineWhitespace {
21518 token,
21519 grapheme_len,
21520 })
21521 } else {
21522 Some(WordBreakToken::Word {
21523 token,
21524 grapheme_len,
21525 })
21526 }
21527 } else {
21528 None
21529 }
21530 }
21531}
21532
21533#[test]
21534fn test_word_breaking_tokenizer() {
21535 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21536 ("", &[]),
21537 (" ", &[whitespace(" ", 2)]),
21538 ("Ʒ", &[word("Ʒ", 1)]),
21539 ("Ǽ", &[word("Ǽ", 1)]),
21540 ("⋑", &[word("⋑", 1)]),
21541 ("⋑⋑", &[word("⋑⋑", 2)]),
21542 (
21543 "原理,进而",
21544 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21545 ),
21546 (
21547 "hello world",
21548 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21549 ),
21550 (
21551 "hello, world",
21552 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21553 ),
21554 (
21555 " hello world",
21556 &[
21557 whitespace(" ", 2),
21558 word("hello", 5),
21559 whitespace(" ", 1),
21560 word("world", 5),
21561 ],
21562 ),
21563 (
21564 "这是什么 \n 钢笔",
21565 &[
21566 word("这", 1),
21567 word("是", 1),
21568 word("什", 1),
21569 word("么", 1),
21570 whitespace(" ", 1),
21571 newline(),
21572 whitespace(" ", 1),
21573 word("钢", 1),
21574 word("笔", 1),
21575 ],
21576 ),
21577 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21578 ];
21579
21580 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21581 WordBreakToken::Word {
21582 token,
21583 grapheme_len,
21584 }
21585 }
21586
21587 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21588 WordBreakToken::InlineWhitespace {
21589 token,
21590 grapheme_len,
21591 }
21592 }
21593
21594 fn newline() -> WordBreakToken<'static> {
21595 WordBreakToken::Newline
21596 }
21597
21598 for (input, result) in tests {
21599 assert_eq!(
21600 WordBreakingTokenizer::new(input)
21601 .collect::<Vec<_>>()
21602 .as_slice(),
21603 *result,
21604 );
21605 }
21606}
21607
21608fn wrap_with_prefix(
21609 first_line_prefix: String,
21610 subsequent_lines_prefix: String,
21611 unwrapped_text: String,
21612 wrap_column: usize,
21613 tab_size: NonZeroU32,
21614 preserve_existing_whitespace: bool,
21615) -> String {
21616 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21617 let subsequent_lines_prefix_len =
21618 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21619 let mut wrapped_text = String::new();
21620 let mut current_line = first_line_prefix.clone();
21621 let mut is_first_line = true;
21622
21623 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21624 let mut current_line_len = first_line_prefix_len;
21625 let mut in_whitespace = false;
21626 for token in tokenizer {
21627 let have_preceding_whitespace = in_whitespace;
21628 match token {
21629 WordBreakToken::Word {
21630 token,
21631 grapheme_len,
21632 } => {
21633 in_whitespace = false;
21634 let current_prefix_len = if is_first_line {
21635 first_line_prefix_len
21636 } else {
21637 subsequent_lines_prefix_len
21638 };
21639 if current_line_len + grapheme_len > wrap_column
21640 && current_line_len != current_prefix_len
21641 {
21642 wrapped_text.push_str(current_line.trim_end());
21643 wrapped_text.push('\n');
21644 is_first_line = false;
21645 current_line = subsequent_lines_prefix.clone();
21646 current_line_len = subsequent_lines_prefix_len;
21647 }
21648 current_line.push_str(token);
21649 current_line_len += grapheme_len;
21650 }
21651 WordBreakToken::InlineWhitespace {
21652 mut token,
21653 mut grapheme_len,
21654 } => {
21655 in_whitespace = true;
21656 if have_preceding_whitespace && !preserve_existing_whitespace {
21657 continue;
21658 }
21659 if !preserve_existing_whitespace {
21660 token = " ";
21661 grapheme_len = 1;
21662 }
21663 let current_prefix_len = if is_first_line {
21664 first_line_prefix_len
21665 } else {
21666 subsequent_lines_prefix_len
21667 };
21668 if current_line_len + grapheme_len > wrap_column {
21669 wrapped_text.push_str(current_line.trim_end());
21670 wrapped_text.push('\n');
21671 is_first_line = false;
21672 current_line = subsequent_lines_prefix.clone();
21673 current_line_len = subsequent_lines_prefix_len;
21674 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21675 current_line.push_str(token);
21676 current_line_len += grapheme_len;
21677 }
21678 }
21679 WordBreakToken::Newline => {
21680 in_whitespace = true;
21681 let current_prefix_len = if is_first_line {
21682 first_line_prefix_len
21683 } else {
21684 subsequent_lines_prefix_len
21685 };
21686 if preserve_existing_whitespace {
21687 wrapped_text.push_str(current_line.trim_end());
21688 wrapped_text.push('\n');
21689 is_first_line = false;
21690 current_line = subsequent_lines_prefix.clone();
21691 current_line_len = subsequent_lines_prefix_len;
21692 } else if have_preceding_whitespace {
21693 continue;
21694 } else if current_line_len + 1 > wrap_column
21695 && current_line_len != current_prefix_len
21696 {
21697 wrapped_text.push_str(current_line.trim_end());
21698 wrapped_text.push('\n');
21699 is_first_line = false;
21700 current_line = subsequent_lines_prefix.clone();
21701 current_line_len = subsequent_lines_prefix_len;
21702 } else if current_line_len != current_prefix_len {
21703 current_line.push(' ');
21704 current_line_len += 1;
21705 }
21706 }
21707 }
21708 }
21709
21710 if !current_line.is_empty() {
21711 wrapped_text.push_str(¤t_line);
21712 }
21713 wrapped_text
21714}
21715
21716#[test]
21717fn test_wrap_with_prefix() {
21718 assert_eq!(
21719 wrap_with_prefix(
21720 "# ".to_string(),
21721 "# ".to_string(),
21722 "abcdefg".to_string(),
21723 4,
21724 NonZeroU32::new(4).unwrap(),
21725 false,
21726 ),
21727 "# abcdefg"
21728 );
21729 assert_eq!(
21730 wrap_with_prefix(
21731 "".to_string(),
21732 "".to_string(),
21733 "\thello world".to_string(),
21734 8,
21735 NonZeroU32::new(4).unwrap(),
21736 false,
21737 ),
21738 "hello\nworld"
21739 );
21740 assert_eq!(
21741 wrap_with_prefix(
21742 "// ".to_string(),
21743 "// ".to_string(),
21744 "xx \nyy zz aa bb cc".to_string(),
21745 12,
21746 NonZeroU32::new(4).unwrap(),
21747 false,
21748 ),
21749 "// xx yy zz\n// aa bb cc"
21750 );
21751 assert_eq!(
21752 wrap_with_prefix(
21753 String::new(),
21754 String::new(),
21755 "这是什么 \n 钢笔".to_string(),
21756 3,
21757 NonZeroU32::new(4).unwrap(),
21758 false,
21759 ),
21760 "这是什\n么 钢\n笔"
21761 );
21762}
21763
21764pub trait CollaborationHub {
21765 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21766 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21767 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21768}
21769
21770impl CollaborationHub for Entity<Project> {
21771 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21772 self.read(cx).collaborators()
21773 }
21774
21775 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21776 self.read(cx).user_store().read(cx).participant_indices()
21777 }
21778
21779 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21780 let this = self.read(cx);
21781 let user_ids = this.collaborators().values().map(|c| c.user_id);
21782 this.user_store().read(cx).participant_names(user_ids, cx)
21783 }
21784}
21785
21786pub trait SemanticsProvider {
21787 fn hover(
21788 &self,
21789 buffer: &Entity<Buffer>,
21790 position: text::Anchor,
21791 cx: &mut App,
21792 ) -> Option<Task<Vec<project::Hover>>>;
21793
21794 fn inline_values(
21795 &self,
21796 buffer_handle: Entity<Buffer>,
21797 range: Range<text::Anchor>,
21798 cx: &mut App,
21799 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21800
21801 fn inlay_hints(
21802 &self,
21803 buffer_handle: Entity<Buffer>,
21804 range: Range<text::Anchor>,
21805 cx: &mut App,
21806 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21807
21808 fn resolve_inlay_hint(
21809 &self,
21810 hint: InlayHint,
21811 buffer_handle: Entity<Buffer>,
21812 server_id: LanguageServerId,
21813 cx: &mut App,
21814 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21815
21816 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21817
21818 fn document_highlights(
21819 &self,
21820 buffer: &Entity<Buffer>,
21821 position: text::Anchor,
21822 cx: &mut App,
21823 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21824
21825 fn definitions(
21826 &self,
21827 buffer: &Entity<Buffer>,
21828 position: text::Anchor,
21829 kind: GotoDefinitionKind,
21830 cx: &mut App,
21831 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21832
21833 fn range_for_rename(
21834 &self,
21835 buffer: &Entity<Buffer>,
21836 position: text::Anchor,
21837 cx: &mut App,
21838 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21839
21840 fn perform_rename(
21841 &self,
21842 buffer: &Entity<Buffer>,
21843 position: text::Anchor,
21844 new_name: String,
21845 cx: &mut App,
21846 ) -> Option<Task<Result<ProjectTransaction>>>;
21847}
21848
21849pub trait CompletionProvider {
21850 fn completions(
21851 &self,
21852 excerpt_id: ExcerptId,
21853 buffer: &Entity<Buffer>,
21854 buffer_position: text::Anchor,
21855 trigger: CompletionContext,
21856 window: &mut Window,
21857 cx: &mut Context<Editor>,
21858 ) -> Task<Result<Vec<CompletionResponse>>>;
21859
21860 fn resolve_completions(
21861 &self,
21862 _buffer: Entity<Buffer>,
21863 _completion_indices: Vec<usize>,
21864 _completions: Rc<RefCell<Box<[Completion]>>>,
21865 _cx: &mut Context<Editor>,
21866 ) -> Task<Result<bool>> {
21867 Task::ready(Ok(false))
21868 }
21869
21870 fn apply_additional_edits_for_completion(
21871 &self,
21872 _buffer: Entity<Buffer>,
21873 _completions: Rc<RefCell<Box<[Completion]>>>,
21874 _completion_index: usize,
21875 _push_to_history: bool,
21876 _cx: &mut Context<Editor>,
21877 ) -> Task<Result<Option<language::Transaction>>> {
21878 Task::ready(Ok(None))
21879 }
21880
21881 fn is_completion_trigger(
21882 &self,
21883 buffer: &Entity<Buffer>,
21884 position: language::Anchor,
21885 text: &str,
21886 trigger_in_words: bool,
21887 menu_is_open: bool,
21888 cx: &mut Context<Editor>,
21889 ) -> bool;
21890
21891 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21892
21893 fn sort_completions(&self) -> bool {
21894 true
21895 }
21896
21897 fn filter_completions(&self) -> bool {
21898 true
21899 }
21900}
21901
21902pub trait CodeActionProvider {
21903 fn id(&self) -> Arc<str>;
21904
21905 fn code_actions(
21906 &self,
21907 buffer: &Entity<Buffer>,
21908 range: Range<text::Anchor>,
21909 window: &mut Window,
21910 cx: &mut App,
21911 ) -> Task<Result<Vec<CodeAction>>>;
21912
21913 fn apply_code_action(
21914 &self,
21915 buffer_handle: Entity<Buffer>,
21916 action: CodeAction,
21917 excerpt_id: ExcerptId,
21918 push_to_history: bool,
21919 window: &mut Window,
21920 cx: &mut App,
21921 ) -> Task<Result<ProjectTransaction>>;
21922}
21923
21924impl CodeActionProvider for Entity<Project> {
21925 fn id(&self) -> Arc<str> {
21926 "project".into()
21927 }
21928
21929 fn code_actions(
21930 &self,
21931 buffer: &Entity<Buffer>,
21932 range: Range<text::Anchor>,
21933 _window: &mut Window,
21934 cx: &mut App,
21935 ) -> Task<Result<Vec<CodeAction>>> {
21936 self.update(cx, |project, cx| {
21937 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21938 let code_actions = project.code_actions(buffer, range, None, cx);
21939 cx.background_spawn(async move {
21940 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21941 Ok(code_lens_actions
21942 .context("code lens fetch")?
21943 .into_iter()
21944 .chain(code_actions.context("code action fetch")?)
21945 .collect())
21946 })
21947 })
21948 }
21949
21950 fn apply_code_action(
21951 &self,
21952 buffer_handle: Entity<Buffer>,
21953 action: CodeAction,
21954 _excerpt_id: ExcerptId,
21955 push_to_history: bool,
21956 _window: &mut Window,
21957 cx: &mut App,
21958 ) -> Task<Result<ProjectTransaction>> {
21959 self.update(cx, |project, cx| {
21960 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21961 })
21962 }
21963}
21964
21965fn snippet_completions(
21966 project: &Project,
21967 buffer: &Entity<Buffer>,
21968 buffer_position: text::Anchor,
21969 cx: &mut App,
21970) -> Task<Result<CompletionResponse>> {
21971 let languages = buffer.read(cx).languages_at(buffer_position);
21972 let snippet_store = project.snippets().read(cx);
21973
21974 let scopes: Vec<_> = languages
21975 .iter()
21976 .filter_map(|language| {
21977 let language_name = language.lsp_id();
21978 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21979
21980 if snippets.is_empty() {
21981 None
21982 } else {
21983 Some((language.default_scope(), snippets))
21984 }
21985 })
21986 .collect();
21987
21988 if scopes.is_empty() {
21989 return Task::ready(Ok(CompletionResponse {
21990 completions: vec![],
21991 is_incomplete: false,
21992 }));
21993 }
21994
21995 let snapshot = buffer.read(cx).text_snapshot();
21996 let chars: String = snapshot
21997 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21998 .collect();
21999 let executor = cx.background_executor().clone();
22000
22001 cx.background_spawn(async move {
22002 let mut is_incomplete = false;
22003 let mut completions: Vec<Completion> = Vec::new();
22004 for (scope, snippets) in scopes.into_iter() {
22005 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22006 let mut last_word = chars
22007 .chars()
22008 .take_while(|c| classifier.is_word(*c))
22009 .collect::<String>();
22010 last_word = last_word.chars().rev().collect();
22011
22012 if last_word.is_empty() {
22013 return Ok(CompletionResponse {
22014 completions: vec![],
22015 is_incomplete: true,
22016 });
22017 }
22018
22019 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22020 let to_lsp = |point: &text::Anchor| {
22021 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22022 point_to_lsp(end)
22023 };
22024 let lsp_end = to_lsp(&buffer_position);
22025
22026 let candidates = snippets
22027 .iter()
22028 .enumerate()
22029 .flat_map(|(ix, snippet)| {
22030 snippet
22031 .prefix
22032 .iter()
22033 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
22034 })
22035 .collect::<Vec<StringMatchCandidate>>();
22036
22037 const MAX_RESULTS: usize = 100;
22038 let mut matches = fuzzy::match_strings(
22039 &candidates,
22040 &last_word,
22041 last_word.chars().any(|c| c.is_uppercase()),
22042 true,
22043 MAX_RESULTS,
22044 &Default::default(),
22045 executor.clone(),
22046 )
22047 .await;
22048
22049 if matches.len() >= MAX_RESULTS {
22050 is_incomplete = true;
22051 }
22052
22053 // Remove all candidates where the query's start does not match the start of any word in the candidate
22054 if let Some(query_start) = last_word.chars().next() {
22055 matches.retain(|string_match| {
22056 split_words(&string_match.string).any(|word| {
22057 // Check that the first codepoint of the word as lowercase matches the first
22058 // codepoint of the query as lowercase
22059 word.chars()
22060 .flat_map(|codepoint| codepoint.to_lowercase())
22061 .zip(query_start.to_lowercase())
22062 .all(|(word_cp, query_cp)| word_cp == query_cp)
22063 })
22064 });
22065 }
22066
22067 let matched_strings = matches
22068 .into_iter()
22069 .map(|m| m.string)
22070 .collect::<HashSet<_>>();
22071
22072 completions.extend(snippets.iter().filter_map(|snippet| {
22073 let matching_prefix = snippet
22074 .prefix
22075 .iter()
22076 .find(|prefix| matched_strings.contains(*prefix))?;
22077 let start = as_offset - last_word.len();
22078 let start = snapshot.anchor_before(start);
22079 let range = start..buffer_position;
22080 let lsp_start = to_lsp(&start);
22081 let lsp_range = lsp::Range {
22082 start: lsp_start,
22083 end: lsp_end,
22084 };
22085 Some(Completion {
22086 replace_range: range,
22087 new_text: snippet.body.clone(),
22088 source: CompletionSource::Lsp {
22089 insert_range: None,
22090 server_id: LanguageServerId(usize::MAX),
22091 resolved: true,
22092 lsp_completion: Box::new(lsp::CompletionItem {
22093 label: snippet.prefix.first().unwrap().clone(),
22094 kind: Some(CompletionItemKind::SNIPPET),
22095 label_details: snippet.description.as_ref().map(|description| {
22096 lsp::CompletionItemLabelDetails {
22097 detail: Some(description.clone()),
22098 description: None,
22099 }
22100 }),
22101 insert_text_format: Some(InsertTextFormat::SNIPPET),
22102 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22103 lsp::InsertReplaceEdit {
22104 new_text: snippet.body.clone(),
22105 insert: lsp_range,
22106 replace: lsp_range,
22107 },
22108 )),
22109 filter_text: Some(snippet.body.clone()),
22110 sort_text: Some(char::MAX.to_string()),
22111 ..lsp::CompletionItem::default()
22112 }),
22113 lsp_defaults: None,
22114 },
22115 label: CodeLabel {
22116 text: matching_prefix.clone(),
22117 runs: Vec::new(),
22118 filter_range: 0..matching_prefix.len(),
22119 },
22120 icon_path: None,
22121 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22122 single_line: snippet.name.clone().into(),
22123 plain_text: snippet
22124 .description
22125 .clone()
22126 .map(|description| description.into()),
22127 }),
22128 insert_text_mode: None,
22129 confirm: None,
22130 })
22131 }))
22132 }
22133
22134 Ok(CompletionResponse {
22135 completions,
22136 is_incomplete,
22137 })
22138 })
22139}
22140
22141impl CompletionProvider for Entity<Project> {
22142 fn completions(
22143 &self,
22144 _excerpt_id: ExcerptId,
22145 buffer: &Entity<Buffer>,
22146 buffer_position: text::Anchor,
22147 options: CompletionContext,
22148 _window: &mut Window,
22149 cx: &mut Context<Editor>,
22150 ) -> Task<Result<Vec<CompletionResponse>>> {
22151 self.update(cx, |project, cx| {
22152 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22153 let project_completions = project.completions(buffer, buffer_position, options, cx);
22154 cx.background_spawn(async move {
22155 let mut responses = project_completions.await?;
22156 let snippets = snippets.await?;
22157 if !snippets.completions.is_empty() {
22158 responses.push(snippets);
22159 }
22160 Ok(responses)
22161 })
22162 })
22163 }
22164
22165 fn resolve_completions(
22166 &self,
22167 buffer: Entity<Buffer>,
22168 completion_indices: Vec<usize>,
22169 completions: Rc<RefCell<Box<[Completion]>>>,
22170 cx: &mut Context<Editor>,
22171 ) -> Task<Result<bool>> {
22172 self.update(cx, |project, cx| {
22173 project.lsp_store().update(cx, |lsp_store, cx| {
22174 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22175 })
22176 })
22177 }
22178
22179 fn apply_additional_edits_for_completion(
22180 &self,
22181 buffer: Entity<Buffer>,
22182 completions: Rc<RefCell<Box<[Completion]>>>,
22183 completion_index: usize,
22184 push_to_history: bool,
22185 cx: &mut Context<Editor>,
22186 ) -> Task<Result<Option<language::Transaction>>> {
22187 self.update(cx, |project, cx| {
22188 project.lsp_store().update(cx, |lsp_store, cx| {
22189 lsp_store.apply_additional_edits_for_completion(
22190 buffer,
22191 completions,
22192 completion_index,
22193 push_to_history,
22194 cx,
22195 )
22196 })
22197 })
22198 }
22199
22200 fn is_completion_trigger(
22201 &self,
22202 buffer: &Entity<Buffer>,
22203 position: language::Anchor,
22204 text: &str,
22205 trigger_in_words: bool,
22206 menu_is_open: bool,
22207 cx: &mut Context<Editor>,
22208 ) -> bool {
22209 let mut chars = text.chars();
22210 let char = if let Some(char) = chars.next() {
22211 char
22212 } else {
22213 return false;
22214 };
22215 if chars.next().is_some() {
22216 return false;
22217 }
22218
22219 let buffer = buffer.read(cx);
22220 let snapshot = buffer.snapshot();
22221 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22222 return false;
22223 }
22224 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22225 if trigger_in_words && classifier.is_word(char) {
22226 return true;
22227 }
22228
22229 buffer.completion_triggers().contains(text)
22230 }
22231}
22232
22233impl SemanticsProvider for Entity<Project> {
22234 fn hover(
22235 &self,
22236 buffer: &Entity<Buffer>,
22237 position: text::Anchor,
22238 cx: &mut App,
22239 ) -> Option<Task<Vec<project::Hover>>> {
22240 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22241 }
22242
22243 fn document_highlights(
22244 &self,
22245 buffer: &Entity<Buffer>,
22246 position: text::Anchor,
22247 cx: &mut App,
22248 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22249 Some(self.update(cx, |project, cx| {
22250 project.document_highlights(buffer, position, cx)
22251 }))
22252 }
22253
22254 fn definitions(
22255 &self,
22256 buffer: &Entity<Buffer>,
22257 position: text::Anchor,
22258 kind: GotoDefinitionKind,
22259 cx: &mut App,
22260 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22261 Some(self.update(cx, |project, cx| match kind {
22262 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22263 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22264 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22265 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22266 }))
22267 }
22268
22269 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22270 self.update(cx, |project, cx| {
22271 if project
22272 .active_debug_session(cx)
22273 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22274 {
22275 return true;
22276 }
22277
22278 buffer.update(cx, |buffer, cx| {
22279 project.any_language_server_supports_inlay_hints(buffer, cx)
22280 })
22281 })
22282 }
22283
22284 fn inline_values(
22285 &self,
22286 buffer_handle: Entity<Buffer>,
22287 range: Range<text::Anchor>,
22288 cx: &mut App,
22289 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22290 self.update(cx, |project, cx| {
22291 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22292
22293 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22294 })
22295 }
22296
22297 fn inlay_hints(
22298 &self,
22299 buffer_handle: Entity<Buffer>,
22300 range: Range<text::Anchor>,
22301 cx: &mut App,
22302 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22303 Some(self.update(cx, |project, cx| {
22304 project.inlay_hints(buffer_handle, range, cx)
22305 }))
22306 }
22307
22308 fn resolve_inlay_hint(
22309 &self,
22310 hint: InlayHint,
22311 buffer_handle: Entity<Buffer>,
22312 server_id: LanguageServerId,
22313 cx: &mut App,
22314 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22315 Some(self.update(cx, |project, cx| {
22316 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22317 }))
22318 }
22319
22320 fn range_for_rename(
22321 &self,
22322 buffer: &Entity<Buffer>,
22323 position: text::Anchor,
22324 cx: &mut App,
22325 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22326 Some(self.update(cx, |project, cx| {
22327 let buffer = buffer.clone();
22328 let task = project.prepare_rename(buffer.clone(), position, cx);
22329 cx.spawn(async move |_, cx| {
22330 Ok(match task.await? {
22331 PrepareRenameResponse::Success(range) => Some(range),
22332 PrepareRenameResponse::InvalidPosition => None,
22333 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22334 // Fallback on using TreeSitter info to determine identifier range
22335 buffer.read_with(cx, |buffer, _| {
22336 let snapshot = buffer.snapshot();
22337 let (range, kind) = snapshot.surrounding_word(position, false);
22338 if kind != Some(CharKind::Word) {
22339 return None;
22340 }
22341 Some(
22342 snapshot.anchor_before(range.start)
22343 ..snapshot.anchor_after(range.end),
22344 )
22345 })?
22346 }
22347 })
22348 })
22349 }))
22350 }
22351
22352 fn perform_rename(
22353 &self,
22354 buffer: &Entity<Buffer>,
22355 position: text::Anchor,
22356 new_name: String,
22357 cx: &mut App,
22358 ) -> Option<Task<Result<ProjectTransaction>>> {
22359 Some(self.update(cx, |project, cx| {
22360 project.perform_rename(buffer.clone(), position, new_name, cx)
22361 }))
22362 }
22363}
22364
22365fn inlay_hint_settings(
22366 location: Anchor,
22367 snapshot: &MultiBufferSnapshot,
22368 cx: &mut Context<Editor>,
22369) -> InlayHintSettings {
22370 let file = snapshot.file_at(location);
22371 let language = snapshot.language_at(location).map(|l| l.name());
22372 language_settings(language, file, cx).inlay_hints
22373}
22374
22375fn consume_contiguous_rows(
22376 contiguous_row_selections: &mut Vec<Selection<Point>>,
22377 selection: &Selection<Point>,
22378 display_map: &DisplaySnapshot,
22379 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22380) -> (MultiBufferRow, MultiBufferRow) {
22381 contiguous_row_selections.push(selection.clone());
22382 let start_row = starting_row(selection, display_map);
22383 let mut end_row = ending_row(selection, display_map);
22384
22385 while let Some(next_selection) = selections.peek() {
22386 if next_selection.start.row <= end_row.0 {
22387 end_row = ending_row(next_selection, display_map);
22388 contiguous_row_selections.push(selections.next().unwrap().clone());
22389 } else {
22390 break;
22391 }
22392 }
22393 (start_row, end_row)
22394}
22395
22396fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22397 if selection.start.column > 0 {
22398 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22399 } else {
22400 MultiBufferRow(selection.start.row)
22401 }
22402}
22403
22404fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22405 if next_selection.end.column > 0 || next_selection.is_empty() {
22406 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22407 } else {
22408 MultiBufferRow(next_selection.end.row)
22409 }
22410}
22411
22412impl EditorSnapshot {
22413 pub fn remote_selections_in_range<'a>(
22414 &'a self,
22415 range: &'a Range<Anchor>,
22416 collaboration_hub: &dyn CollaborationHub,
22417 cx: &'a App,
22418 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22419 let participant_names = collaboration_hub.user_names(cx);
22420 let participant_indices = collaboration_hub.user_participant_indices(cx);
22421 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22422 let collaborators_by_replica_id = collaborators_by_peer_id
22423 .values()
22424 .map(|collaborator| (collaborator.replica_id, collaborator))
22425 .collect::<HashMap<_, _>>();
22426 self.buffer_snapshot
22427 .selections_in_range(range, false)
22428 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22429 if replica_id == AGENT_REPLICA_ID {
22430 Some(RemoteSelection {
22431 replica_id,
22432 selection,
22433 cursor_shape,
22434 line_mode,
22435 collaborator_id: CollaboratorId::Agent,
22436 user_name: Some("Agent".into()),
22437 color: cx.theme().players().agent(),
22438 })
22439 } else {
22440 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22441 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22442 let user_name = participant_names.get(&collaborator.user_id).cloned();
22443 Some(RemoteSelection {
22444 replica_id,
22445 selection,
22446 cursor_shape,
22447 line_mode,
22448 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22449 user_name,
22450 color: if let Some(index) = participant_index {
22451 cx.theme().players().color_for_participant(index.0)
22452 } else {
22453 cx.theme().players().absent()
22454 },
22455 })
22456 }
22457 })
22458 }
22459
22460 pub fn hunks_for_ranges(
22461 &self,
22462 ranges: impl IntoIterator<Item = Range<Point>>,
22463 ) -> Vec<MultiBufferDiffHunk> {
22464 let mut hunks = Vec::new();
22465 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22466 HashMap::default();
22467 for query_range in ranges {
22468 let query_rows =
22469 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22470 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22471 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22472 ) {
22473 // Include deleted hunks that are adjacent to the query range, because
22474 // otherwise they would be missed.
22475 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22476 if hunk.status().is_deleted() {
22477 intersects_range |= hunk.row_range.start == query_rows.end;
22478 intersects_range |= hunk.row_range.end == query_rows.start;
22479 }
22480 if intersects_range {
22481 if !processed_buffer_rows
22482 .entry(hunk.buffer_id)
22483 .or_default()
22484 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22485 {
22486 continue;
22487 }
22488 hunks.push(hunk);
22489 }
22490 }
22491 }
22492
22493 hunks
22494 }
22495
22496 fn display_diff_hunks_for_rows<'a>(
22497 &'a self,
22498 display_rows: Range<DisplayRow>,
22499 folded_buffers: &'a HashSet<BufferId>,
22500 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22501 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22502 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22503
22504 self.buffer_snapshot
22505 .diff_hunks_in_range(buffer_start..buffer_end)
22506 .filter_map(|hunk| {
22507 if folded_buffers.contains(&hunk.buffer_id) {
22508 return None;
22509 }
22510
22511 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22512 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22513
22514 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22515 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22516
22517 let display_hunk = if hunk_display_start.column() != 0 {
22518 DisplayDiffHunk::Folded {
22519 display_row: hunk_display_start.row(),
22520 }
22521 } else {
22522 let mut end_row = hunk_display_end.row();
22523 if hunk_display_end.column() > 0 {
22524 end_row.0 += 1;
22525 }
22526 let is_created_file = hunk.is_created_file();
22527 DisplayDiffHunk::Unfolded {
22528 status: hunk.status(),
22529 diff_base_byte_range: hunk.diff_base_byte_range,
22530 display_row_range: hunk_display_start.row()..end_row,
22531 multi_buffer_range: Anchor::range_in_buffer(
22532 hunk.excerpt_id,
22533 hunk.buffer_id,
22534 hunk.buffer_range,
22535 ),
22536 is_created_file,
22537 }
22538 };
22539
22540 Some(display_hunk)
22541 })
22542 }
22543
22544 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22545 self.display_snapshot.buffer_snapshot.language_at(position)
22546 }
22547
22548 pub fn is_focused(&self) -> bool {
22549 self.is_focused
22550 }
22551
22552 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22553 self.placeholder_text.as_ref()
22554 }
22555
22556 pub fn scroll_position(&self) -> gpui::Point<f32> {
22557 self.scroll_anchor.scroll_position(&self.display_snapshot)
22558 }
22559
22560 fn gutter_dimensions(
22561 &self,
22562 font_id: FontId,
22563 font_size: Pixels,
22564 max_line_number_width: Pixels,
22565 cx: &App,
22566 ) -> Option<GutterDimensions> {
22567 if !self.show_gutter {
22568 return None;
22569 }
22570
22571 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22572 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22573
22574 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22575 matches!(
22576 ProjectSettings::get_global(cx).git.git_gutter,
22577 Some(GitGutterSetting::TrackedFiles)
22578 )
22579 });
22580 let gutter_settings = EditorSettings::get_global(cx).gutter;
22581 let show_line_numbers = self
22582 .show_line_numbers
22583 .unwrap_or(gutter_settings.line_numbers);
22584 let line_gutter_width = if show_line_numbers {
22585 // Avoid flicker-like gutter resizes when the line number gains another digit by
22586 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22587 let min_width_for_number_on_gutter =
22588 ch_advance * gutter_settings.min_line_number_digits as f32;
22589 max_line_number_width.max(min_width_for_number_on_gutter)
22590 } else {
22591 0.0.into()
22592 };
22593
22594 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22595 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22596
22597 let git_blame_entries_width =
22598 self.git_blame_gutter_max_author_length
22599 .map(|max_author_length| {
22600 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22601 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22602
22603 /// The number of characters to dedicate to gaps and margins.
22604 const SPACING_WIDTH: usize = 4;
22605
22606 let max_char_count = max_author_length.min(renderer.max_author_length())
22607 + ::git::SHORT_SHA_LENGTH
22608 + MAX_RELATIVE_TIMESTAMP.len()
22609 + SPACING_WIDTH;
22610
22611 ch_advance * max_char_count
22612 });
22613
22614 let is_singleton = self.buffer_snapshot.is_singleton();
22615
22616 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22617 left_padding += if !is_singleton {
22618 ch_width * 4.0
22619 } else if show_runnables || show_breakpoints {
22620 ch_width * 3.0
22621 } else if show_git_gutter && show_line_numbers {
22622 ch_width * 2.0
22623 } else if show_git_gutter || show_line_numbers {
22624 ch_width
22625 } else {
22626 px(0.)
22627 };
22628
22629 let shows_folds = is_singleton && gutter_settings.folds;
22630
22631 let right_padding = if shows_folds && show_line_numbers {
22632 ch_width * 4.0
22633 } else if shows_folds || (!is_singleton && show_line_numbers) {
22634 ch_width * 3.0
22635 } else if show_line_numbers {
22636 ch_width
22637 } else {
22638 px(0.)
22639 };
22640
22641 Some(GutterDimensions {
22642 left_padding,
22643 right_padding,
22644 width: line_gutter_width + left_padding + right_padding,
22645 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22646 git_blame_entries_width,
22647 })
22648 }
22649
22650 pub fn render_crease_toggle(
22651 &self,
22652 buffer_row: MultiBufferRow,
22653 row_contains_cursor: bool,
22654 editor: Entity<Editor>,
22655 window: &mut Window,
22656 cx: &mut App,
22657 ) -> Option<AnyElement> {
22658 let folded = self.is_line_folded(buffer_row);
22659 let mut is_foldable = false;
22660
22661 if let Some(crease) = self
22662 .crease_snapshot
22663 .query_row(buffer_row, &self.buffer_snapshot)
22664 {
22665 is_foldable = true;
22666 match crease {
22667 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22668 if let Some(render_toggle) = render_toggle {
22669 let toggle_callback =
22670 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22671 if folded {
22672 editor.update(cx, |editor, cx| {
22673 editor.fold_at(buffer_row, window, cx)
22674 });
22675 } else {
22676 editor.update(cx, |editor, cx| {
22677 editor.unfold_at(buffer_row, window, cx)
22678 });
22679 }
22680 });
22681 return Some((render_toggle)(
22682 buffer_row,
22683 folded,
22684 toggle_callback,
22685 window,
22686 cx,
22687 ));
22688 }
22689 }
22690 }
22691 }
22692
22693 is_foldable |= self.starts_indent(buffer_row);
22694
22695 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22696 Some(
22697 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22698 .toggle_state(folded)
22699 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22700 if folded {
22701 this.unfold_at(buffer_row, window, cx);
22702 } else {
22703 this.fold_at(buffer_row, window, cx);
22704 }
22705 }))
22706 .into_any_element(),
22707 )
22708 } else {
22709 None
22710 }
22711 }
22712
22713 pub fn render_crease_trailer(
22714 &self,
22715 buffer_row: MultiBufferRow,
22716 window: &mut Window,
22717 cx: &mut App,
22718 ) -> Option<AnyElement> {
22719 let folded = self.is_line_folded(buffer_row);
22720 if let Crease::Inline { render_trailer, .. } = self
22721 .crease_snapshot
22722 .query_row(buffer_row, &self.buffer_snapshot)?
22723 {
22724 let render_trailer = render_trailer.as_ref()?;
22725 Some(render_trailer(buffer_row, folded, window, cx))
22726 } else {
22727 None
22728 }
22729 }
22730}
22731
22732impl Deref for EditorSnapshot {
22733 type Target = DisplaySnapshot;
22734
22735 fn deref(&self) -> &Self::Target {
22736 &self.display_snapshot
22737 }
22738}
22739
22740#[derive(Clone, Debug, PartialEq, Eq)]
22741pub enum EditorEvent {
22742 InputIgnored {
22743 text: Arc<str>,
22744 },
22745 InputHandled {
22746 utf16_range_to_replace: Option<Range<isize>>,
22747 text: Arc<str>,
22748 },
22749 ExcerptsAdded {
22750 buffer: Entity<Buffer>,
22751 predecessor: ExcerptId,
22752 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22753 },
22754 ExcerptsRemoved {
22755 ids: Vec<ExcerptId>,
22756 removed_buffer_ids: Vec<BufferId>,
22757 },
22758 BufferFoldToggled {
22759 ids: Vec<ExcerptId>,
22760 folded: bool,
22761 },
22762 ExcerptsEdited {
22763 ids: Vec<ExcerptId>,
22764 },
22765 ExcerptsExpanded {
22766 ids: Vec<ExcerptId>,
22767 },
22768 BufferEdited,
22769 Edited {
22770 transaction_id: clock::Lamport,
22771 },
22772 Reparsed(BufferId),
22773 Focused,
22774 FocusedIn,
22775 Blurred,
22776 DirtyChanged,
22777 Saved,
22778 TitleChanged,
22779 DiffBaseChanged,
22780 SelectionsChanged {
22781 local: bool,
22782 },
22783 ScrollPositionChanged {
22784 local: bool,
22785 autoscroll: bool,
22786 },
22787 Closed,
22788 TransactionUndone {
22789 transaction_id: clock::Lamport,
22790 },
22791 TransactionBegun {
22792 transaction_id: clock::Lamport,
22793 },
22794 Reloaded,
22795 CursorShapeChanged,
22796 PushedToNavHistory {
22797 anchor: Anchor,
22798 is_deactivate: bool,
22799 },
22800}
22801
22802impl EventEmitter<EditorEvent> for Editor {}
22803
22804impl Focusable for Editor {
22805 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22806 self.focus_handle.clone()
22807 }
22808}
22809
22810impl Render for Editor {
22811 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22812 let settings = ThemeSettings::get_global(cx);
22813
22814 let mut text_style = match self.mode {
22815 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22816 color: cx.theme().colors().editor_foreground,
22817 font_family: settings.ui_font.family.clone(),
22818 font_features: settings.ui_font.features.clone(),
22819 font_fallbacks: settings.ui_font.fallbacks.clone(),
22820 font_size: rems(0.875).into(),
22821 font_weight: settings.ui_font.weight,
22822 line_height: relative(settings.buffer_line_height.value()),
22823 ..Default::default()
22824 },
22825 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22826 color: cx.theme().colors().editor_foreground,
22827 font_family: settings.buffer_font.family.clone(),
22828 font_features: settings.buffer_font.features.clone(),
22829 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22830 font_size: settings.buffer_font_size(cx).into(),
22831 font_weight: settings.buffer_font.weight,
22832 line_height: relative(settings.buffer_line_height.value()),
22833 ..Default::default()
22834 },
22835 };
22836 if let Some(text_style_refinement) = &self.text_style_refinement {
22837 text_style.refine(text_style_refinement)
22838 }
22839
22840 let background = match self.mode {
22841 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22842 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22843 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22844 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22845 };
22846
22847 EditorElement::new(
22848 &cx.entity(),
22849 EditorStyle {
22850 background,
22851 border: cx.theme().colors().border,
22852 local_player: cx.theme().players().local(),
22853 text: text_style,
22854 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22855 syntax: cx.theme().syntax().clone(),
22856 status: cx.theme().status().clone(),
22857 inlay_hints_style: make_inlay_hints_style(cx),
22858 edit_prediction_styles: make_suggestion_styles(cx),
22859 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22860 show_underlines: self.diagnostics_enabled(),
22861 },
22862 )
22863 }
22864}
22865
22866impl EntityInputHandler for Editor {
22867 fn text_for_range(
22868 &mut self,
22869 range_utf16: Range<usize>,
22870 adjusted_range: &mut Option<Range<usize>>,
22871 _: &mut Window,
22872 cx: &mut Context<Self>,
22873 ) -> Option<String> {
22874 let snapshot = self.buffer.read(cx).read(cx);
22875 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22876 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22877 if (start.0..end.0) != range_utf16 {
22878 adjusted_range.replace(start.0..end.0);
22879 }
22880 Some(snapshot.text_for_range(start..end).collect())
22881 }
22882
22883 fn selected_text_range(
22884 &mut self,
22885 ignore_disabled_input: bool,
22886 _: &mut Window,
22887 cx: &mut Context<Self>,
22888 ) -> Option<UTF16Selection> {
22889 // Prevent the IME menu from appearing when holding down an alphabetic key
22890 // while input is disabled.
22891 if !ignore_disabled_input && !self.input_enabled {
22892 return None;
22893 }
22894
22895 let selection = self.selections.newest::<OffsetUtf16>(cx);
22896 let range = selection.range();
22897
22898 Some(UTF16Selection {
22899 range: range.start.0..range.end.0,
22900 reversed: selection.reversed,
22901 })
22902 }
22903
22904 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22905 let snapshot = self.buffer.read(cx).read(cx);
22906 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22907 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22908 }
22909
22910 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22911 self.clear_highlights::<InputComposition>(cx);
22912 self.ime_transaction.take();
22913 }
22914
22915 fn replace_text_in_range(
22916 &mut self,
22917 range_utf16: Option<Range<usize>>,
22918 text: &str,
22919 window: &mut Window,
22920 cx: &mut Context<Self>,
22921 ) {
22922 if !self.input_enabled {
22923 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22924 return;
22925 }
22926
22927 self.transact(window, cx, |this, window, cx| {
22928 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22929 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22930 Some(this.selection_replacement_ranges(range_utf16, cx))
22931 } else {
22932 this.marked_text_ranges(cx)
22933 };
22934
22935 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22936 let newest_selection_id = this.selections.newest_anchor().id;
22937 this.selections
22938 .all::<OffsetUtf16>(cx)
22939 .iter()
22940 .zip(ranges_to_replace.iter())
22941 .find_map(|(selection, range)| {
22942 if selection.id == newest_selection_id {
22943 Some(
22944 (range.start.0 as isize - selection.head().0 as isize)
22945 ..(range.end.0 as isize - selection.head().0 as isize),
22946 )
22947 } else {
22948 None
22949 }
22950 })
22951 });
22952
22953 cx.emit(EditorEvent::InputHandled {
22954 utf16_range_to_replace: range_to_replace,
22955 text: text.into(),
22956 });
22957
22958 if let Some(new_selected_ranges) = new_selected_ranges {
22959 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22960 selections.select_ranges(new_selected_ranges)
22961 });
22962 this.backspace(&Default::default(), window, cx);
22963 }
22964
22965 this.handle_input(text, window, cx);
22966 });
22967
22968 if let Some(transaction) = self.ime_transaction {
22969 self.buffer.update(cx, |buffer, cx| {
22970 buffer.group_until_transaction(transaction, cx);
22971 });
22972 }
22973
22974 self.unmark_text(window, cx);
22975 }
22976
22977 fn replace_and_mark_text_in_range(
22978 &mut self,
22979 range_utf16: Option<Range<usize>>,
22980 text: &str,
22981 new_selected_range_utf16: Option<Range<usize>>,
22982 window: &mut Window,
22983 cx: &mut Context<Self>,
22984 ) {
22985 if !self.input_enabled {
22986 return;
22987 }
22988
22989 let transaction = self.transact(window, cx, |this, window, cx| {
22990 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22991 let snapshot = this.buffer.read(cx).read(cx);
22992 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22993 for marked_range in &mut marked_ranges {
22994 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22995 marked_range.start.0 += relative_range_utf16.start;
22996 marked_range.start =
22997 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22998 marked_range.end =
22999 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23000 }
23001 }
23002 Some(marked_ranges)
23003 } else if let Some(range_utf16) = range_utf16 {
23004 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23005 Some(this.selection_replacement_ranges(range_utf16, cx))
23006 } else {
23007 None
23008 };
23009
23010 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23011 let newest_selection_id = this.selections.newest_anchor().id;
23012 this.selections
23013 .all::<OffsetUtf16>(cx)
23014 .iter()
23015 .zip(ranges_to_replace.iter())
23016 .find_map(|(selection, range)| {
23017 if selection.id == newest_selection_id {
23018 Some(
23019 (range.start.0 as isize - selection.head().0 as isize)
23020 ..(range.end.0 as isize - selection.head().0 as isize),
23021 )
23022 } else {
23023 None
23024 }
23025 })
23026 });
23027
23028 cx.emit(EditorEvent::InputHandled {
23029 utf16_range_to_replace: range_to_replace,
23030 text: text.into(),
23031 });
23032
23033 if let Some(ranges) = ranges_to_replace {
23034 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23035 s.select_ranges(ranges)
23036 });
23037 }
23038
23039 let marked_ranges = {
23040 let snapshot = this.buffer.read(cx).read(cx);
23041 this.selections
23042 .disjoint_anchors()
23043 .iter()
23044 .map(|selection| {
23045 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23046 })
23047 .collect::<Vec<_>>()
23048 };
23049
23050 if text.is_empty() {
23051 this.unmark_text(window, cx);
23052 } else {
23053 this.highlight_text::<InputComposition>(
23054 marked_ranges.clone(),
23055 HighlightStyle {
23056 underline: Some(UnderlineStyle {
23057 thickness: px(1.),
23058 color: None,
23059 wavy: false,
23060 }),
23061 ..Default::default()
23062 },
23063 cx,
23064 );
23065 }
23066
23067 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23068 let use_autoclose = this.use_autoclose;
23069 let use_auto_surround = this.use_auto_surround;
23070 this.set_use_autoclose(false);
23071 this.set_use_auto_surround(false);
23072 this.handle_input(text, window, cx);
23073 this.set_use_autoclose(use_autoclose);
23074 this.set_use_auto_surround(use_auto_surround);
23075
23076 if let Some(new_selected_range) = new_selected_range_utf16 {
23077 let snapshot = this.buffer.read(cx).read(cx);
23078 let new_selected_ranges = marked_ranges
23079 .into_iter()
23080 .map(|marked_range| {
23081 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23082 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23083 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23084 snapshot.clip_offset_utf16(new_start, Bias::Left)
23085 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23086 })
23087 .collect::<Vec<_>>();
23088
23089 drop(snapshot);
23090 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23091 selections.select_ranges(new_selected_ranges)
23092 });
23093 }
23094 });
23095
23096 self.ime_transaction = self.ime_transaction.or(transaction);
23097 if let Some(transaction) = self.ime_transaction {
23098 self.buffer.update(cx, |buffer, cx| {
23099 buffer.group_until_transaction(transaction, cx);
23100 });
23101 }
23102
23103 if self.text_highlights::<InputComposition>(cx).is_none() {
23104 self.ime_transaction.take();
23105 }
23106 }
23107
23108 fn bounds_for_range(
23109 &mut self,
23110 range_utf16: Range<usize>,
23111 element_bounds: gpui::Bounds<Pixels>,
23112 window: &mut Window,
23113 cx: &mut Context<Self>,
23114 ) -> Option<gpui::Bounds<Pixels>> {
23115 let text_layout_details = self.text_layout_details(window);
23116 let CharacterDimensions {
23117 em_width,
23118 em_advance,
23119 line_height,
23120 } = self.character_dimensions(window);
23121
23122 let snapshot = self.snapshot(window, cx);
23123 let scroll_position = snapshot.scroll_position();
23124 let scroll_left = scroll_position.x * em_advance;
23125
23126 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23127 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23128 + self.gutter_dimensions.full_width();
23129 let y = line_height * (start.row().as_f32() - scroll_position.y);
23130
23131 Some(Bounds {
23132 origin: element_bounds.origin + point(x, y),
23133 size: size(em_width, line_height),
23134 })
23135 }
23136
23137 fn character_index_for_point(
23138 &mut self,
23139 point: gpui::Point<Pixels>,
23140 _window: &mut Window,
23141 _cx: &mut Context<Self>,
23142 ) -> Option<usize> {
23143 let position_map = self.last_position_map.as_ref()?;
23144 if !position_map.text_hitbox.contains(&point) {
23145 return None;
23146 }
23147 let display_point = position_map.point_for_position(point).previous_valid;
23148 let anchor = position_map
23149 .snapshot
23150 .display_point_to_anchor(display_point, Bias::Left);
23151 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23152 Some(utf16_offset.0)
23153 }
23154}
23155
23156trait SelectionExt {
23157 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23158 fn spanned_rows(
23159 &self,
23160 include_end_if_at_line_start: bool,
23161 map: &DisplaySnapshot,
23162 ) -> Range<MultiBufferRow>;
23163}
23164
23165impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23166 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23167 let start = self
23168 .start
23169 .to_point(&map.buffer_snapshot)
23170 .to_display_point(map);
23171 let end = self
23172 .end
23173 .to_point(&map.buffer_snapshot)
23174 .to_display_point(map);
23175 if self.reversed {
23176 end..start
23177 } else {
23178 start..end
23179 }
23180 }
23181
23182 fn spanned_rows(
23183 &self,
23184 include_end_if_at_line_start: bool,
23185 map: &DisplaySnapshot,
23186 ) -> Range<MultiBufferRow> {
23187 let start = self.start.to_point(&map.buffer_snapshot);
23188 let mut end = self.end.to_point(&map.buffer_snapshot);
23189 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23190 end.row -= 1;
23191 }
23192
23193 let buffer_start = map.prev_line_boundary(start).0;
23194 let buffer_end = map.next_line_boundary(end).0;
23195 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23196 }
23197}
23198
23199impl<T: InvalidationRegion> InvalidationStack<T> {
23200 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23201 where
23202 S: Clone + ToOffset,
23203 {
23204 while let Some(region) = self.last() {
23205 let all_selections_inside_invalidation_ranges =
23206 if selections.len() == region.ranges().len() {
23207 selections
23208 .iter()
23209 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23210 .all(|(selection, invalidation_range)| {
23211 let head = selection.head().to_offset(buffer);
23212 invalidation_range.start <= head && invalidation_range.end >= head
23213 })
23214 } else {
23215 false
23216 };
23217
23218 if all_selections_inside_invalidation_ranges {
23219 break;
23220 } else {
23221 self.pop();
23222 }
23223 }
23224 }
23225}
23226
23227impl<T> Default for InvalidationStack<T> {
23228 fn default() -> Self {
23229 Self(Default::default())
23230 }
23231}
23232
23233impl<T> Deref for InvalidationStack<T> {
23234 type Target = Vec<T>;
23235
23236 fn deref(&self) -> &Self::Target {
23237 &self.0
23238 }
23239}
23240
23241impl<T> DerefMut for InvalidationStack<T> {
23242 fn deref_mut(&mut self) -> &mut Self::Target {
23243 &mut self.0
23244 }
23245}
23246
23247impl InvalidationRegion for SnippetState {
23248 fn ranges(&self) -> &[Range<Anchor>] {
23249 &self.ranges[self.active_index]
23250 }
23251}
23252
23253fn edit_prediction_edit_text(
23254 current_snapshot: &BufferSnapshot,
23255 edits: &[(Range<Anchor>, String)],
23256 edit_preview: &EditPreview,
23257 include_deletions: bool,
23258 cx: &App,
23259) -> HighlightedText {
23260 let edits = edits
23261 .iter()
23262 .map(|(anchor, text)| {
23263 (
23264 anchor.start.text_anchor..anchor.end.text_anchor,
23265 text.clone(),
23266 )
23267 })
23268 .collect::<Vec<_>>();
23269
23270 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23271}
23272
23273pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23274 match severity {
23275 lsp::DiagnosticSeverity::ERROR => colors.error,
23276 lsp::DiagnosticSeverity::WARNING => colors.warning,
23277 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23278 lsp::DiagnosticSeverity::HINT => colors.info,
23279 _ => colors.ignored,
23280 }
23281}
23282
23283pub fn styled_runs_for_code_label<'a>(
23284 label: &'a CodeLabel,
23285 syntax_theme: &'a theme::SyntaxTheme,
23286) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23287 let fade_out = HighlightStyle {
23288 fade_out: Some(0.35),
23289 ..Default::default()
23290 };
23291
23292 let mut prev_end = label.filter_range.end;
23293 label
23294 .runs
23295 .iter()
23296 .enumerate()
23297 .flat_map(move |(ix, (range, highlight_id))| {
23298 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23299 style
23300 } else {
23301 return Default::default();
23302 };
23303 let mut muted_style = style;
23304 muted_style.highlight(fade_out);
23305
23306 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23307 if range.start >= label.filter_range.end {
23308 if range.start > prev_end {
23309 runs.push((prev_end..range.start, fade_out));
23310 }
23311 runs.push((range.clone(), muted_style));
23312 } else if range.end <= label.filter_range.end {
23313 runs.push((range.clone(), style));
23314 } else {
23315 runs.push((range.start..label.filter_range.end, style));
23316 runs.push((label.filter_range.end..range.end, muted_style));
23317 }
23318 prev_end = cmp::max(prev_end, range.end);
23319
23320 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23321 runs.push((prev_end..label.text.len(), fade_out));
23322 }
23323
23324 runs
23325 })
23326}
23327
23328pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23329 let mut prev_index = 0;
23330 let mut prev_codepoint: Option<char> = None;
23331 text.char_indices()
23332 .chain([(text.len(), '\0')])
23333 .filter_map(move |(index, codepoint)| {
23334 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23335 let is_boundary = index == text.len()
23336 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23337 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23338 if is_boundary {
23339 let chunk = &text[prev_index..index];
23340 prev_index = index;
23341 Some(chunk)
23342 } else {
23343 None
23344 }
23345 })
23346}
23347
23348pub trait RangeToAnchorExt: Sized {
23349 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23350
23351 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23352 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23353 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23354 }
23355}
23356
23357impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23358 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23359 let start_offset = self.start.to_offset(snapshot);
23360 let end_offset = self.end.to_offset(snapshot);
23361 if start_offset == end_offset {
23362 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23363 } else {
23364 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23365 }
23366 }
23367}
23368
23369pub trait RowExt {
23370 fn as_f32(&self) -> f32;
23371
23372 fn next_row(&self) -> Self;
23373
23374 fn previous_row(&self) -> Self;
23375
23376 fn minus(&self, other: Self) -> u32;
23377}
23378
23379impl RowExt for DisplayRow {
23380 fn as_f32(&self) -> f32 {
23381 self.0 as f32
23382 }
23383
23384 fn next_row(&self) -> Self {
23385 Self(self.0 + 1)
23386 }
23387
23388 fn previous_row(&self) -> Self {
23389 Self(self.0.saturating_sub(1))
23390 }
23391
23392 fn minus(&self, other: Self) -> u32 {
23393 self.0 - other.0
23394 }
23395}
23396
23397impl RowExt for MultiBufferRow {
23398 fn as_f32(&self) -> f32 {
23399 self.0 as f32
23400 }
23401
23402 fn next_row(&self) -> Self {
23403 Self(self.0 + 1)
23404 }
23405
23406 fn previous_row(&self) -> Self {
23407 Self(self.0.saturating_sub(1))
23408 }
23409
23410 fn minus(&self, other: Self) -> u32 {
23411 self.0 - other.0
23412 }
23413}
23414
23415trait RowRangeExt {
23416 type Row;
23417
23418 fn len(&self) -> usize;
23419
23420 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23421}
23422
23423impl RowRangeExt for Range<MultiBufferRow> {
23424 type Row = MultiBufferRow;
23425
23426 fn len(&self) -> usize {
23427 (self.end.0 - self.start.0) as usize
23428 }
23429
23430 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23431 (self.start.0..self.end.0).map(MultiBufferRow)
23432 }
23433}
23434
23435impl RowRangeExt for Range<DisplayRow> {
23436 type Row = DisplayRow;
23437
23438 fn len(&self) -> usize {
23439 (self.end.0 - self.start.0) as usize
23440 }
23441
23442 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23443 (self.start.0..self.end.0).map(DisplayRow)
23444 }
23445}
23446
23447/// If select range has more than one line, we
23448/// just point the cursor to range.start.
23449fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23450 if range.start.row == range.end.row {
23451 range
23452 } else {
23453 range.start..range.start
23454 }
23455}
23456pub struct KillRing(ClipboardItem);
23457impl Global for KillRing {}
23458
23459const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23460
23461enum BreakpointPromptEditAction {
23462 Log,
23463 Condition,
23464 HitCondition,
23465}
23466
23467struct BreakpointPromptEditor {
23468 pub(crate) prompt: Entity<Editor>,
23469 editor: WeakEntity<Editor>,
23470 breakpoint_anchor: Anchor,
23471 breakpoint: Breakpoint,
23472 edit_action: BreakpointPromptEditAction,
23473 block_ids: HashSet<CustomBlockId>,
23474 editor_margins: Arc<Mutex<EditorMargins>>,
23475 _subscriptions: Vec<Subscription>,
23476}
23477
23478impl BreakpointPromptEditor {
23479 const MAX_LINES: u8 = 4;
23480
23481 fn new(
23482 editor: WeakEntity<Editor>,
23483 breakpoint_anchor: Anchor,
23484 breakpoint: Breakpoint,
23485 edit_action: BreakpointPromptEditAction,
23486 window: &mut Window,
23487 cx: &mut Context<Self>,
23488 ) -> Self {
23489 let base_text = match edit_action {
23490 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23491 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23492 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23493 }
23494 .map(|msg| msg.to_string())
23495 .unwrap_or_default();
23496
23497 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23498 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23499
23500 let prompt = cx.new(|cx| {
23501 let mut prompt = Editor::new(
23502 EditorMode::AutoHeight {
23503 min_lines: 1,
23504 max_lines: Some(Self::MAX_LINES as usize),
23505 },
23506 buffer,
23507 None,
23508 window,
23509 cx,
23510 );
23511 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23512 prompt.set_show_cursor_when_unfocused(false, cx);
23513 prompt.set_placeholder_text(
23514 match edit_action {
23515 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23516 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23517 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23518 },
23519 cx,
23520 );
23521
23522 prompt
23523 });
23524
23525 Self {
23526 prompt,
23527 editor,
23528 breakpoint_anchor,
23529 breakpoint,
23530 edit_action,
23531 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23532 block_ids: Default::default(),
23533 _subscriptions: vec![],
23534 }
23535 }
23536
23537 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23538 self.block_ids.extend(block_ids)
23539 }
23540
23541 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23542 if let Some(editor) = self.editor.upgrade() {
23543 let message = self
23544 .prompt
23545 .read(cx)
23546 .buffer
23547 .read(cx)
23548 .as_singleton()
23549 .expect("A multi buffer in breakpoint prompt isn't possible")
23550 .read(cx)
23551 .as_rope()
23552 .to_string();
23553
23554 editor.update(cx, |editor, cx| {
23555 editor.edit_breakpoint_at_anchor(
23556 self.breakpoint_anchor,
23557 self.breakpoint.clone(),
23558 match self.edit_action {
23559 BreakpointPromptEditAction::Log => {
23560 BreakpointEditAction::EditLogMessage(message.into())
23561 }
23562 BreakpointPromptEditAction::Condition => {
23563 BreakpointEditAction::EditCondition(message.into())
23564 }
23565 BreakpointPromptEditAction::HitCondition => {
23566 BreakpointEditAction::EditHitCondition(message.into())
23567 }
23568 },
23569 cx,
23570 );
23571
23572 editor.remove_blocks(self.block_ids.clone(), None, cx);
23573 cx.focus_self(window);
23574 });
23575 }
23576 }
23577
23578 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23579 self.editor
23580 .update(cx, |editor, cx| {
23581 editor.remove_blocks(self.block_ids.clone(), None, cx);
23582 window.focus(&editor.focus_handle);
23583 })
23584 .log_err();
23585 }
23586
23587 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23588 let settings = ThemeSettings::get_global(cx);
23589 let text_style = TextStyle {
23590 color: if self.prompt.read(cx).read_only(cx) {
23591 cx.theme().colors().text_disabled
23592 } else {
23593 cx.theme().colors().text
23594 },
23595 font_family: settings.buffer_font.family.clone(),
23596 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23597 font_size: settings.buffer_font_size(cx).into(),
23598 font_weight: settings.buffer_font.weight,
23599 line_height: relative(settings.buffer_line_height.value()),
23600 ..Default::default()
23601 };
23602 EditorElement::new(
23603 &self.prompt,
23604 EditorStyle {
23605 background: cx.theme().colors().editor_background,
23606 local_player: cx.theme().players().local(),
23607 text: text_style,
23608 ..Default::default()
23609 },
23610 )
23611 }
23612}
23613
23614impl Render for BreakpointPromptEditor {
23615 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23616 let editor_margins = *self.editor_margins.lock();
23617 let gutter_dimensions = editor_margins.gutter;
23618 h_flex()
23619 .key_context("Editor")
23620 .bg(cx.theme().colors().editor_background)
23621 .border_y_1()
23622 .border_color(cx.theme().status().info_border)
23623 .size_full()
23624 .py(window.line_height() / 2.5)
23625 .on_action(cx.listener(Self::confirm))
23626 .on_action(cx.listener(Self::cancel))
23627 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23628 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23629 }
23630}
23631
23632impl Focusable for BreakpointPromptEditor {
23633 fn focus_handle(&self, cx: &App) -> FocusHandle {
23634 self.prompt.focus_handle(cx)
23635 }
23636}
23637
23638fn all_edits_insertions_or_deletions(
23639 edits: &Vec<(Range<Anchor>, String)>,
23640 snapshot: &MultiBufferSnapshot,
23641) -> bool {
23642 let mut all_insertions = true;
23643 let mut all_deletions = true;
23644
23645 for (range, new_text) in edits.iter() {
23646 let range_is_empty = range.to_offset(&snapshot).is_empty();
23647 let text_is_empty = new_text.is_empty();
23648
23649 if range_is_empty != text_is_empty {
23650 if range_is_empty {
23651 all_deletions = false;
23652 } else {
23653 all_insertions = false;
23654 }
23655 } else {
23656 return false;
23657 }
23658
23659 if !all_insertions && !all_deletions {
23660 return false;
23661 }
23662 }
23663 all_insertions || all_deletions
23664}
23665
23666struct MissingEditPredictionKeybindingTooltip;
23667
23668impl Render for MissingEditPredictionKeybindingTooltip {
23669 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23670 ui::tooltip_container(window, cx, |container, _, cx| {
23671 container
23672 .flex_shrink_0()
23673 .max_w_80()
23674 .min_h(rems_from_px(124.))
23675 .justify_between()
23676 .child(
23677 v_flex()
23678 .flex_1()
23679 .text_ui_sm(cx)
23680 .child(Label::new("Conflict with Accept Keybinding"))
23681 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23682 )
23683 .child(
23684 h_flex()
23685 .pb_1()
23686 .gap_1()
23687 .items_end()
23688 .w_full()
23689 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23690 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23691 }))
23692 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23693 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23694 })),
23695 )
23696 })
23697 }
23698}
23699
23700#[derive(Debug, Clone, Copy, PartialEq)]
23701pub struct LineHighlight {
23702 pub background: Background,
23703 pub border: Option<gpui::Hsla>,
23704 pub include_gutter: bool,
23705 pub type_id: Option<TypeId>,
23706}
23707
23708struct LineManipulationResult {
23709 pub new_text: String,
23710 pub line_count_before: usize,
23711 pub line_count_after: usize,
23712}
23713
23714fn render_diff_hunk_controls(
23715 row: u32,
23716 status: &DiffHunkStatus,
23717 hunk_range: Range<Anchor>,
23718 is_created_file: bool,
23719 line_height: Pixels,
23720 editor: &Entity<Editor>,
23721 _window: &mut Window,
23722 cx: &mut App,
23723) -> AnyElement {
23724 h_flex()
23725 .h(line_height)
23726 .mr_1()
23727 .gap_1()
23728 .px_0p5()
23729 .pb_1()
23730 .border_x_1()
23731 .border_b_1()
23732 .border_color(cx.theme().colors().border_variant)
23733 .rounded_b_lg()
23734 .bg(cx.theme().colors().editor_background)
23735 .gap_1()
23736 .block_mouse_except_scroll()
23737 .shadow_md()
23738 .child(if status.has_secondary_hunk() {
23739 Button::new(("stage", row as u64), "Stage")
23740 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23741 .tooltip({
23742 let focus_handle = editor.focus_handle(cx);
23743 move |window, cx| {
23744 Tooltip::for_action_in(
23745 "Stage Hunk",
23746 &::git::ToggleStaged,
23747 &focus_handle,
23748 window,
23749 cx,
23750 )
23751 }
23752 })
23753 .on_click({
23754 let editor = editor.clone();
23755 move |_event, _window, cx| {
23756 editor.update(cx, |editor, cx| {
23757 editor.stage_or_unstage_diff_hunks(
23758 true,
23759 vec![hunk_range.start..hunk_range.start],
23760 cx,
23761 );
23762 });
23763 }
23764 })
23765 } else {
23766 Button::new(("unstage", row as u64), "Unstage")
23767 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23768 .tooltip({
23769 let focus_handle = editor.focus_handle(cx);
23770 move |window, cx| {
23771 Tooltip::for_action_in(
23772 "Unstage Hunk",
23773 &::git::ToggleStaged,
23774 &focus_handle,
23775 window,
23776 cx,
23777 )
23778 }
23779 })
23780 .on_click({
23781 let editor = editor.clone();
23782 move |_event, _window, cx| {
23783 editor.update(cx, |editor, cx| {
23784 editor.stage_or_unstage_diff_hunks(
23785 false,
23786 vec![hunk_range.start..hunk_range.start],
23787 cx,
23788 );
23789 });
23790 }
23791 })
23792 })
23793 .child(
23794 Button::new(("restore", row as u64), "Restore")
23795 .tooltip({
23796 let focus_handle = editor.focus_handle(cx);
23797 move |window, cx| {
23798 Tooltip::for_action_in(
23799 "Restore Hunk",
23800 &::git::Restore,
23801 &focus_handle,
23802 window,
23803 cx,
23804 )
23805 }
23806 })
23807 .on_click({
23808 let editor = editor.clone();
23809 move |_event, window, cx| {
23810 editor.update(cx, |editor, cx| {
23811 let snapshot = editor.snapshot(window, cx);
23812 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23813 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23814 });
23815 }
23816 })
23817 .disabled(is_created_file),
23818 )
23819 .when(
23820 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23821 |el| {
23822 el.child(
23823 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23824 .shape(IconButtonShape::Square)
23825 .icon_size(IconSize::Small)
23826 // .disabled(!has_multiple_hunks)
23827 .tooltip({
23828 let focus_handle = editor.focus_handle(cx);
23829 move |window, cx| {
23830 Tooltip::for_action_in(
23831 "Next Hunk",
23832 &GoToHunk,
23833 &focus_handle,
23834 window,
23835 cx,
23836 )
23837 }
23838 })
23839 .on_click({
23840 let editor = editor.clone();
23841 move |_event, window, cx| {
23842 editor.update(cx, |editor, cx| {
23843 let snapshot = editor.snapshot(window, cx);
23844 let position =
23845 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23846 editor.go_to_hunk_before_or_after_position(
23847 &snapshot,
23848 position,
23849 Direction::Next,
23850 window,
23851 cx,
23852 );
23853 editor.expand_selected_diff_hunks(cx);
23854 });
23855 }
23856 }),
23857 )
23858 .child(
23859 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23860 .shape(IconButtonShape::Square)
23861 .icon_size(IconSize::Small)
23862 // .disabled(!has_multiple_hunks)
23863 .tooltip({
23864 let focus_handle = editor.focus_handle(cx);
23865 move |window, cx| {
23866 Tooltip::for_action_in(
23867 "Previous Hunk",
23868 &GoToPreviousHunk,
23869 &focus_handle,
23870 window,
23871 cx,
23872 )
23873 }
23874 })
23875 .on_click({
23876 let editor = editor.clone();
23877 move |_event, window, cx| {
23878 editor.update(cx, |editor, cx| {
23879 let snapshot = editor.snapshot(window, cx);
23880 let point =
23881 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23882 editor.go_to_hunk_before_or_after_position(
23883 &snapshot,
23884 point,
23885 Direction::Prev,
23886 window,
23887 cx,
23888 );
23889 editor.expand_selected_diff_hunks(cx);
23890 });
23891 }
23892 }),
23893 )
23894 },
23895 )
23896 .into_any_element()
23897}