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_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt,
78 future::{self, Shared, join},
79};
80use fuzzy::{StringMatch, StringMatchCandidate};
81
82use ::git::blame::BlameEntry;
83use ::git::{Restore, blame::ParsedCommitMessage};
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use git::blame::{GitBlame, GlobalBlameRenderer};
89use gpui::{
90 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
91 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
92 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
93 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
94 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
95 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
96 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
97 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
98};
99use highlight_matching_bracket::refresh_matching_bracket_highlights;
100use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
101pub use hover_popover::hover_markdown_style;
102use hover_popover::{HoverState, hide_hover};
103use indent_guides::ActiveIndentGuidesState;
104use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
105pub use inline_completion::Direction;
106use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
107pub use items::MAX_TAB_TITLE_LEN;
108use itertools::Itertools;
109use language::{
110 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
111 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
112 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
113 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
114 language_settings::{
115 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
116 all_language_settings, language_settings,
117 },
118 point_from_lsp, text_diff_with_options,
119};
120use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
121use linked_editing_ranges::refresh_linked_ranges;
122use markdown::Markdown;
123use mouse_context_menu::MouseContextMenu;
124use persistence::DB;
125use project::{
126 BreakpointWithPosition, CompletionResponse, ProjectPath,
127 debugger::{
128 breakpoint_store::{
129 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
130 BreakpointStoreEvent,
131 },
132 session::{Session, SessionEvent},
133 },
134 project_settings::DiagnosticSeverity,
135};
136
137pub use git::blame::BlameRenderer;
138pub use proposed_changes_editor::{
139 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
140};
141use std::{cell::OnceCell, iter::Peekable, ops::Not};
142use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
143
144pub use lsp::CompletionContext;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId, LanguageServerName,
148};
149
150use language::BufferSnapshot;
151pub use lsp_ext::lsp_tasks;
152use movement::TextLayoutDetails;
153pub use multi_buffer::{
154 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
155 RowInfo, ToOffset, ToPoint,
156};
157use multi_buffer::{
158 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
159 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
160};
161use parking_lot::Mutex;
162use project::{
163 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
164 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
165 TaskSourceKind,
166 debugger::breakpoint_store::Breakpoint,
167 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
168 project_settings::{GitGutterSetting, ProjectSettings},
169};
170use rand::prelude::*;
171use rpc::{ErrorExt, proto::*};
172use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
173use selections_collection::{
174 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
175};
176use serde::{Deserialize, Serialize};
177use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
178use smallvec::{SmallVec, smallvec};
179use snippet::Snippet;
180use std::sync::Arc;
181use std::{
182 any::TypeId,
183 borrow::Cow,
184 cell::RefCell,
185 cmp::{self, Ordering, Reverse},
186 mem,
187 num::NonZeroU32,
188 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
189 path::{Path, PathBuf},
190 rc::Rc,
191 time::{Duration, Instant},
192};
193pub use sum_tree::Bias;
194use sum_tree::TreeMap;
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
196use theme::{
197 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
205use workspace::{
206 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
207 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
208 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
209 item::{ItemHandle, PreviewTabsSettings},
210 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
211 searchable::SearchEvent,
212};
213
214use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
215use crate::{
216 code_context_menus::CompletionsMenuSource,
217 hover_links::{find_url, find_url_from_range},
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 MIN_LINE_NUMBER_DIGITS: u32 = 4;
239pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
240
241pub type RenderDiffHunkControlsFn = Arc<
242 dyn Fn(
243 u32,
244 &DiffHunkStatus,
245 Range<Anchor>,
246 bool,
247 Pixels,
248 &Entity<Editor>,
249 &mut Window,
250 &mut App,
251 ) -> AnyElement,
252>;
253
254const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
255 alt: true,
256 shift: true,
257 control: false,
258 platform: false,
259 function: false,
260};
261
262struct InlineValueCache {
263 enabled: bool,
264 inlays: Vec<InlayId>,
265 refresh_task: Task<Option<()>>,
266}
267
268impl InlineValueCache {
269 fn new(enabled: bool) -> Self {
270 Self {
271 enabled,
272 inlays: Vec::new(),
273 refresh_task: Task::ready(None),
274 }
275 }
276}
277
278#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
279pub enum InlayId {
280 InlineCompletion(usize),
281 Hint(usize),
282 DebuggerValue(usize),
283}
284
285impl InlayId {
286 fn id(&self) -> usize {
287 match self {
288 Self::InlineCompletion(id) => *id,
289 Self::Hint(id) => *id,
290 Self::DebuggerValue(id) => *id,
291 }
292 }
293}
294
295pub enum ActiveDebugLine {}
296pub enum DebugStackFrameLine {}
297enum DocumentHighlightRead {}
298enum DocumentHighlightWrite {}
299enum InputComposition {}
300enum SelectedTextHighlight {}
301
302pub enum ConflictsOuter {}
303pub enum ConflictsOurs {}
304pub enum ConflictsTheirs {}
305pub enum ConflictsOursMarker {}
306pub enum ConflictsTheirsMarker {}
307
308#[derive(Debug, Copy, Clone, PartialEq, Eq)]
309pub enum Navigated {
310 Yes,
311 No,
312}
313
314impl Navigated {
315 pub fn from_bool(yes: bool) -> Navigated {
316 if yes { Navigated::Yes } else { Navigated::No }
317 }
318}
319
320#[derive(Debug, Clone, PartialEq, Eq)]
321enum DisplayDiffHunk {
322 Folded {
323 display_row: DisplayRow,
324 },
325 Unfolded {
326 is_created_file: bool,
327 diff_base_byte_range: Range<usize>,
328 display_row_range: Range<DisplayRow>,
329 multi_buffer_range: Range<Anchor>,
330 status: DiffHunkStatus,
331 },
332}
333
334pub enum HideMouseCursorOrigin {
335 TypingAction,
336 MovementAction,
337}
338
339pub fn init_settings(cx: &mut App) {
340 EditorSettings::register(cx);
341}
342
343pub fn init(cx: &mut App) {
344 init_settings(cx);
345
346 cx.set_global(GlobalBlameRenderer(Arc::new(())));
347
348 workspace::register_project_item::<Editor>(cx);
349 workspace::FollowableViewRegistry::register::<Editor>(cx);
350 workspace::register_serializable_item::<Editor>(cx);
351
352 cx.observe_new(
353 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
354 workspace.register_action(Editor::new_file);
355 workspace.register_action(Editor::new_file_vertical);
356 workspace.register_action(Editor::new_file_horizontal);
357 workspace.register_action(Editor::cancel_language_server_work);
358 },
359 )
360 .detach();
361
362 cx.on_action(move |_: &workspace::NewFile, cx| {
363 let app_state = workspace::AppState::global(cx);
364 if let Some(app_state) = app_state.upgrade() {
365 workspace::open_new(
366 Default::default(),
367 app_state,
368 cx,
369 |workspace, window, cx| {
370 Editor::new_file(workspace, &Default::default(), window, cx)
371 },
372 )
373 .detach();
374 }
375 });
376 cx.on_action(move |_: &workspace::NewWindow, cx| {
377 let app_state = workspace::AppState::global(cx);
378 if let Some(app_state) = app_state.upgrade() {
379 workspace::open_new(
380 Default::default(),
381 app_state,
382 cx,
383 |workspace, window, cx| {
384 cx.activate(true);
385 Editor::new_file(workspace, &Default::default(), window, cx)
386 },
387 )
388 .detach();
389 }
390 });
391}
392
393pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
394 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
395}
396
397pub trait DiagnosticRenderer {
398 fn render_group(
399 &self,
400 diagnostic_group: Vec<DiagnosticEntry<Point>>,
401 buffer_id: BufferId,
402 snapshot: EditorSnapshot,
403 editor: WeakEntity<Editor>,
404 cx: &mut App,
405 ) -> Vec<BlockProperties<Anchor>>;
406
407 fn render_hover(
408 &self,
409 diagnostic_group: Vec<DiagnosticEntry<Point>>,
410 range: Range<Point>,
411 buffer_id: BufferId,
412 cx: &mut App,
413 ) -> Option<Entity<markdown::Markdown>>;
414
415 fn open_link(
416 &self,
417 editor: &mut Editor,
418 link: SharedString,
419 window: &mut Window,
420 cx: &mut Context<Editor>,
421 );
422}
423
424pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
425
426impl GlobalDiagnosticRenderer {
427 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
428 cx.try_global::<Self>().map(|g| g.0.clone())
429 }
430}
431
432impl gpui::Global for GlobalDiagnosticRenderer {}
433pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
434 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
435}
436
437pub struct SearchWithinRange;
438
439trait InvalidationRegion {
440 fn ranges(&self) -> &[Range<Anchor>];
441}
442
443#[derive(Clone, Debug, PartialEq)]
444pub enum SelectPhase {
445 Begin {
446 position: DisplayPoint,
447 add: bool,
448 click_count: usize,
449 },
450 BeginColumnar {
451 position: DisplayPoint,
452 reset: bool,
453 goal_column: u32,
454 },
455 Extend {
456 position: DisplayPoint,
457 click_count: usize,
458 },
459 Update {
460 position: DisplayPoint,
461 goal_column: u32,
462 scroll_delta: gpui::Point<f32>,
463 },
464 End,
465}
466
467#[derive(Clone, Debug)]
468pub enum SelectMode {
469 Character,
470 Word(Range<Anchor>),
471 Line(Range<Anchor>),
472 All,
473}
474
475#[derive(Clone, PartialEq, Eq, Debug)]
476pub enum EditorMode {
477 SingleLine {
478 auto_width: bool,
479 },
480 AutoHeight {
481 max_lines: usize,
482 },
483 Full {
484 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
485 scale_ui_elements_with_buffer_font_size: bool,
486 /// When set to `true`, the editor will render a background for the active line.
487 show_active_line_background: bool,
488 /// When set to `true`, the editor's height will be determined by its content.
489 sized_by_content: bool,
490 },
491 Minimap {
492 parent: WeakEntity<Editor>,
493 },
494}
495
496impl EditorMode {
497 pub fn full() -> Self {
498 Self::Full {
499 scale_ui_elements_with_buffer_font_size: true,
500 show_active_line_background: true,
501 sized_by_content: false,
502 }
503 }
504
505 pub fn is_full(&self) -> bool {
506 matches!(self, Self::Full { .. })
507 }
508
509 fn is_minimap(&self) -> bool {
510 matches!(self, Self::Minimap { .. })
511 }
512}
513
514#[derive(Copy, Clone, Debug)]
515pub enum SoftWrap {
516 /// Prefer not to wrap at all.
517 ///
518 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
519 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
520 GitDiff,
521 /// Prefer a single line generally, unless an overly long line is encountered.
522 None,
523 /// Soft wrap lines that exceed the editor width.
524 EditorWidth,
525 /// Soft wrap lines at the preferred line length.
526 Column(u32),
527 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
528 Bounded(u32),
529}
530
531#[derive(Clone)]
532pub struct EditorStyle {
533 pub background: Hsla,
534 pub local_player: PlayerColor,
535 pub text: TextStyle,
536 pub scrollbar_width: Pixels,
537 pub syntax: Arc<SyntaxTheme>,
538 pub status: StatusColors,
539 pub inlay_hints_style: HighlightStyle,
540 pub inline_completion_styles: InlineCompletionStyles,
541 pub unnecessary_code_fade: f32,
542 pub show_underlines: bool,
543}
544
545impl Default for EditorStyle {
546 fn default() -> Self {
547 Self {
548 background: Hsla::default(),
549 local_player: PlayerColor::default(),
550 text: TextStyle::default(),
551 scrollbar_width: Pixels::default(),
552 syntax: Default::default(),
553 // HACK: Status colors don't have a real default.
554 // We should look into removing the status colors from the editor
555 // style and retrieve them directly from the theme.
556 status: StatusColors::dark(),
557 inlay_hints_style: HighlightStyle::default(),
558 inline_completion_styles: InlineCompletionStyles {
559 insertion: HighlightStyle::default(),
560 whitespace: HighlightStyle::default(),
561 },
562 unnecessary_code_fade: Default::default(),
563 show_underlines: true,
564 }
565 }
566}
567
568pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
569 let show_background = language_settings::language_settings(None, None, cx)
570 .inlay_hints
571 .show_background;
572
573 HighlightStyle {
574 color: Some(cx.theme().status().hint),
575 background_color: show_background.then(|| cx.theme().status().hint_background),
576 ..HighlightStyle::default()
577 }
578}
579
580pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
581 InlineCompletionStyles {
582 insertion: HighlightStyle {
583 color: Some(cx.theme().status().predictive),
584 ..HighlightStyle::default()
585 },
586 whitespace: HighlightStyle {
587 background_color: Some(cx.theme().status().created_background),
588 ..HighlightStyle::default()
589 },
590 }
591}
592
593type CompletionId = usize;
594
595pub(crate) enum EditDisplayMode {
596 TabAccept,
597 DiffPopover,
598 Inline,
599}
600
601enum InlineCompletion {
602 Edit {
603 edits: Vec<(Range<Anchor>, String)>,
604 edit_preview: Option<EditPreview>,
605 display_mode: EditDisplayMode,
606 snapshot: BufferSnapshot,
607 },
608 Move {
609 target: Anchor,
610 snapshot: BufferSnapshot,
611 },
612}
613
614struct InlineCompletionState {
615 inlay_ids: Vec<InlayId>,
616 completion: InlineCompletion,
617 completion_id: Option<SharedString>,
618 invalidation_range: Range<Anchor>,
619}
620
621enum EditPredictionSettings {
622 Disabled,
623 Enabled {
624 show_in_menu: bool,
625 preview_requires_modifier: bool,
626 },
627}
628
629enum InlineCompletionHighlight {}
630
631#[derive(Debug, Clone)]
632struct InlineDiagnostic {
633 message: SharedString,
634 group_id: usize,
635 is_primary: bool,
636 start: Point,
637 severity: lsp::DiagnosticSeverity,
638}
639
640pub enum MenuInlineCompletionsPolicy {
641 Never,
642 ByProvider,
643}
644
645pub enum EditPredictionPreview {
646 /// Modifier is not pressed
647 Inactive { released_too_fast: bool },
648 /// Modifier pressed
649 Active {
650 since: Instant,
651 previous_scroll_position: Option<ScrollAnchor>,
652 },
653}
654
655impl EditPredictionPreview {
656 pub fn released_too_fast(&self) -> bool {
657 match self {
658 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
659 EditPredictionPreview::Active { .. } => false,
660 }
661 }
662
663 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
664 if let EditPredictionPreview::Active {
665 previous_scroll_position,
666 ..
667 } = self
668 {
669 *previous_scroll_position = scroll_position;
670 }
671 }
672}
673
674pub struct ContextMenuOptions {
675 pub min_entries_visible: usize,
676 pub max_entries_visible: usize,
677 pub placement: Option<ContextMenuPlacement>,
678}
679
680#[derive(Debug, Clone, PartialEq, Eq)]
681pub enum ContextMenuPlacement {
682 Above,
683 Below,
684}
685
686#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
687struct EditorActionId(usize);
688
689impl EditorActionId {
690 pub fn post_inc(&mut self) -> Self {
691 let answer = self.0;
692
693 *self = Self(answer + 1);
694
695 Self(answer)
696 }
697}
698
699// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
700// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
701
702type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
703type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
704
705#[derive(Default)]
706struct ScrollbarMarkerState {
707 scrollbar_size: Size<Pixels>,
708 dirty: bool,
709 markers: Arc<[PaintQuad]>,
710 pending_refresh: Option<Task<Result<()>>>,
711}
712
713impl ScrollbarMarkerState {
714 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
715 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
716 }
717}
718
719#[derive(Clone, Copy, PartialEq, Eq)]
720pub enum MinimapVisibility {
721 Disabled,
722 Enabled {
723 /// The configuration currently present in the users settings.
724 setting_configuration: bool,
725 /// Whether to override the currently set visibility from the users setting.
726 toggle_override: bool,
727 },
728}
729
730impl MinimapVisibility {
731 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
732 if mode.is_full() {
733 Self::Enabled {
734 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
735 toggle_override: false,
736 }
737 } else {
738 Self::Disabled
739 }
740 }
741
742 fn hidden(&self) -> Self {
743 match *self {
744 Self::Enabled {
745 setting_configuration,
746 ..
747 } => Self::Enabled {
748 setting_configuration,
749 toggle_override: setting_configuration,
750 },
751 Self::Disabled => Self::Disabled,
752 }
753 }
754
755 fn disabled(&self) -> bool {
756 match *self {
757 Self::Disabled => true,
758 _ => false,
759 }
760 }
761
762 fn settings_visibility(&self) -> bool {
763 match *self {
764 Self::Enabled {
765 setting_configuration,
766 ..
767 } => setting_configuration,
768 _ => false,
769 }
770 }
771
772 fn visible(&self) -> bool {
773 match *self {
774 Self::Enabled {
775 setting_configuration,
776 toggle_override,
777 } => setting_configuration ^ toggle_override,
778 _ => false,
779 }
780 }
781
782 fn toggle_visibility(&self) -> Self {
783 match *self {
784 Self::Enabled {
785 toggle_override,
786 setting_configuration,
787 } => Self::Enabled {
788 setting_configuration,
789 toggle_override: !toggle_override,
790 },
791 Self::Disabled => Self::Disabled,
792 }
793 }
794}
795
796#[derive(Clone, Debug)]
797struct RunnableTasks {
798 templates: Vec<(TaskSourceKind, TaskTemplate)>,
799 offset: multi_buffer::Anchor,
800 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
801 column: u32,
802 // Values of all named captures, including those starting with '_'
803 extra_variables: HashMap<String, String>,
804 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
805 context_range: Range<BufferOffset>,
806}
807
808impl RunnableTasks {
809 fn resolve<'a>(
810 &'a self,
811 cx: &'a task::TaskContext,
812 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
813 self.templates.iter().filter_map(|(kind, template)| {
814 template
815 .resolve_task(&kind.to_id_base(), cx)
816 .map(|task| (kind.clone(), task))
817 })
818 }
819}
820
821#[derive(Clone)]
822pub struct ResolvedTasks {
823 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
824 position: Anchor,
825}
826
827#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
828struct BufferOffset(usize);
829
830// Addons allow storing per-editor state in other crates (e.g. Vim)
831pub trait Addon: 'static {
832 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
833
834 fn render_buffer_header_controls(
835 &self,
836 _: &ExcerptInfo,
837 _: &Window,
838 _: &App,
839 ) -> Option<AnyElement> {
840 None
841 }
842
843 fn to_any(&self) -> &dyn std::any::Any;
844
845 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
846 None
847 }
848}
849
850/// A set of caret positions, registered when the editor was edited.
851pub struct ChangeList {
852 changes: Vec<Vec<Anchor>>,
853 /// Currently "selected" change.
854 position: Option<usize>,
855}
856
857impl ChangeList {
858 pub fn new() -> Self {
859 Self {
860 changes: Vec::new(),
861 position: None,
862 }
863 }
864
865 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
866 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
867 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
868 if self.changes.is_empty() {
869 return None;
870 }
871
872 let prev = self.position.unwrap_or(self.changes.len());
873 let next = if direction == Direction::Prev {
874 prev.saturating_sub(count)
875 } else {
876 (prev + count).min(self.changes.len() - 1)
877 };
878 self.position = Some(next);
879 self.changes.get(next).map(|anchors| anchors.as_slice())
880 }
881
882 /// Adds a new change to the list, resetting the change list position.
883 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
884 self.position.take();
885 if pop_state {
886 self.changes.pop();
887 }
888 self.changes.push(new_positions.clone());
889 }
890
891 pub fn last(&self) -> Option<&[Anchor]> {
892 self.changes.last().map(|anchors| anchors.as_slice())
893 }
894}
895
896#[derive(Clone)]
897struct InlineBlamePopoverState {
898 scroll_handle: ScrollHandle,
899 commit_message: Option<ParsedCommitMessage>,
900 markdown: Entity<Markdown>,
901}
902
903struct InlineBlamePopover {
904 position: gpui::Point<Pixels>,
905 show_task: Option<Task<()>>,
906 hide_task: Option<Task<()>>,
907 popover_bounds: Option<Bounds<Pixels>>,
908 popover_state: InlineBlamePopoverState,
909}
910
911/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
912/// a breakpoint on them.
913#[derive(Clone, Copy, Debug)]
914struct PhantomBreakpointIndicator {
915 display_row: DisplayRow,
916 /// There's a small debounce between hovering over the line and showing the indicator.
917 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
918 is_active: bool,
919 collides_with_existing_breakpoint: bool,
920}
921/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
922///
923/// See the [module level documentation](self) for more information.
924pub struct Editor {
925 focus_handle: FocusHandle,
926 last_focused_descendant: Option<WeakFocusHandle>,
927 /// The text buffer being edited
928 buffer: Entity<MultiBuffer>,
929 /// Map of how text in the buffer should be displayed.
930 /// Handles soft wraps, folds, fake inlay text insertions, etc.
931 pub display_map: Entity<DisplayMap>,
932 pub selections: SelectionsCollection,
933 pub scroll_manager: ScrollManager,
934 /// When inline assist editors are linked, they all render cursors because
935 /// typing enters text into each of them, even the ones that aren't focused.
936 pub(crate) show_cursor_when_unfocused: bool,
937 columnar_selection_tail: Option<Anchor>,
938 columnar_display_point: Option<DisplayPoint>,
939 add_selections_state: Option<AddSelectionsState>,
940 select_next_state: Option<SelectNextState>,
941 select_prev_state: Option<SelectNextState>,
942 selection_history: SelectionHistory,
943 defer_selection_effects: bool,
944 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
945 autoclose_regions: Vec<AutocloseRegion>,
946 snippet_stack: InvalidationStack<SnippetState>,
947 select_syntax_node_history: SelectSyntaxNodeHistory,
948 ime_transaction: Option<TransactionId>,
949 pub diagnostics_max_severity: DiagnosticSeverity,
950 active_diagnostics: ActiveDiagnostic,
951 show_inline_diagnostics: bool,
952 inline_diagnostics_update: Task<()>,
953 inline_diagnostics_enabled: bool,
954 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
955 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
956 hard_wrap: Option<usize>,
957
958 // TODO: make this a access method
959 pub project: Option<Entity<Project>>,
960 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
961 completion_provider: Option<Rc<dyn CompletionProvider>>,
962 collaboration_hub: Option<Box<dyn CollaborationHub>>,
963 blink_manager: Entity<BlinkManager>,
964 show_cursor_names: bool,
965 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
966 pub show_local_selections: bool,
967 mode: EditorMode,
968 show_breadcrumbs: bool,
969 show_gutter: bool,
970 show_scrollbars: ScrollbarAxes,
971 minimap_visibility: MinimapVisibility,
972 offset_content: bool,
973 disable_expand_excerpt_buttons: bool,
974 show_line_numbers: Option<bool>,
975 use_relative_line_numbers: Option<bool>,
976 show_git_diff_gutter: Option<bool>,
977 show_code_actions: Option<bool>,
978 show_runnables: Option<bool>,
979 show_breakpoints: Option<bool>,
980 show_wrap_guides: Option<bool>,
981 show_indent_guides: Option<bool>,
982 placeholder_text: Option<Arc<str>>,
983 highlight_order: usize,
984 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
985 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
986 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
987 scrollbar_marker_state: ScrollbarMarkerState,
988 active_indent_guides_state: ActiveIndentGuidesState,
989 nav_history: Option<ItemNavHistory>,
990 context_menu: RefCell<Option<CodeContextMenu>>,
991 context_menu_options: Option<ContextMenuOptions>,
992 mouse_context_menu: Option<MouseContextMenu>,
993 completion_tasks: Vec<(CompletionId, Task<()>)>,
994 inline_blame_popover: Option<InlineBlamePopover>,
995 signature_help_state: SignatureHelpState,
996 auto_signature_help: Option<bool>,
997 find_all_references_task_sources: Vec<Anchor>,
998 next_completion_id: CompletionId,
999 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1000 code_actions_task: Option<Task<Result<()>>>,
1001 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1002 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1003 document_highlights_task: Option<Task<()>>,
1004 linked_editing_range_task: Option<Task<Option<()>>>,
1005 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1006 pending_rename: Option<RenameState>,
1007 searchable: bool,
1008 cursor_shape: CursorShape,
1009 current_line_highlight: Option<CurrentLineHighlight>,
1010 collapse_matches: bool,
1011 autoindent_mode: Option<AutoindentMode>,
1012 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1013 input_enabled: bool,
1014 use_modal_editing: bool,
1015 read_only: bool,
1016 leader_id: Option<CollaboratorId>,
1017 remote_id: Option<ViewId>,
1018 pub hover_state: HoverState,
1019 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1020 gutter_hovered: bool,
1021 hovered_link_state: Option<HoveredLinkState>,
1022 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1023 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1024 active_inline_completion: Option<InlineCompletionState>,
1025 /// Used to prevent flickering as the user types while the menu is open
1026 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1027 edit_prediction_settings: EditPredictionSettings,
1028 inline_completions_hidden_for_vim_mode: bool,
1029 show_inline_completions_override: Option<bool>,
1030 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1031 edit_prediction_preview: EditPredictionPreview,
1032 edit_prediction_indent_conflict: bool,
1033 edit_prediction_requires_modifier_in_indent_conflict: bool,
1034 inlay_hint_cache: InlayHintCache,
1035 next_inlay_id: usize,
1036 _subscriptions: Vec<Subscription>,
1037 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1038 gutter_dimensions: GutterDimensions,
1039 style: Option<EditorStyle>,
1040 text_style_refinement: Option<TextStyleRefinement>,
1041 next_editor_action_id: EditorActionId,
1042 editor_actions:
1043 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
1044 use_autoclose: bool,
1045 use_auto_surround: bool,
1046 auto_replace_emoji_shortcode: bool,
1047 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1048 show_git_blame_gutter: bool,
1049 show_git_blame_inline: bool,
1050 show_git_blame_inline_delay_task: Option<Task<()>>,
1051 git_blame_inline_enabled: bool,
1052 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1053 serialize_dirty_buffers: bool,
1054 show_selection_menu: Option<bool>,
1055 blame: Option<Entity<GitBlame>>,
1056 blame_subscription: Option<Subscription>,
1057 custom_context_menu: Option<
1058 Box<
1059 dyn 'static
1060 + Fn(
1061 &mut Self,
1062 DisplayPoint,
1063 &mut Window,
1064 &mut Context<Self>,
1065 ) -> Option<Entity<ui::ContextMenu>>,
1066 >,
1067 >,
1068 last_bounds: Option<Bounds<Pixels>>,
1069 last_position_map: Option<Rc<PositionMap>>,
1070 expect_bounds_change: Option<Bounds<Pixels>>,
1071 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1072 tasks_update_task: Option<Task<()>>,
1073 breakpoint_store: Option<Entity<BreakpointStore>>,
1074 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1075 in_project_search: bool,
1076 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1077 breadcrumb_header: Option<String>,
1078 focused_block: Option<FocusedBlock>,
1079 next_scroll_position: NextScrollCursorCenterTopBottom,
1080 addons: HashMap<TypeId, Box<dyn Addon>>,
1081 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1082 load_diff_task: Option<Shared<Task<()>>>,
1083 /// Whether we are temporarily displaying a diff other than git's
1084 temporary_diff_override: bool,
1085 selection_mark_mode: bool,
1086 toggle_fold_multiple_buffers: Task<()>,
1087 _scroll_cursor_center_top_bottom_task: Task<()>,
1088 serialize_selections: Task<()>,
1089 serialize_folds: Task<()>,
1090 mouse_cursor_hidden: bool,
1091 minimap: Option<Entity<Self>>,
1092 hide_mouse_mode: HideMouseMode,
1093 pub change_list: ChangeList,
1094 inline_value_cache: InlineValueCache,
1095}
1096
1097#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1098enum NextScrollCursorCenterTopBottom {
1099 #[default]
1100 Center,
1101 Top,
1102 Bottom,
1103}
1104
1105impl NextScrollCursorCenterTopBottom {
1106 fn next(&self) -> Self {
1107 match self {
1108 Self::Center => Self::Top,
1109 Self::Top => Self::Bottom,
1110 Self::Bottom => Self::Center,
1111 }
1112 }
1113}
1114
1115#[derive(Clone)]
1116pub struct EditorSnapshot {
1117 pub mode: EditorMode,
1118 show_gutter: bool,
1119 show_line_numbers: Option<bool>,
1120 show_git_diff_gutter: Option<bool>,
1121 show_code_actions: Option<bool>,
1122 show_runnables: Option<bool>,
1123 show_breakpoints: Option<bool>,
1124 git_blame_gutter_max_author_length: Option<usize>,
1125 pub display_snapshot: DisplaySnapshot,
1126 pub placeholder_text: Option<Arc<str>>,
1127 is_focused: bool,
1128 scroll_anchor: ScrollAnchor,
1129 ongoing_scroll: OngoingScroll,
1130 current_line_highlight: CurrentLineHighlight,
1131 gutter_hovered: bool,
1132}
1133
1134#[derive(Default, Debug, Clone, Copy)]
1135pub struct GutterDimensions {
1136 pub left_padding: Pixels,
1137 pub right_padding: Pixels,
1138 pub width: Pixels,
1139 pub margin: Pixels,
1140 pub git_blame_entries_width: Option<Pixels>,
1141}
1142
1143impl GutterDimensions {
1144 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1145 Self {
1146 margin: Self::default_gutter_margin(font_id, font_size, cx),
1147 ..Default::default()
1148 }
1149 }
1150
1151 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1152 -cx.text_system().descent(font_id, font_size)
1153 }
1154 /// The full width of the space taken up by the gutter.
1155 pub fn full_width(&self) -> Pixels {
1156 self.margin + self.width
1157 }
1158
1159 /// The width of the space reserved for the fold indicators,
1160 /// use alongside 'justify_end' and `gutter_width` to
1161 /// right align content with the line numbers
1162 pub fn fold_area_width(&self) -> Pixels {
1163 self.margin + self.right_padding
1164 }
1165}
1166
1167#[derive(Debug)]
1168pub struct RemoteSelection {
1169 pub replica_id: ReplicaId,
1170 pub selection: Selection<Anchor>,
1171 pub cursor_shape: CursorShape,
1172 pub collaborator_id: CollaboratorId,
1173 pub line_mode: bool,
1174 pub user_name: Option<SharedString>,
1175 pub color: PlayerColor,
1176}
1177
1178#[derive(Clone, Debug)]
1179struct SelectionHistoryEntry {
1180 selections: Arc<[Selection<Anchor>]>,
1181 select_next_state: Option<SelectNextState>,
1182 select_prev_state: Option<SelectNextState>,
1183 add_selections_state: Option<AddSelectionsState>,
1184}
1185
1186enum SelectionHistoryMode {
1187 Normal,
1188 Undoing,
1189 Redoing,
1190}
1191
1192#[derive(Clone, PartialEq, Eq, Hash)]
1193struct HoveredCursor {
1194 replica_id: u16,
1195 selection_id: usize,
1196}
1197
1198impl Default for SelectionHistoryMode {
1199 fn default() -> Self {
1200 Self::Normal
1201 }
1202}
1203
1204struct DeferredSelectionEffectsState {
1205 changed: bool,
1206 should_update_completions: bool,
1207 autoscroll: Option<Autoscroll>,
1208 old_cursor_position: Anchor,
1209 history_entry: SelectionHistoryEntry,
1210}
1211
1212#[derive(Default)]
1213struct SelectionHistory {
1214 #[allow(clippy::type_complexity)]
1215 selections_by_transaction:
1216 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1217 mode: SelectionHistoryMode,
1218 undo_stack: VecDeque<SelectionHistoryEntry>,
1219 redo_stack: VecDeque<SelectionHistoryEntry>,
1220}
1221
1222impl SelectionHistory {
1223 fn insert_transaction(
1224 &mut self,
1225 transaction_id: TransactionId,
1226 selections: Arc<[Selection<Anchor>]>,
1227 ) {
1228 self.selections_by_transaction
1229 .insert(transaction_id, (selections, None));
1230 }
1231
1232 #[allow(clippy::type_complexity)]
1233 fn transaction(
1234 &self,
1235 transaction_id: TransactionId,
1236 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1237 self.selections_by_transaction.get(&transaction_id)
1238 }
1239
1240 #[allow(clippy::type_complexity)]
1241 fn transaction_mut(
1242 &mut self,
1243 transaction_id: TransactionId,
1244 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1245 self.selections_by_transaction.get_mut(&transaction_id)
1246 }
1247
1248 fn push(&mut self, entry: SelectionHistoryEntry) {
1249 if !entry.selections.is_empty() {
1250 match self.mode {
1251 SelectionHistoryMode::Normal => {
1252 self.push_undo(entry);
1253 self.redo_stack.clear();
1254 }
1255 SelectionHistoryMode::Undoing => self.push_redo(entry),
1256 SelectionHistoryMode::Redoing => self.push_undo(entry),
1257 }
1258 }
1259 }
1260
1261 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1262 if self
1263 .undo_stack
1264 .back()
1265 .map_or(true, |e| e.selections != entry.selections)
1266 {
1267 self.undo_stack.push_back(entry);
1268 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1269 self.undo_stack.pop_front();
1270 }
1271 }
1272 }
1273
1274 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1275 if self
1276 .redo_stack
1277 .back()
1278 .map_or(true, |e| e.selections != entry.selections)
1279 {
1280 self.redo_stack.push_back(entry);
1281 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1282 self.redo_stack.pop_front();
1283 }
1284 }
1285 }
1286}
1287
1288#[derive(Clone, Copy)]
1289pub struct RowHighlightOptions {
1290 pub autoscroll: bool,
1291 pub include_gutter: bool,
1292}
1293
1294impl Default for RowHighlightOptions {
1295 fn default() -> Self {
1296 Self {
1297 autoscroll: Default::default(),
1298 include_gutter: true,
1299 }
1300 }
1301}
1302
1303struct RowHighlight {
1304 index: usize,
1305 range: Range<Anchor>,
1306 color: Hsla,
1307 options: RowHighlightOptions,
1308 type_id: TypeId,
1309}
1310
1311#[derive(Clone, Debug)]
1312struct AddSelectionsState {
1313 above: bool,
1314 stack: Vec<usize>,
1315}
1316
1317#[derive(Clone)]
1318struct SelectNextState {
1319 query: AhoCorasick,
1320 wordwise: bool,
1321 done: bool,
1322}
1323
1324impl std::fmt::Debug for SelectNextState {
1325 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1326 f.debug_struct(std::any::type_name::<Self>())
1327 .field("wordwise", &self.wordwise)
1328 .field("done", &self.done)
1329 .finish()
1330 }
1331}
1332
1333#[derive(Debug)]
1334struct AutocloseRegion {
1335 selection_id: usize,
1336 range: Range<Anchor>,
1337 pair: BracketPair,
1338}
1339
1340#[derive(Debug)]
1341struct SnippetState {
1342 ranges: Vec<Vec<Range<Anchor>>>,
1343 active_index: usize,
1344 choices: Vec<Option<Vec<String>>>,
1345}
1346
1347#[doc(hidden)]
1348pub struct RenameState {
1349 pub range: Range<Anchor>,
1350 pub old_name: Arc<str>,
1351 pub editor: Entity<Editor>,
1352 block_id: CustomBlockId,
1353}
1354
1355struct InvalidationStack<T>(Vec<T>);
1356
1357struct RegisteredInlineCompletionProvider {
1358 provider: Arc<dyn InlineCompletionProviderHandle>,
1359 _subscription: Subscription,
1360}
1361
1362#[derive(Debug, PartialEq, Eq)]
1363pub struct ActiveDiagnosticGroup {
1364 pub active_range: Range<Anchor>,
1365 pub active_message: String,
1366 pub group_id: usize,
1367 pub blocks: HashSet<CustomBlockId>,
1368}
1369
1370#[derive(Debug, PartialEq, Eq)]
1371
1372pub(crate) enum ActiveDiagnostic {
1373 None,
1374 All,
1375 Group(ActiveDiagnosticGroup),
1376}
1377
1378#[derive(Serialize, Deserialize, Clone, Debug)]
1379pub struct ClipboardSelection {
1380 /// The number of bytes in this selection.
1381 pub len: usize,
1382 /// Whether this was a full-line selection.
1383 pub is_entire_line: bool,
1384 /// The indentation of the first line when this content was originally copied.
1385 pub first_line_indent: u32,
1386}
1387
1388// selections, scroll behavior, was newest selection reversed
1389type SelectSyntaxNodeHistoryState = (
1390 Box<[Selection<usize>]>,
1391 SelectSyntaxNodeScrollBehavior,
1392 bool,
1393);
1394
1395#[derive(Default)]
1396struct SelectSyntaxNodeHistory {
1397 stack: Vec<SelectSyntaxNodeHistoryState>,
1398 // disable temporarily to allow changing selections without losing the stack
1399 pub disable_clearing: bool,
1400}
1401
1402impl SelectSyntaxNodeHistory {
1403 pub fn try_clear(&mut self) {
1404 if !self.disable_clearing {
1405 self.stack.clear();
1406 }
1407 }
1408
1409 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1410 self.stack.push(selection);
1411 }
1412
1413 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1414 self.stack.pop()
1415 }
1416}
1417
1418enum SelectSyntaxNodeScrollBehavior {
1419 CursorTop,
1420 FitSelection,
1421 CursorBottom,
1422}
1423
1424#[derive(Debug)]
1425pub(crate) struct NavigationData {
1426 cursor_anchor: Anchor,
1427 cursor_position: Point,
1428 scroll_anchor: ScrollAnchor,
1429 scroll_top_row: u32,
1430}
1431
1432#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1433pub enum GotoDefinitionKind {
1434 Symbol,
1435 Declaration,
1436 Type,
1437 Implementation,
1438}
1439
1440#[derive(Debug, Clone)]
1441enum InlayHintRefreshReason {
1442 ModifiersChanged(bool),
1443 Toggle(bool),
1444 SettingsChange(InlayHintSettings),
1445 NewLinesShown,
1446 BufferEdited(HashSet<Arc<Language>>),
1447 RefreshRequested,
1448 ExcerptsRemoved(Vec<ExcerptId>),
1449}
1450
1451impl InlayHintRefreshReason {
1452 fn description(&self) -> &'static str {
1453 match self {
1454 Self::ModifiersChanged(_) => "modifiers changed",
1455 Self::Toggle(_) => "toggle",
1456 Self::SettingsChange(_) => "settings change",
1457 Self::NewLinesShown => "new lines shown",
1458 Self::BufferEdited(_) => "buffer edited",
1459 Self::RefreshRequested => "refresh requested",
1460 Self::ExcerptsRemoved(_) => "excerpts removed",
1461 }
1462 }
1463}
1464
1465pub enum FormatTarget {
1466 Buffers,
1467 Ranges(Vec<Range<MultiBufferPoint>>),
1468}
1469
1470pub(crate) struct FocusedBlock {
1471 id: BlockId,
1472 focus_handle: WeakFocusHandle,
1473}
1474
1475#[derive(Clone)]
1476enum JumpData {
1477 MultiBufferRow {
1478 row: MultiBufferRow,
1479 line_offset_from_top: u32,
1480 },
1481 MultiBufferPoint {
1482 excerpt_id: ExcerptId,
1483 position: Point,
1484 anchor: text::Anchor,
1485 line_offset_from_top: u32,
1486 },
1487}
1488
1489pub enum MultibufferSelectionMode {
1490 First,
1491 All,
1492}
1493
1494#[derive(Clone, Copy, Debug, Default)]
1495pub struct RewrapOptions {
1496 pub override_language_settings: bool,
1497 pub preserve_existing_whitespace: bool,
1498}
1499
1500impl Editor {
1501 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1502 let buffer = cx.new(|cx| Buffer::local("", cx));
1503 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1504 Self::new(
1505 EditorMode::SingleLine { auto_width: false },
1506 buffer,
1507 None,
1508 window,
1509 cx,
1510 )
1511 }
1512
1513 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1514 let buffer = cx.new(|cx| Buffer::local("", cx));
1515 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1516 Self::new(EditorMode::full(), buffer, None, window, cx)
1517 }
1518
1519 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1520 let buffer = cx.new(|cx| Buffer::local("", cx));
1521 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1522 Self::new(
1523 EditorMode::SingleLine { auto_width: true },
1524 buffer,
1525 None,
1526 window,
1527 cx,
1528 )
1529 }
1530
1531 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1532 let buffer = cx.new(|cx| Buffer::local("", cx));
1533 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1534 Self::new(
1535 EditorMode::AutoHeight { max_lines },
1536 buffer,
1537 None,
1538 window,
1539 cx,
1540 )
1541 }
1542
1543 pub fn for_buffer(
1544 buffer: Entity<Buffer>,
1545 project: Option<Entity<Project>>,
1546 window: &mut Window,
1547 cx: &mut Context<Self>,
1548 ) -> Self {
1549 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1550 Self::new(EditorMode::full(), buffer, project, window, cx)
1551 }
1552
1553 pub fn for_multibuffer(
1554 buffer: Entity<MultiBuffer>,
1555 project: Option<Entity<Project>>,
1556 window: &mut Window,
1557 cx: &mut Context<Self>,
1558 ) -> Self {
1559 Self::new(EditorMode::full(), buffer, project, window, cx)
1560 }
1561
1562 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1563 let mut clone = Self::new(
1564 self.mode.clone(),
1565 self.buffer.clone(),
1566 self.project.clone(),
1567 window,
1568 cx,
1569 );
1570 self.display_map.update(cx, |display_map, cx| {
1571 let snapshot = display_map.snapshot(cx);
1572 clone.display_map.update(cx, |display_map, cx| {
1573 display_map.set_state(&snapshot, cx);
1574 });
1575 });
1576 clone.folds_did_change(cx);
1577 clone.selections.clone_state(&self.selections);
1578 clone.scroll_manager.clone_state(&self.scroll_manager);
1579 clone.searchable = self.searchable;
1580 clone.read_only = self.read_only;
1581 clone
1582 }
1583
1584 pub fn new(
1585 mode: EditorMode,
1586 buffer: Entity<MultiBuffer>,
1587 project: Option<Entity<Project>>,
1588 window: &mut Window,
1589 cx: &mut Context<Self>,
1590 ) -> Self {
1591 Editor::new_internal(mode, buffer, project, None, window, cx)
1592 }
1593
1594 fn new_internal(
1595 mode: EditorMode,
1596 buffer: Entity<MultiBuffer>,
1597 project: Option<Entity<Project>>,
1598 display_map: Option<Entity<DisplayMap>>,
1599 window: &mut Window,
1600 cx: &mut Context<Self>,
1601 ) -> Self {
1602 debug_assert!(
1603 display_map.is_none() || mode.is_minimap(),
1604 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1605 );
1606
1607 let full_mode = mode.is_full();
1608 let diagnostics_max_severity = if full_mode {
1609 EditorSettings::get_global(cx)
1610 .diagnostics_max_severity
1611 .unwrap_or(DiagnosticSeverity::Hint)
1612 } else {
1613 DiagnosticSeverity::Off
1614 };
1615 let style = window.text_style();
1616 let font_size = style.font_size.to_pixels(window.rem_size());
1617 let editor = cx.entity().downgrade();
1618 let fold_placeholder = FoldPlaceholder {
1619 constrain_width: true,
1620 render: Arc::new(move |fold_id, fold_range, cx| {
1621 let editor = editor.clone();
1622 div()
1623 .id(fold_id)
1624 .bg(cx.theme().colors().ghost_element_background)
1625 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1626 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1627 .rounded_xs()
1628 .size_full()
1629 .cursor_pointer()
1630 .child("⋯")
1631 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1632 .on_click(move |_, _window, cx| {
1633 editor
1634 .update(cx, |editor, cx| {
1635 editor.unfold_ranges(
1636 &[fold_range.start..fold_range.end],
1637 true,
1638 false,
1639 cx,
1640 );
1641 cx.stop_propagation();
1642 })
1643 .ok();
1644 })
1645 .into_any()
1646 }),
1647 merge_adjacent: true,
1648 ..FoldPlaceholder::default()
1649 };
1650 let display_map = display_map.unwrap_or_else(|| {
1651 cx.new(|cx| {
1652 DisplayMap::new(
1653 buffer.clone(),
1654 style.font(),
1655 font_size,
1656 None,
1657 FILE_HEADER_HEIGHT,
1658 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1659 fold_placeholder,
1660 diagnostics_max_severity,
1661 cx,
1662 )
1663 })
1664 });
1665
1666 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1667
1668 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1669
1670 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1671 .then(|| language_settings::SoftWrap::None);
1672
1673 let mut project_subscriptions = Vec::new();
1674 if mode.is_full() {
1675 if let Some(project) = project.as_ref() {
1676 project_subscriptions.push(cx.subscribe_in(
1677 project,
1678 window,
1679 |editor, _, event, window, cx| match event {
1680 project::Event::RefreshCodeLens => {
1681 // we always query lens with actions, without storing them, always refreshing them
1682 }
1683 project::Event::RefreshInlayHints => {
1684 editor
1685 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1686 }
1687 project::Event::LanguageServerAdded(..)
1688 | project::Event::LanguageServerRemoved(..) => {
1689 if editor.tasks_update_task.is_none() {
1690 editor.tasks_update_task =
1691 Some(editor.refresh_runnables(window, cx));
1692 }
1693 }
1694 project::Event::SnippetEdit(id, snippet_edits) => {
1695 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1696 let focus_handle = editor.focus_handle(cx);
1697 if focus_handle.is_focused(window) {
1698 let snapshot = buffer.read(cx).snapshot();
1699 for (range, snippet) in snippet_edits {
1700 let editor_range =
1701 language::range_from_lsp(*range).to_offset(&snapshot);
1702 editor
1703 .insert_snippet(
1704 &[editor_range],
1705 snippet.clone(),
1706 window,
1707 cx,
1708 )
1709 .ok();
1710 }
1711 }
1712 }
1713 }
1714 _ => {}
1715 },
1716 ));
1717 if let Some(task_inventory) = project
1718 .read(cx)
1719 .task_store()
1720 .read(cx)
1721 .task_inventory()
1722 .cloned()
1723 {
1724 project_subscriptions.push(cx.observe_in(
1725 &task_inventory,
1726 window,
1727 |editor, _, window, cx| {
1728 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1729 },
1730 ));
1731 };
1732
1733 project_subscriptions.push(cx.subscribe_in(
1734 &project.read(cx).breakpoint_store(),
1735 window,
1736 |editor, _, event, window, cx| match event {
1737 BreakpointStoreEvent::ClearDebugLines => {
1738 editor.clear_row_highlights::<ActiveDebugLine>();
1739 editor.refresh_inline_values(cx);
1740 }
1741 BreakpointStoreEvent::SetDebugLine => {
1742 if editor.go_to_active_debug_line(window, cx) {
1743 cx.stop_propagation();
1744 }
1745
1746 editor.refresh_inline_values(cx);
1747 }
1748 _ => {}
1749 },
1750 ));
1751 }
1752 }
1753
1754 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1755
1756 let inlay_hint_settings =
1757 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1758 let focus_handle = cx.focus_handle();
1759 cx.on_focus(&focus_handle, window, Self::handle_focus)
1760 .detach();
1761 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1762 .detach();
1763 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1764 .detach();
1765 cx.on_blur(&focus_handle, window, Self::handle_blur)
1766 .detach();
1767
1768 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1769 Some(false)
1770 } else {
1771 None
1772 };
1773
1774 let breakpoint_store = match (&mode, project.as_ref()) {
1775 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1776 _ => None,
1777 };
1778
1779 let mut code_action_providers = Vec::new();
1780 let mut load_uncommitted_diff = None;
1781 if let Some(project) = project.clone() {
1782 load_uncommitted_diff = Some(
1783 update_uncommitted_diff_for_buffer(
1784 cx.entity(),
1785 &project,
1786 buffer.read(cx).all_buffers(),
1787 buffer.clone(),
1788 cx,
1789 )
1790 .shared(),
1791 );
1792 code_action_providers.push(Rc::new(project) as Rc<_>);
1793 }
1794
1795 let mut this = Self {
1796 focus_handle,
1797 show_cursor_when_unfocused: false,
1798 last_focused_descendant: None,
1799 buffer: buffer.clone(),
1800 display_map: display_map.clone(),
1801 selections,
1802 scroll_manager: ScrollManager::new(cx),
1803 columnar_selection_tail: None,
1804 columnar_display_point: None,
1805 add_selections_state: None,
1806 select_next_state: None,
1807 select_prev_state: None,
1808 selection_history: SelectionHistory::default(),
1809 defer_selection_effects: false,
1810 deferred_selection_effects_state: None,
1811 autoclose_regions: Vec::new(),
1812 snippet_stack: InvalidationStack::default(),
1813 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1814 ime_transaction: None,
1815 active_diagnostics: ActiveDiagnostic::None,
1816 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1817 inline_diagnostics_update: Task::ready(()),
1818 inline_diagnostics: Vec::new(),
1819 soft_wrap_mode_override,
1820 diagnostics_max_severity,
1821 hard_wrap: None,
1822 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1823 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1824 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1825 project,
1826 blink_manager: blink_manager.clone(),
1827 show_local_selections: true,
1828 show_scrollbars: ScrollbarAxes {
1829 horizontal: full_mode,
1830 vertical: full_mode,
1831 },
1832 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1833 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1834 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1835 show_gutter: mode.is_full(),
1836 show_line_numbers: None,
1837 use_relative_line_numbers: None,
1838 disable_expand_excerpt_buttons: false,
1839 show_git_diff_gutter: None,
1840 show_code_actions: None,
1841 show_runnables: None,
1842 show_breakpoints: None,
1843 show_wrap_guides: None,
1844 show_indent_guides,
1845 placeholder_text: None,
1846 highlight_order: 0,
1847 highlighted_rows: HashMap::default(),
1848 background_highlights: TreeMap::default(),
1849 gutter_highlights: TreeMap::default(),
1850 scrollbar_marker_state: ScrollbarMarkerState::default(),
1851 active_indent_guides_state: ActiveIndentGuidesState::default(),
1852 nav_history: None,
1853 context_menu: RefCell::new(None),
1854 context_menu_options: None,
1855 mouse_context_menu: None,
1856 completion_tasks: Vec::new(),
1857 inline_blame_popover: None,
1858 signature_help_state: SignatureHelpState::default(),
1859 auto_signature_help: None,
1860 find_all_references_task_sources: Vec::new(),
1861 next_completion_id: 0,
1862 next_inlay_id: 0,
1863 code_action_providers,
1864 available_code_actions: None,
1865 code_actions_task: None,
1866 quick_selection_highlight_task: None,
1867 debounced_selection_highlight_task: None,
1868 document_highlights_task: None,
1869 linked_editing_range_task: None,
1870 pending_rename: None,
1871 searchable: true,
1872 cursor_shape: EditorSettings::get_global(cx)
1873 .cursor_shape
1874 .unwrap_or_default(),
1875 current_line_highlight: None,
1876 autoindent_mode: Some(AutoindentMode::EachLine),
1877 collapse_matches: false,
1878 workspace: None,
1879 input_enabled: true,
1880 use_modal_editing: mode.is_full(),
1881 read_only: mode.is_minimap(),
1882 use_autoclose: true,
1883 use_auto_surround: true,
1884 auto_replace_emoji_shortcode: false,
1885 jsx_tag_auto_close_enabled_in_any_buffer: false,
1886 leader_id: None,
1887 remote_id: None,
1888 hover_state: HoverState::default(),
1889 pending_mouse_down: None,
1890 hovered_link_state: None,
1891 edit_prediction_provider: None,
1892 active_inline_completion: None,
1893 stale_inline_completion_in_menu: None,
1894 edit_prediction_preview: EditPredictionPreview::Inactive {
1895 released_too_fast: false,
1896 },
1897 inline_diagnostics_enabled: mode.is_full(),
1898 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1899 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1900
1901 gutter_hovered: false,
1902 pixel_position_of_newest_cursor: None,
1903 last_bounds: None,
1904 last_position_map: None,
1905 expect_bounds_change: None,
1906 gutter_dimensions: GutterDimensions::default(),
1907 style: None,
1908 show_cursor_names: false,
1909 hovered_cursors: HashMap::default(),
1910 next_editor_action_id: EditorActionId::default(),
1911 editor_actions: Rc::default(),
1912 inline_completions_hidden_for_vim_mode: false,
1913 show_inline_completions_override: None,
1914 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1915 edit_prediction_settings: EditPredictionSettings::Disabled,
1916 edit_prediction_indent_conflict: false,
1917 edit_prediction_requires_modifier_in_indent_conflict: true,
1918 custom_context_menu: None,
1919 show_git_blame_gutter: false,
1920 show_git_blame_inline: false,
1921 show_selection_menu: None,
1922 show_git_blame_inline_delay_task: None,
1923 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1924 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1925 serialize_dirty_buffers: !mode.is_minimap()
1926 && ProjectSettings::get_global(cx)
1927 .session
1928 .restore_unsaved_buffers,
1929 blame: None,
1930 blame_subscription: None,
1931 tasks: BTreeMap::default(),
1932
1933 breakpoint_store,
1934 gutter_breakpoint_indicator: (None, None),
1935 _subscriptions: vec![
1936 cx.observe(&buffer, Self::on_buffer_changed),
1937 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1938 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1939 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1940 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1941 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1942 cx.observe_window_activation(window, |editor, window, cx| {
1943 let active = window.is_window_active();
1944 editor.blink_manager.update(cx, |blink_manager, cx| {
1945 if active {
1946 blink_manager.enable(cx);
1947 } else {
1948 blink_manager.disable(cx);
1949 }
1950 });
1951 if active {
1952 editor.show_mouse_cursor();
1953 }
1954 }),
1955 ],
1956 tasks_update_task: None,
1957 linked_edit_ranges: Default::default(),
1958 in_project_search: false,
1959 previous_search_ranges: None,
1960 breadcrumb_header: None,
1961 focused_block: None,
1962 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1963 addons: HashMap::default(),
1964 registered_buffers: HashMap::default(),
1965 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1966 selection_mark_mode: false,
1967 toggle_fold_multiple_buffers: Task::ready(()),
1968 serialize_selections: Task::ready(()),
1969 serialize_folds: Task::ready(()),
1970 text_style_refinement: None,
1971 load_diff_task: load_uncommitted_diff,
1972 temporary_diff_override: false,
1973 mouse_cursor_hidden: false,
1974 minimap: None,
1975 hide_mouse_mode: EditorSettings::get_global(cx)
1976 .hide_mouse
1977 .unwrap_or_default(),
1978 change_list: ChangeList::new(),
1979 mode,
1980 };
1981 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1982 this._subscriptions
1983 .push(cx.observe(breakpoints, |_, _, cx| {
1984 cx.notify();
1985 }));
1986 }
1987 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1988 this._subscriptions.extend(project_subscriptions);
1989
1990 this._subscriptions.push(cx.subscribe_in(
1991 &cx.entity(),
1992 window,
1993 |editor, _, e: &EditorEvent, window, cx| match e {
1994 EditorEvent::ScrollPositionChanged { local, .. } => {
1995 if *local {
1996 let new_anchor = editor.scroll_manager.anchor();
1997 let snapshot = editor.snapshot(window, cx);
1998 editor.update_restoration_data(cx, move |data| {
1999 data.scroll_position = (
2000 new_anchor.top_row(&snapshot.buffer_snapshot),
2001 new_anchor.offset,
2002 );
2003 });
2004 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2005 editor.inline_blame_popover.take();
2006 }
2007 }
2008 EditorEvent::Edited { .. } => {
2009 if !vim_enabled(cx) {
2010 let (map, selections) = editor.selections.all_adjusted_display(cx);
2011 let pop_state = editor
2012 .change_list
2013 .last()
2014 .map(|previous| {
2015 previous.len() == selections.len()
2016 && previous.iter().enumerate().all(|(ix, p)| {
2017 p.to_display_point(&map).row()
2018 == selections[ix].head().row()
2019 })
2020 })
2021 .unwrap_or(false);
2022 let new_positions = selections
2023 .into_iter()
2024 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2025 .collect();
2026 editor
2027 .change_list
2028 .push_to_change_list(pop_state, new_positions);
2029 }
2030 }
2031 _ => (),
2032 },
2033 ));
2034
2035 if let Some(dap_store) = this
2036 .project
2037 .as_ref()
2038 .map(|project| project.read(cx).dap_store())
2039 {
2040 let weak_editor = cx.weak_entity();
2041
2042 this._subscriptions
2043 .push(
2044 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2045 let session_entity = cx.entity();
2046 weak_editor
2047 .update(cx, |editor, cx| {
2048 editor._subscriptions.push(
2049 cx.subscribe(&session_entity, Self::on_debug_session_event),
2050 );
2051 })
2052 .ok();
2053 }),
2054 );
2055
2056 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2057 this._subscriptions
2058 .push(cx.subscribe(&session, Self::on_debug_session_event));
2059 }
2060 }
2061
2062 this.end_selection(window, cx);
2063 this.scroll_manager.show_scrollbars(window, cx);
2064 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
2065
2066 if full_mode {
2067 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2068 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2069
2070 if this.git_blame_inline_enabled {
2071 this.start_git_blame_inline(false, window, cx);
2072 }
2073
2074 this.go_to_active_debug_line(window, cx);
2075
2076 if let Some(buffer) = buffer.read(cx).as_singleton() {
2077 if let Some(project) = this.project.as_ref() {
2078 let handle = project.update(cx, |project, cx| {
2079 project.register_buffer_with_language_servers(&buffer, cx)
2080 });
2081 this.registered_buffers
2082 .insert(buffer.read(cx).remote_id(), handle);
2083 }
2084 }
2085
2086 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2087 }
2088
2089 this.report_editor_event("Editor Opened", None, cx);
2090 this
2091 }
2092
2093 pub fn deploy_mouse_context_menu(
2094 &mut self,
2095 position: gpui::Point<Pixels>,
2096 context_menu: Entity<ContextMenu>,
2097 window: &mut Window,
2098 cx: &mut Context<Self>,
2099 ) {
2100 self.mouse_context_menu = Some(MouseContextMenu::new(
2101 self,
2102 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2103 context_menu,
2104 window,
2105 cx,
2106 ));
2107 }
2108
2109 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2110 self.mouse_context_menu
2111 .as_ref()
2112 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2113 }
2114
2115 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2116 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2117 }
2118
2119 fn key_context_internal(
2120 &self,
2121 has_active_edit_prediction: bool,
2122 window: &Window,
2123 cx: &App,
2124 ) -> KeyContext {
2125 let mut key_context = KeyContext::new_with_defaults();
2126 key_context.add("Editor");
2127 let mode = match self.mode {
2128 EditorMode::SingleLine { .. } => "single_line",
2129 EditorMode::AutoHeight { .. } => "auto_height",
2130 EditorMode::Minimap { .. } => "minimap",
2131 EditorMode::Full { .. } => "full",
2132 };
2133
2134 if EditorSettings::jupyter_enabled(cx) {
2135 key_context.add("jupyter");
2136 }
2137
2138 key_context.set("mode", mode);
2139 if self.pending_rename.is_some() {
2140 key_context.add("renaming");
2141 }
2142
2143 match self.context_menu.borrow().as_ref() {
2144 Some(CodeContextMenu::Completions(_)) => {
2145 key_context.add("menu");
2146 key_context.add("showing_completions");
2147 }
2148 Some(CodeContextMenu::CodeActions(_)) => {
2149 key_context.add("menu");
2150 key_context.add("showing_code_actions")
2151 }
2152 None => {}
2153 }
2154
2155 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2156 if !self.focus_handle(cx).contains_focused(window, cx)
2157 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2158 {
2159 for addon in self.addons.values() {
2160 addon.extend_key_context(&mut key_context, cx)
2161 }
2162 }
2163
2164 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2165 if let Some(extension) = singleton_buffer
2166 .read(cx)
2167 .file()
2168 .and_then(|file| file.path().extension()?.to_str())
2169 {
2170 key_context.set("extension", extension.to_string());
2171 }
2172 } else {
2173 key_context.add("multibuffer");
2174 }
2175
2176 if has_active_edit_prediction {
2177 if self.edit_prediction_in_conflict() {
2178 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2179 } else {
2180 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2181 key_context.add("copilot_suggestion");
2182 }
2183 }
2184
2185 if self.selection_mark_mode {
2186 key_context.add("selection_mode");
2187 }
2188
2189 key_context
2190 }
2191
2192 fn show_mouse_cursor(&mut self) {
2193 self.mouse_cursor_hidden = false;
2194 }
2195
2196 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2197 self.mouse_cursor_hidden = match origin {
2198 HideMouseCursorOrigin::TypingAction => {
2199 matches!(
2200 self.hide_mouse_mode,
2201 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2202 )
2203 }
2204 HideMouseCursorOrigin::MovementAction => {
2205 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2206 }
2207 };
2208 }
2209
2210 pub fn edit_prediction_in_conflict(&self) -> bool {
2211 if !self.show_edit_predictions_in_menu() {
2212 return false;
2213 }
2214
2215 let showing_completions = self
2216 .context_menu
2217 .borrow()
2218 .as_ref()
2219 .map_or(false, |context| {
2220 matches!(context, CodeContextMenu::Completions(_))
2221 });
2222
2223 showing_completions
2224 || self.edit_prediction_requires_modifier()
2225 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2226 // bindings to insert tab characters.
2227 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2228 }
2229
2230 pub fn accept_edit_prediction_keybind(
2231 &self,
2232 window: &Window,
2233 cx: &App,
2234 ) -> AcceptEditPredictionBinding {
2235 let key_context = self.key_context_internal(true, window, cx);
2236 let in_conflict = self.edit_prediction_in_conflict();
2237
2238 AcceptEditPredictionBinding(
2239 window
2240 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2241 .into_iter()
2242 .filter(|binding| {
2243 !in_conflict
2244 || binding
2245 .keystrokes()
2246 .first()
2247 .map_or(false, |keystroke| keystroke.modifiers.modified())
2248 })
2249 .rev()
2250 .min_by_key(|binding| {
2251 binding
2252 .keystrokes()
2253 .first()
2254 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2255 }),
2256 )
2257 }
2258
2259 pub fn new_file(
2260 workspace: &mut Workspace,
2261 _: &workspace::NewFile,
2262 window: &mut Window,
2263 cx: &mut Context<Workspace>,
2264 ) {
2265 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2266 "Failed to create buffer",
2267 window,
2268 cx,
2269 |e, _, _| match e.error_code() {
2270 ErrorCode::RemoteUpgradeRequired => Some(format!(
2271 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2272 e.error_tag("required").unwrap_or("the latest version")
2273 )),
2274 _ => None,
2275 },
2276 );
2277 }
2278
2279 pub fn new_in_workspace(
2280 workspace: &mut Workspace,
2281 window: &mut Window,
2282 cx: &mut Context<Workspace>,
2283 ) -> Task<Result<Entity<Editor>>> {
2284 let project = workspace.project().clone();
2285 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2286
2287 cx.spawn_in(window, async move |workspace, cx| {
2288 let buffer = create.await?;
2289 workspace.update_in(cx, |workspace, window, cx| {
2290 let editor =
2291 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2292 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2293 editor
2294 })
2295 })
2296 }
2297
2298 fn new_file_vertical(
2299 workspace: &mut Workspace,
2300 _: &workspace::NewFileSplitVertical,
2301 window: &mut Window,
2302 cx: &mut Context<Workspace>,
2303 ) {
2304 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2305 }
2306
2307 fn new_file_horizontal(
2308 workspace: &mut Workspace,
2309 _: &workspace::NewFileSplitHorizontal,
2310 window: &mut Window,
2311 cx: &mut Context<Workspace>,
2312 ) {
2313 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2314 }
2315
2316 fn new_file_in_direction(
2317 workspace: &mut Workspace,
2318 direction: SplitDirection,
2319 window: &mut Window,
2320 cx: &mut Context<Workspace>,
2321 ) {
2322 let project = workspace.project().clone();
2323 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2324
2325 cx.spawn_in(window, async move |workspace, cx| {
2326 let buffer = create.await?;
2327 workspace.update_in(cx, move |workspace, window, cx| {
2328 workspace.split_item(
2329 direction,
2330 Box::new(
2331 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2332 ),
2333 window,
2334 cx,
2335 )
2336 })?;
2337 anyhow::Ok(())
2338 })
2339 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2340 match e.error_code() {
2341 ErrorCode::RemoteUpgradeRequired => Some(format!(
2342 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2343 e.error_tag("required").unwrap_or("the latest version")
2344 )),
2345 _ => None,
2346 }
2347 });
2348 }
2349
2350 pub fn leader_id(&self) -> Option<CollaboratorId> {
2351 self.leader_id
2352 }
2353
2354 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2355 &self.buffer
2356 }
2357
2358 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2359 self.workspace.as_ref()?.0.upgrade()
2360 }
2361
2362 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2363 self.buffer().read(cx).title(cx)
2364 }
2365
2366 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2367 let git_blame_gutter_max_author_length = self
2368 .render_git_blame_gutter(cx)
2369 .then(|| {
2370 if let Some(blame) = self.blame.as_ref() {
2371 let max_author_length =
2372 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2373 Some(max_author_length)
2374 } else {
2375 None
2376 }
2377 })
2378 .flatten();
2379
2380 EditorSnapshot {
2381 mode: self.mode.clone(),
2382 show_gutter: self.show_gutter,
2383 show_line_numbers: self.show_line_numbers,
2384 show_git_diff_gutter: self.show_git_diff_gutter,
2385 show_code_actions: self.show_code_actions,
2386 show_runnables: self.show_runnables,
2387 show_breakpoints: self.show_breakpoints,
2388 git_blame_gutter_max_author_length,
2389 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2390 scroll_anchor: self.scroll_manager.anchor(),
2391 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2392 placeholder_text: self.placeholder_text.clone(),
2393 is_focused: self.focus_handle.is_focused(window),
2394 current_line_highlight: self
2395 .current_line_highlight
2396 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2397 gutter_hovered: self.gutter_hovered,
2398 }
2399 }
2400
2401 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2402 self.buffer.read(cx).language_at(point, cx)
2403 }
2404
2405 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2406 self.buffer.read(cx).read(cx).file_at(point).cloned()
2407 }
2408
2409 pub fn active_excerpt(
2410 &self,
2411 cx: &App,
2412 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2413 self.buffer
2414 .read(cx)
2415 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2416 }
2417
2418 pub fn mode(&self) -> &EditorMode {
2419 &self.mode
2420 }
2421
2422 pub fn set_mode(&mut self, mode: EditorMode) {
2423 self.mode = mode;
2424 }
2425
2426 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2427 self.collaboration_hub.as_deref()
2428 }
2429
2430 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2431 self.collaboration_hub = Some(hub);
2432 }
2433
2434 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2435 self.in_project_search = in_project_search;
2436 }
2437
2438 pub fn set_custom_context_menu(
2439 &mut self,
2440 f: impl 'static
2441 + Fn(
2442 &mut Self,
2443 DisplayPoint,
2444 &mut Window,
2445 &mut Context<Self>,
2446 ) -> Option<Entity<ui::ContextMenu>>,
2447 ) {
2448 self.custom_context_menu = Some(Box::new(f))
2449 }
2450
2451 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2452 self.completion_provider = provider;
2453 }
2454
2455 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2456 self.semantics_provider.clone()
2457 }
2458
2459 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2460 self.semantics_provider = provider;
2461 }
2462
2463 pub fn set_edit_prediction_provider<T>(
2464 &mut self,
2465 provider: Option<Entity<T>>,
2466 window: &mut Window,
2467 cx: &mut Context<Self>,
2468 ) where
2469 T: EditPredictionProvider,
2470 {
2471 self.edit_prediction_provider =
2472 provider.map(|provider| RegisteredInlineCompletionProvider {
2473 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2474 if this.focus_handle.is_focused(window) {
2475 this.update_visible_inline_completion(window, cx);
2476 }
2477 }),
2478 provider: Arc::new(provider),
2479 });
2480 self.update_edit_prediction_settings(cx);
2481 self.refresh_inline_completion(false, false, window, cx);
2482 }
2483
2484 pub fn placeholder_text(&self) -> Option<&str> {
2485 self.placeholder_text.as_deref()
2486 }
2487
2488 pub fn set_placeholder_text(
2489 &mut self,
2490 placeholder_text: impl Into<Arc<str>>,
2491 cx: &mut Context<Self>,
2492 ) {
2493 let placeholder_text = Some(placeholder_text.into());
2494 if self.placeholder_text != placeholder_text {
2495 self.placeholder_text = placeholder_text;
2496 cx.notify();
2497 }
2498 }
2499
2500 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2501 self.cursor_shape = cursor_shape;
2502
2503 // Disrupt blink for immediate user feedback that the cursor shape has changed
2504 self.blink_manager.update(cx, BlinkManager::show_cursor);
2505
2506 cx.notify();
2507 }
2508
2509 pub fn set_current_line_highlight(
2510 &mut self,
2511 current_line_highlight: Option<CurrentLineHighlight>,
2512 ) {
2513 self.current_line_highlight = current_line_highlight;
2514 }
2515
2516 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2517 self.collapse_matches = collapse_matches;
2518 }
2519
2520 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2521 let buffers = self.buffer.read(cx).all_buffers();
2522 let Some(project) = self.project.as_ref() else {
2523 return;
2524 };
2525 project.update(cx, |project, cx| {
2526 for buffer in buffers {
2527 self.registered_buffers
2528 .entry(buffer.read(cx).remote_id())
2529 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2530 }
2531 })
2532 }
2533
2534 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2535 if self.collapse_matches {
2536 return range.start..range.start;
2537 }
2538 range.clone()
2539 }
2540
2541 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2542 if self.display_map.read(cx).clip_at_line_ends != clip {
2543 self.display_map
2544 .update(cx, |map, _| map.clip_at_line_ends = clip);
2545 }
2546 }
2547
2548 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2549 self.input_enabled = input_enabled;
2550 }
2551
2552 pub fn set_inline_completions_hidden_for_vim_mode(
2553 &mut self,
2554 hidden: bool,
2555 window: &mut Window,
2556 cx: &mut Context<Self>,
2557 ) {
2558 if hidden != self.inline_completions_hidden_for_vim_mode {
2559 self.inline_completions_hidden_for_vim_mode = hidden;
2560 if hidden {
2561 self.update_visible_inline_completion(window, cx);
2562 } else {
2563 self.refresh_inline_completion(true, false, window, cx);
2564 }
2565 }
2566 }
2567
2568 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2569 self.menu_inline_completions_policy = value;
2570 }
2571
2572 pub fn set_autoindent(&mut self, autoindent: bool) {
2573 if autoindent {
2574 self.autoindent_mode = Some(AutoindentMode::EachLine);
2575 } else {
2576 self.autoindent_mode = None;
2577 }
2578 }
2579
2580 pub fn read_only(&self, cx: &App) -> bool {
2581 self.read_only || self.buffer.read(cx).read_only()
2582 }
2583
2584 pub fn set_read_only(&mut self, read_only: bool) {
2585 self.read_only = read_only;
2586 }
2587
2588 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2589 self.use_autoclose = autoclose;
2590 }
2591
2592 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2593 self.use_auto_surround = auto_surround;
2594 }
2595
2596 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2597 self.auto_replace_emoji_shortcode = auto_replace;
2598 }
2599
2600 pub fn toggle_edit_predictions(
2601 &mut self,
2602 _: &ToggleEditPrediction,
2603 window: &mut Window,
2604 cx: &mut Context<Self>,
2605 ) {
2606 if self.show_inline_completions_override.is_some() {
2607 self.set_show_edit_predictions(None, window, cx);
2608 } else {
2609 let show_edit_predictions = !self.edit_predictions_enabled();
2610 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2611 }
2612 }
2613
2614 pub fn set_show_edit_predictions(
2615 &mut self,
2616 show_edit_predictions: Option<bool>,
2617 window: &mut Window,
2618 cx: &mut Context<Self>,
2619 ) {
2620 self.show_inline_completions_override = show_edit_predictions;
2621 self.update_edit_prediction_settings(cx);
2622
2623 if let Some(false) = show_edit_predictions {
2624 self.discard_inline_completion(false, cx);
2625 } else {
2626 self.refresh_inline_completion(false, true, window, cx);
2627 }
2628 }
2629
2630 fn inline_completions_disabled_in_scope(
2631 &self,
2632 buffer: &Entity<Buffer>,
2633 buffer_position: language::Anchor,
2634 cx: &App,
2635 ) -> bool {
2636 let snapshot = buffer.read(cx).snapshot();
2637 let settings = snapshot.settings_at(buffer_position, cx);
2638
2639 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2640 return false;
2641 };
2642
2643 scope.override_name().map_or(false, |scope_name| {
2644 settings
2645 .edit_predictions_disabled_in
2646 .iter()
2647 .any(|s| s == scope_name)
2648 })
2649 }
2650
2651 pub fn set_use_modal_editing(&mut self, to: bool) {
2652 self.use_modal_editing = to;
2653 }
2654
2655 pub fn use_modal_editing(&self) -> bool {
2656 self.use_modal_editing
2657 }
2658
2659 fn selections_did_change(
2660 &mut self,
2661 local: bool,
2662 old_cursor_position: &Anchor,
2663 should_update_completions: bool,
2664 window: &mut Window,
2665 cx: &mut Context<Self>,
2666 ) {
2667 window.invalidate_character_coordinates();
2668
2669 // Copy selections to primary selection buffer
2670 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2671 if local {
2672 let selections = self.selections.all::<usize>(cx);
2673 let buffer_handle = self.buffer.read(cx).read(cx);
2674
2675 let mut text = String::new();
2676 for (index, selection) in selections.iter().enumerate() {
2677 let text_for_selection = buffer_handle
2678 .text_for_range(selection.start..selection.end)
2679 .collect::<String>();
2680
2681 text.push_str(&text_for_selection);
2682 if index != selections.len() - 1 {
2683 text.push('\n');
2684 }
2685 }
2686
2687 if !text.is_empty() {
2688 cx.write_to_primary(ClipboardItem::new_string(text));
2689 }
2690 }
2691
2692 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2693 self.buffer.update(cx, |buffer, cx| {
2694 buffer.set_active_selections(
2695 &self.selections.disjoint_anchors(),
2696 self.selections.line_mode,
2697 self.cursor_shape,
2698 cx,
2699 )
2700 });
2701 }
2702 let display_map = self
2703 .display_map
2704 .update(cx, |display_map, cx| display_map.snapshot(cx));
2705 let buffer = &display_map.buffer_snapshot;
2706 self.add_selections_state = None;
2707 self.select_next_state = None;
2708 self.select_prev_state = None;
2709 self.select_syntax_node_history.try_clear();
2710 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2711 self.snippet_stack
2712 .invalidate(&self.selections.disjoint_anchors(), buffer);
2713 self.take_rename(false, window, cx);
2714
2715 let new_cursor_position = self.selections.newest_anchor().head();
2716
2717 self.push_to_nav_history(
2718 *old_cursor_position,
2719 Some(new_cursor_position.to_point(buffer)),
2720 false,
2721 cx,
2722 );
2723
2724 if local {
2725 let new_cursor_position = self.selections.newest_anchor().head();
2726
2727 if let Some(buffer_id) = new_cursor_position.buffer_id {
2728 if !self.registered_buffers.contains_key(&buffer_id) {
2729 if let Some(project) = self.project.as_ref() {
2730 project.update(cx, |project, cx| {
2731 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2732 return;
2733 };
2734 self.registered_buffers.insert(
2735 buffer_id,
2736 project.register_buffer_with_language_servers(&buffer, cx),
2737 );
2738 })
2739 }
2740 }
2741 }
2742
2743 let mut context_menu = self.context_menu.borrow_mut();
2744 let completion_menu = match context_menu.as_ref() {
2745 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2746 Some(CodeContextMenu::CodeActions(_)) => {
2747 *context_menu = None;
2748 None
2749 }
2750 None => None,
2751 };
2752 let completion_position = completion_menu.map(|menu| menu.initial_position);
2753 drop(context_menu);
2754
2755 if should_update_completions {
2756 if let Some(completion_position) = completion_position {
2757 let new_cursor_offset = new_cursor_position.to_offset(buffer);
2758 let position_matches =
2759 new_cursor_offset == completion_position.to_offset(buffer);
2760 let continue_showing = if position_matches {
2761 let (word_range, kind) = buffer.surrounding_word(new_cursor_offset, true);
2762 if let Some(CharKind::Word) = kind {
2763 word_range.start < new_cursor_offset
2764 } else {
2765 false
2766 }
2767 } else {
2768 false
2769 };
2770
2771 if continue_showing {
2772 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2773 } else {
2774 self.hide_context_menu(window, cx);
2775 }
2776 }
2777 }
2778
2779 hide_hover(self, cx);
2780
2781 if old_cursor_position.to_display_point(&display_map).row()
2782 != new_cursor_position.to_display_point(&display_map).row()
2783 {
2784 self.available_code_actions.take();
2785 }
2786 self.refresh_code_actions(window, cx);
2787 self.refresh_document_highlights(cx);
2788 self.refresh_selected_text_highlights(false, window, cx);
2789 refresh_matching_bracket_highlights(self, window, cx);
2790 self.update_visible_inline_completion(window, cx);
2791 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2792 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2793 self.inline_blame_popover.take();
2794 if self.git_blame_inline_enabled {
2795 self.start_inline_blame_timer(window, cx);
2796 }
2797 }
2798
2799 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2800 cx.emit(EditorEvent::SelectionsChanged { local });
2801
2802 let selections = &self.selections.disjoint;
2803 if selections.len() == 1 {
2804 cx.emit(SearchEvent::ActiveMatchChanged)
2805 }
2806 if local {
2807 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2808 let inmemory_selections = selections
2809 .iter()
2810 .map(|s| {
2811 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2812 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2813 })
2814 .collect();
2815 self.update_restoration_data(cx, |data| {
2816 data.selections = inmemory_selections;
2817 });
2818
2819 if WorkspaceSettings::get(None, cx).restore_on_startup
2820 != RestoreOnStartupBehavior::None
2821 {
2822 if let Some(workspace_id) =
2823 self.workspace.as_ref().and_then(|workspace| workspace.1)
2824 {
2825 let snapshot = self.buffer().read(cx).snapshot(cx);
2826 let selections = selections.clone();
2827 let background_executor = cx.background_executor().clone();
2828 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2829 self.serialize_selections = cx.background_spawn(async move {
2830 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2831 let db_selections = selections
2832 .iter()
2833 .map(|selection| {
2834 (
2835 selection.start.to_offset(&snapshot),
2836 selection.end.to_offset(&snapshot),
2837 )
2838 })
2839 .collect();
2840
2841 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2842 .await
2843 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2844 .log_err();
2845 });
2846 }
2847 }
2848 }
2849 }
2850
2851 cx.notify();
2852 }
2853
2854 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2855 use text::ToOffset as _;
2856 use text::ToPoint as _;
2857
2858 if self.mode.is_minimap()
2859 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2860 {
2861 return;
2862 }
2863
2864 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2865 return;
2866 };
2867
2868 let snapshot = singleton.read(cx).snapshot();
2869 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2870 let display_snapshot = display_map.snapshot(cx);
2871
2872 display_snapshot
2873 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2874 .map(|fold| {
2875 fold.range.start.text_anchor.to_point(&snapshot)
2876 ..fold.range.end.text_anchor.to_point(&snapshot)
2877 })
2878 .collect()
2879 });
2880 self.update_restoration_data(cx, |data| {
2881 data.folds = inmemory_folds;
2882 });
2883
2884 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2885 return;
2886 };
2887 let background_executor = cx.background_executor().clone();
2888 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2889 let db_folds = self.display_map.update(cx, |display_map, cx| {
2890 display_map
2891 .snapshot(cx)
2892 .folds_in_range(0..snapshot.len())
2893 .map(|fold| {
2894 (
2895 fold.range.start.text_anchor.to_offset(&snapshot),
2896 fold.range.end.text_anchor.to_offset(&snapshot),
2897 )
2898 })
2899 .collect()
2900 });
2901 self.serialize_folds = cx.background_spawn(async move {
2902 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2903 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2904 .await
2905 .with_context(|| {
2906 format!(
2907 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2908 )
2909 })
2910 .log_err();
2911 });
2912 }
2913
2914 pub fn sync_selections(
2915 &mut self,
2916 other: Entity<Editor>,
2917 cx: &mut Context<Self>,
2918 ) -> gpui::Subscription {
2919 let other_selections = other.read(cx).selections.disjoint.to_vec();
2920 self.selections.change_with(cx, |selections| {
2921 selections.select_anchors(other_selections);
2922 });
2923
2924 let other_subscription =
2925 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2926 EditorEvent::SelectionsChanged { local: true } => {
2927 let other_selections = other.read(cx).selections.disjoint.to_vec();
2928 if other_selections.is_empty() {
2929 return;
2930 }
2931 this.selections.change_with(cx, |selections| {
2932 selections.select_anchors(other_selections);
2933 });
2934 }
2935 _ => {}
2936 });
2937
2938 let this_subscription =
2939 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2940 EditorEvent::SelectionsChanged { local: true } => {
2941 let these_selections = this.selections.disjoint.to_vec();
2942 if these_selections.is_empty() {
2943 return;
2944 }
2945 other.update(cx, |other_editor, cx| {
2946 other_editor.selections.change_with(cx, |selections| {
2947 selections.select_anchors(these_selections);
2948 })
2949 });
2950 }
2951 _ => {}
2952 });
2953
2954 Subscription::join(other_subscription, this_subscription)
2955 }
2956
2957 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
2958 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
2959 /// effects of selection change occur at the end of the transaction.
2960 pub fn change_selections<R>(
2961 &mut self,
2962 autoscroll: Option<Autoscroll>,
2963 window: &mut Window,
2964 cx: &mut Context<Self>,
2965 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2966 ) -> R {
2967 self.change_selections_inner(true, autoscroll, window, cx, change)
2968 }
2969
2970 pub(crate) fn change_selections_without_updating_completions<R>(
2971 &mut self,
2972 autoscroll: Option<Autoscroll>,
2973 window: &mut Window,
2974 cx: &mut Context<Self>,
2975 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2976 ) -> R {
2977 self.change_selections_inner(false, autoscroll, window, cx, change)
2978 }
2979
2980 fn change_selections_inner<R>(
2981 &mut self,
2982 should_update_completions: bool,
2983 autoscroll: Option<Autoscroll>,
2984 window: &mut Window,
2985 cx: &mut Context<Self>,
2986 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2987 ) -> R {
2988 if let Some(state) = &mut self.deferred_selection_effects_state {
2989 state.autoscroll = autoscroll.or(state.autoscroll);
2990 state.should_update_completions = should_update_completions;
2991 let (changed, result) = self.selections.change_with(cx, change);
2992 state.changed |= changed;
2993 return result;
2994 }
2995 let mut state = DeferredSelectionEffectsState {
2996 changed: false,
2997 should_update_completions,
2998 autoscroll,
2999 old_cursor_position: self.selections.newest_anchor().head(),
3000 history_entry: SelectionHistoryEntry {
3001 selections: self.selections.disjoint_anchors(),
3002 select_next_state: self.select_next_state.clone(),
3003 select_prev_state: self.select_prev_state.clone(),
3004 add_selections_state: self.add_selections_state.clone(),
3005 },
3006 };
3007 let (changed, result) = self.selections.change_with(cx, change);
3008 state.changed = state.changed || changed;
3009 if self.defer_selection_effects {
3010 self.deferred_selection_effects_state = Some(state);
3011 } else {
3012 self.apply_selection_effects(state, window, cx);
3013 }
3014 result
3015 }
3016
3017 /// Defers the effects of selection change, so that the effects of multiple calls to
3018 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3019 /// to selection history and the state of popovers based on selection position aren't
3020 /// erroneously updated.
3021 pub fn with_selection_effects_deferred<R>(
3022 &mut self,
3023 window: &mut Window,
3024 cx: &mut Context<Self>,
3025 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3026 ) -> R {
3027 let already_deferred = self.defer_selection_effects;
3028 self.defer_selection_effects = true;
3029 let result = update(self, window, cx);
3030 if !already_deferred {
3031 self.defer_selection_effects = false;
3032 if let Some(state) = self.deferred_selection_effects_state.take() {
3033 self.apply_selection_effects(state, window, cx);
3034 }
3035 }
3036 result
3037 }
3038
3039 fn apply_selection_effects(
3040 &mut self,
3041 state: DeferredSelectionEffectsState,
3042 window: &mut Window,
3043 cx: &mut Context<Self>,
3044 ) {
3045 if state.changed {
3046 self.selection_history.push(state.history_entry);
3047
3048 if let Some(autoscroll) = state.autoscroll {
3049 self.request_autoscroll(autoscroll, cx);
3050 }
3051
3052 let old_cursor_position = &state.old_cursor_position;
3053
3054 self.selections_did_change(
3055 true,
3056 &old_cursor_position,
3057 state.should_update_completions,
3058 window,
3059 cx,
3060 );
3061
3062 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3063 self.show_signature_help(&ShowSignatureHelp, window, cx);
3064 }
3065 }
3066 }
3067
3068 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3069 where
3070 I: IntoIterator<Item = (Range<S>, T)>,
3071 S: ToOffset,
3072 T: Into<Arc<str>>,
3073 {
3074 if self.read_only(cx) {
3075 return;
3076 }
3077
3078 self.buffer
3079 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3080 }
3081
3082 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3083 where
3084 I: IntoIterator<Item = (Range<S>, T)>,
3085 S: ToOffset,
3086 T: Into<Arc<str>>,
3087 {
3088 if self.read_only(cx) {
3089 return;
3090 }
3091
3092 self.buffer.update(cx, |buffer, cx| {
3093 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3094 });
3095 }
3096
3097 pub fn edit_with_block_indent<I, S, T>(
3098 &mut self,
3099 edits: I,
3100 original_indent_columns: Vec<Option<u32>>,
3101 cx: &mut Context<Self>,
3102 ) where
3103 I: IntoIterator<Item = (Range<S>, T)>,
3104 S: ToOffset,
3105 T: Into<Arc<str>>,
3106 {
3107 if self.read_only(cx) {
3108 return;
3109 }
3110
3111 self.buffer.update(cx, |buffer, cx| {
3112 buffer.edit(
3113 edits,
3114 Some(AutoindentMode::Block {
3115 original_indent_columns,
3116 }),
3117 cx,
3118 )
3119 });
3120 }
3121
3122 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3123 self.hide_context_menu(window, cx);
3124
3125 match phase {
3126 SelectPhase::Begin {
3127 position,
3128 add,
3129 click_count,
3130 } => self.begin_selection(position, add, click_count, window, cx),
3131 SelectPhase::BeginColumnar {
3132 position,
3133 goal_column,
3134 reset,
3135 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3136 SelectPhase::Extend {
3137 position,
3138 click_count,
3139 } => self.extend_selection(position, click_count, window, cx),
3140 SelectPhase::Update {
3141 position,
3142 goal_column,
3143 scroll_delta,
3144 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3145 SelectPhase::End => self.end_selection(window, cx),
3146 }
3147 }
3148
3149 fn extend_selection(
3150 &mut self,
3151 position: DisplayPoint,
3152 click_count: usize,
3153 window: &mut Window,
3154 cx: &mut Context<Self>,
3155 ) {
3156 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3157 let tail = self.selections.newest::<usize>(cx).tail();
3158 self.begin_selection(position, false, click_count, window, cx);
3159
3160 let position = position.to_offset(&display_map, Bias::Left);
3161 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3162
3163 let mut pending_selection = self
3164 .selections
3165 .pending_anchor()
3166 .expect("extend_selection not called with pending selection");
3167 if position >= tail {
3168 pending_selection.start = tail_anchor;
3169 } else {
3170 pending_selection.end = tail_anchor;
3171 pending_selection.reversed = true;
3172 }
3173
3174 let mut pending_mode = self.selections.pending_mode().unwrap();
3175 match &mut pending_mode {
3176 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3177 _ => {}
3178 }
3179
3180 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3181
3182 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3183 s.set_pending(pending_selection, pending_mode)
3184 });
3185 }
3186
3187 fn begin_selection(
3188 &mut self,
3189 position: DisplayPoint,
3190 add: bool,
3191 click_count: usize,
3192 window: &mut Window,
3193 cx: &mut Context<Self>,
3194 ) {
3195 if !self.focus_handle.is_focused(window) {
3196 self.last_focused_descendant = None;
3197 window.focus(&self.focus_handle);
3198 }
3199
3200 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3201 let buffer = &display_map.buffer_snapshot;
3202 let position = display_map.clip_point(position, Bias::Left);
3203
3204 let start;
3205 let end;
3206 let mode;
3207 let mut auto_scroll;
3208 match click_count {
3209 1 => {
3210 start = buffer.anchor_before(position.to_point(&display_map));
3211 end = start;
3212 mode = SelectMode::Character;
3213 auto_scroll = true;
3214 }
3215 2 => {
3216 let range = movement::surrounding_word(&display_map, position);
3217 start = buffer.anchor_before(range.start.to_point(&display_map));
3218 end = buffer.anchor_before(range.end.to_point(&display_map));
3219 mode = SelectMode::Word(start..end);
3220 auto_scroll = true;
3221 }
3222 3 => {
3223 let position = display_map
3224 .clip_point(position, Bias::Left)
3225 .to_point(&display_map);
3226 let line_start = display_map.prev_line_boundary(position).0;
3227 let next_line_start = buffer.clip_point(
3228 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3229 Bias::Left,
3230 );
3231 start = buffer.anchor_before(line_start);
3232 end = buffer.anchor_before(next_line_start);
3233 mode = SelectMode::Line(start..end);
3234 auto_scroll = true;
3235 }
3236 _ => {
3237 start = buffer.anchor_before(0);
3238 end = buffer.anchor_before(buffer.len());
3239 mode = SelectMode::All;
3240 auto_scroll = false;
3241 }
3242 }
3243 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3244
3245 let point_to_delete: Option<usize> = {
3246 let selected_points: Vec<Selection<Point>> =
3247 self.selections.disjoint_in_range(start..end, cx);
3248
3249 if !add || click_count > 1 {
3250 None
3251 } else if !selected_points.is_empty() {
3252 Some(selected_points[0].id)
3253 } else {
3254 let clicked_point_already_selected =
3255 self.selections.disjoint.iter().find(|selection| {
3256 selection.start.to_point(buffer) == start.to_point(buffer)
3257 || selection.end.to_point(buffer) == end.to_point(buffer)
3258 });
3259
3260 clicked_point_already_selected.map(|selection| selection.id)
3261 }
3262 };
3263
3264 let selections_count = self.selections.count();
3265
3266 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3267 if let Some(point_to_delete) = point_to_delete {
3268 s.delete(point_to_delete);
3269
3270 if selections_count == 1 {
3271 s.set_pending_anchor_range(start..end, mode);
3272 }
3273 } else {
3274 if !add {
3275 s.clear_disjoint();
3276 }
3277
3278 s.set_pending_anchor_range(start..end, mode);
3279 }
3280 });
3281 }
3282
3283 fn begin_columnar_selection(
3284 &mut self,
3285 position: DisplayPoint,
3286 goal_column: u32,
3287 reset: bool,
3288 window: &mut Window,
3289 cx: &mut Context<Self>,
3290 ) {
3291 if !self.focus_handle.is_focused(window) {
3292 self.last_focused_descendant = None;
3293 window.focus(&self.focus_handle);
3294 }
3295
3296 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3297
3298 if reset {
3299 let pointer_position = display_map
3300 .buffer_snapshot
3301 .anchor_before(position.to_point(&display_map));
3302
3303 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3304 s.clear_disjoint();
3305 s.set_pending_anchor_range(
3306 pointer_position..pointer_position,
3307 SelectMode::Character,
3308 );
3309 });
3310 if position.column() != goal_column {
3311 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3312 } else {
3313 self.columnar_display_point = None;
3314 }
3315 }
3316
3317 let tail = self.selections.newest::<Point>(cx).tail();
3318 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3319
3320 if !reset {
3321 self.columnar_display_point = None;
3322 self.select_columns(
3323 tail.to_display_point(&display_map),
3324 position,
3325 goal_column,
3326 &display_map,
3327 window,
3328 cx,
3329 );
3330 }
3331 }
3332
3333 fn update_selection(
3334 &mut self,
3335 position: DisplayPoint,
3336 goal_column: u32,
3337 scroll_delta: gpui::Point<f32>,
3338 window: &mut Window,
3339 cx: &mut Context<Self>,
3340 ) {
3341 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3342
3343 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3344 let tail = self
3345 .columnar_display_point
3346 .unwrap_or_else(|| tail.to_display_point(&display_map));
3347 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3348 } else if let Some(mut pending) = self.selections.pending_anchor() {
3349 let buffer = self.buffer.read(cx).snapshot(cx);
3350 let head;
3351 let tail;
3352 let mode = self.selections.pending_mode().unwrap();
3353 match &mode {
3354 SelectMode::Character => {
3355 head = position.to_point(&display_map);
3356 tail = pending.tail().to_point(&buffer);
3357 }
3358 SelectMode::Word(original_range) => {
3359 let original_display_range = original_range.start.to_display_point(&display_map)
3360 ..original_range.end.to_display_point(&display_map);
3361 let original_buffer_range = original_display_range.start.to_point(&display_map)
3362 ..original_display_range.end.to_point(&display_map);
3363 if movement::is_inside_word(&display_map, position)
3364 || original_display_range.contains(&position)
3365 {
3366 let word_range = movement::surrounding_word(&display_map, position);
3367 if word_range.start < original_display_range.start {
3368 head = word_range.start.to_point(&display_map);
3369 } else {
3370 head = word_range.end.to_point(&display_map);
3371 }
3372 } else {
3373 head = position.to_point(&display_map);
3374 }
3375
3376 if head <= original_buffer_range.start {
3377 tail = original_buffer_range.end;
3378 } else {
3379 tail = original_buffer_range.start;
3380 }
3381 }
3382 SelectMode::Line(original_range) => {
3383 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3384
3385 let position = display_map
3386 .clip_point(position, Bias::Left)
3387 .to_point(&display_map);
3388 let line_start = display_map.prev_line_boundary(position).0;
3389 let next_line_start = buffer.clip_point(
3390 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3391 Bias::Left,
3392 );
3393
3394 if line_start < original_range.start {
3395 head = line_start
3396 } else {
3397 head = next_line_start
3398 }
3399
3400 if head <= original_range.start {
3401 tail = original_range.end;
3402 } else {
3403 tail = original_range.start;
3404 }
3405 }
3406 SelectMode::All => {
3407 return;
3408 }
3409 };
3410
3411 if head < tail {
3412 pending.start = buffer.anchor_before(head);
3413 pending.end = buffer.anchor_before(tail);
3414 pending.reversed = true;
3415 } else {
3416 pending.start = buffer.anchor_before(tail);
3417 pending.end = buffer.anchor_before(head);
3418 pending.reversed = false;
3419 }
3420
3421 self.change_selections(None, window, cx, |s| {
3422 s.set_pending(pending, mode);
3423 });
3424 } else {
3425 log::error!("update_selection dispatched with no pending selection");
3426 return;
3427 }
3428
3429 self.apply_scroll_delta(scroll_delta, window, cx);
3430 cx.notify();
3431 }
3432
3433 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3434 self.columnar_selection_tail.take();
3435 if self.selections.pending_anchor().is_some() {
3436 let selections = self.selections.all::<usize>(cx);
3437 self.change_selections(None, window, cx, |s| {
3438 s.select(selections);
3439 s.clear_pending();
3440 });
3441 }
3442 }
3443
3444 fn select_columns(
3445 &mut self,
3446 tail: DisplayPoint,
3447 head: DisplayPoint,
3448 goal_column: u32,
3449 display_map: &DisplaySnapshot,
3450 window: &mut Window,
3451 cx: &mut Context<Self>,
3452 ) {
3453 let start_row = cmp::min(tail.row(), head.row());
3454 let end_row = cmp::max(tail.row(), head.row());
3455 let start_column = cmp::min(tail.column(), goal_column);
3456 let end_column = cmp::max(tail.column(), goal_column);
3457 let reversed = start_column < tail.column();
3458
3459 let selection_ranges = (start_row.0..=end_row.0)
3460 .map(DisplayRow)
3461 .filter_map(|row| {
3462 if !display_map.is_block_line(row) {
3463 let start = display_map
3464 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3465 .to_point(display_map);
3466 let end = display_map
3467 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3468 .to_point(display_map);
3469 if reversed {
3470 Some(end..start)
3471 } else {
3472 Some(start..end)
3473 }
3474 } else {
3475 None
3476 }
3477 })
3478 .collect::<Vec<_>>();
3479
3480 let mut non_empty_ranges = selection_ranges
3481 .iter()
3482 .filter(|selection_range| selection_range.start != selection_range.end)
3483 .peekable();
3484
3485 let ranges = if non_empty_ranges.peek().is_some() {
3486 non_empty_ranges.cloned().collect()
3487 } else {
3488 selection_ranges
3489 };
3490
3491 self.change_selections(None, window, cx, |s| {
3492 s.select_ranges(ranges);
3493 });
3494 cx.notify();
3495 }
3496
3497 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3498 self.selections
3499 .all_adjusted(cx)
3500 .iter()
3501 .any(|selection| !selection.is_empty())
3502 }
3503
3504 pub fn has_pending_nonempty_selection(&self) -> bool {
3505 let pending_nonempty_selection = match self.selections.pending_anchor() {
3506 Some(Selection { start, end, .. }) => start != end,
3507 None => false,
3508 };
3509
3510 pending_nonempty_selection
3511 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3512 }
3513
3514 pub fn has_pending_selection(&self) -> bool {
3515 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3516 }
3517
3518 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3519 self.selection_mark_mode = false;
3520
3521 if self.clear_expanded_diff_hunks(cx) {
3522 cx.notify();
3523 return;
3524 }
3525 if self.dismiss_menus_and_popups(true, window, cx) {
3526 return;
3527 }
3528
3529 if self.mode.is_full()
3530 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3531 {
3532 return;
3533 }
3534
3535 cx.propagate();
3536 }
3537
3538 pub fn dismiss_menus_and_popups(
3539 &mut self,
3540 is_user_requested: bool,
3541 window: &mut Window,
3542 cx: &mut Context<Self>,
3543 ) -> bool {
3544 if self.take_rename(false, window, cx).is_some() {
3545 return true;
3546 }
3547
3548 if hide_hover(self, cx) {
3549 return true;
3550 }
3551
3552 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3553 return true;
3554 }
3555
3556 if self.hide_context_menu(window, cx).is_some() {
3557 return true;
3558 }
3559
3560 if self.mouse_context_menu.take().is_some() {
3561 return true;
3562 }
3563
3564 if is_user_requested && self.discard_inline_completion(true, cx) {
3565 return true;
3566 }
3567
3568 if self.snippet_stack.pop().is_some() {
3569 return true;
3570 }
3571
3572 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3573 self.dismiss_diagnostics(cx);
3574 return true;
3575 }
3576
3577 false
3578 }
3579
3580 fn linked_editing_ranges_for(
3581 &self,
3582 selection: Range<text::Anchor>,
3583 cx: &App,
3584 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3585 if self.linked_edit_ranges.is_empty() {
3586 return None;
3587 }
3588 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3589 selection.end.buffer_id.and_then(|end_buffer_id| {
3590 if selection.start.buffer_id != Some(end_buffer_id) {
3591 return None;
3592 }
3593 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3594 let snapshot = buffer.read(cx).snapshot();
3595 self.linked_edit_ranges
3596 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3597 .map(|ranges| (ranges, snapshot, buffer))
3598 })?;
3599 use text::ToOffset as TO;
3600 // find offset from the start of current range to current cursor position
3601 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3602
3603 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3604 let start_difference = start_offset - start_byte_offset;
3605 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3606 let end_difference = end_offset - start_byte_offset;
3607 // Current range has associated linked ranges.
3608 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3609 for range in linked_ranges.iter() {
3610 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3611 let end_offset = start_offset + end_difference;
3612 let start_offset = start_offset + start_difference;
3613 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3614 continue;
3615 }
3616 if self.selections.disjoint_anchor_ranges().any(|s| {
3617 if s.start.buffer_id != selection.start.buffer_id
3618 || s.end.buffer_id != selection.end.buffer_id
3619 {
3620 return false;
3621 }
3622 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3623 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3624 }) {
3625 continue;
3626 }
3627 let start = buffer_snapshot.anchor_after(start_offset);
3628 let end = buffer_snapshot.anchor_after(end_offset);
3629 linked_edits
3630 .entry(buffer.clone())
3631 .or_default()
3632 .push(start..end);
3633 }
3634 Some(linked_edits)
3635 }
3636
3637 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3638 let text: Arc<str> = text.into();
3639
3640 if self.read_only(cx) {
3641 return;
3642 }
3643
3644 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3645
3646 let selections = self.selections.all_adjusted(cx);
3647 let mut bracket_inserted = false;
3648 let mut edits = Vec::new();
3649 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3650 let mut new_selections = Vec::with_capacity(selections.len());
3651 let mut new_autoclose_regions = Vec::new();
3652 let snapshot = self.buffer.read(cx).read(cx);
3653 let mut clear_linked_edit_ranges = false;
3654
3655 for (selection, autoclose_region) in
3656 self.selections_with_autoclose_regions(selections, &snapshot)
3657 {
3658 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3659 // Determine if the inserted text matches the opening or closing
3660 // bracket of any of this language's bracket pairs.
3661 let mut bracket_pair = None;
3662 let mut is_bracket_pair_start = false;
3663 let mut is_bracket_pair_end = false;
3664 if !text.is_empty() {
3665 let mut bracket_pair_matching_end = None;
3666 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3667 // and they are removing the character that triggered IME popup.
3668 for (pair, enabled) in scope.brackets() {
3669 if !pair.close && !pair.surround {
3670 continue;
3671 }
3672
3673 if enabled && pair.start.ends_with(text.as_ref()) {
3674 let prefix_len = pair.start.len() - text.len();
3675 let preceding_text_matches_prefix = prefix_len == 0
3676 || (selection.start.column >= (prefix_len as u32)
3677 && snapshot.contains_str_at(
3678 Point::new(
3679 selection.start.row,
3680 selection.start.column - (prefix_len as u32),
3681 ),
3682 &pair.start[..prefix_len],
3683 ));
3684 if preceding_text_matches_prefix {
3685 bracket_pair = Some(pair.clone());
3686 is_bracket_pair_start = true;
3687 break;
3688 }
3689 }
3690 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3691 {
3692 // take first bracket pair matching end, but don't break in case a later bracket
3693 // pair matches start
3694 bracket_pair_matching_end = Some(pair.clone());
3695 }
3696 }
3697 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3698 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3699 is_bracket_pair_end = true;
3700 }
3701 }
3702
3703 if let Some(bracket_pair) = bracket_pair {
3704 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3705 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3706 let auto_surround =
3707 self.use_auto_surround && snapshot_settings.use_auto_surround;
3708 if selection.is_empty() {
3709 if is_bracket_pair_start {
3710 // If the inserted text is a suffix of an opening bracket and the
3711 // selection is preceded by the rest of the opening bracket, then
3712 // insert the closing bracket.
3713 let following_text_allows_autoclose = snapshot
3714 .chars_at(selection.start)
3715 .next()
3716 .map_or(true, |c| scope.should_autoclose_before(c));
3717
3718 let preceding_text_allows_autoclose = selection.start.column == 0
3719 || snapshot.reversed_chars_at(selection.start).next().map_or(
3720 true,
3721 |c| {
3722 bracket_pair.start != bracket_pair.end
3723 || !snapshot
3724 .char_classifier_at(selection.start)
3725 .is_word(c)
3726 },
3727 );
3728
3729 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3730 && bracket_pair.start.len() == 1
3731 {
3732 let target = bracket_pair.start.chars().next().unwrap();
3733 let current_line_count = snapshot
3734 .reversed_chars_at(selection.start)
3735 .take_while(|&c| c != '\n')
3736 .filter(|&c| c == target)
3737 .count();
3738 current_line_count % 2 == 1
3739 } else {
3740 false
3741 };
3742
3743 if autoclose
3744 && bracket_pair.close
3745 && following_text_allows_autoclose
3746 && preceding_text_allows_autoclose
3747 && !is_closing_quote
3748 {
3749 let anchor = snapshot.anchor_before(selection.end);
3750 new_selections.push((selection.map(|_| anchor), text.len()));
3751 new_autoclose_regions.push((
3752 anchor,
3753 text.len(),
3754 selection.id,
3755 bracket_pair.clone(),
3756 ));
3757 edits.push((
3758 selection.range(),
3759 format!("{}{}", text, bracket_pair.end).into(),
3760 ));
3761 bracket_inserted = true;
3762 continue;
3763 }
3764 }
3765
3766 if let Some(region) = autoclose_region {
3767 // If the selection is followed by an auto-inserted closing bracket,
3768 // then don't insert that closing bracket again; just move the selection
3769 // past the closing bracket.
3770 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3771 && text.as_ref() == region.pair.end.as_str();
3772 if should_skip {
3773 let anchor = snapshot.anchor_after(selection.end);
3774 new_selections
3775 .push((selection.map(|_| anchor), region.pair.end.len()));
3776 continue;
3777 }
3778 }
3779
3780 let always_treat_brackets_as_autoclosed = snapshot
3781 .language_settings_at(selection.start, cx)
3782 .always_treat_brackets_as_autoclosed;
3783 if always_treat_brackets_as_autoclosed
3784 && is_bracket_pair_end
3785 && snapshot.contains_str_at(selection.end, text.as_ref())
3786 {
3787 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3788 // and the inserted text is a closing bracket and the selection is followed
3789 // by the closing bracket then move the selection past the closing bracket.
3790 let anchor = snapshot.anchor_after(selection.end);
3791 new_selections.push((selection.map(|_| anchor), text.len()));
3792 continue;
3793 }
3794 }
3795 // If an opening bracket is 1 character long and is typed while
3796 // text is selected, then surround that text with the bracket pair.
3797 else if auto_surround
3798 && bracket_pair.surround
3799 && is_bracket_pair_start
3800 && bracket_pair.start.chars().count() == 1
3801 {
3802 edits.push((selection.start..selection.start, text.clone()));
3803 edits.push((
3804 selection.end..selection.end,
3805 bracket_pair.end.as_str().into(),
3806 ));
3807 bracket_inserted = true;
3808 new_selections.push((
3809 Selection {
3810 id: selection.id,
3811 start: snapshot.anchor_after(selection.start),
3812 end: snapshot.anchor_before(selection.end),
3813 reversed: selection.reversed,
3814 goal: selection.goal,
3815 },
3816 0,
3817 ));
3818 continue;
3819 }
3820 }
3821 }
3822
3823 if self.auto_replace_emoji_shortcode
3824 && selection.is_empty()
3825 && text.as_ref().ends_with(':')
3826 {
3827 if let Some(possible_emoji_short_code) =
3828 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3829 {
3830 if !possible_emoji_short_code.is_empty() {
3831 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3832 let emoji_shortcode_start = Point::new(
3833 selection.start.row,
3834 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3835 );
3836
3837 // Remove shortcode from buffer
3838 edits.push((
3839 emoji_shortcode_start..selection.start,
3840 "".to_string().into(),
3841 ));
3842 new_selections.push((
3843 Selection {
3844 id: selection.id,
3845 start: snapshot.anchor_after(emoji_shortcode_start),
3846 end: snapshot.anchor_before(selection.start),
3847 reversed: selection.reversed,
3848 goal: selection.goal,
3849 },
3850 0,
3851 ));
3852
3853 // Insert emoji
3854 let selection_start_anchor = snapshot.anchor_after(selection.start);
3855 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3856 edits.push((selection.start..selection.end, emoji.to_string().into()));
3857
3858 continue;
3859 }
3860 }
3861 }
3862 }
3863
3864 // If not handling any auto-close operation, then just replace the selected
3865 // text with the given input and move the selection to the end of the
3866 // newly inserted text.
3867 let anchor = snapshot.anchor_after(selection.end);
3868 if !self.linked_edit_ranges.is_empty() {
3869 let start_anchor = snapshot.anchor_before(selection.start);
3870
3871 let is_word_char = text.chars().next().map_or(true, |char| {
3872 let classifier = snapshot
3873 .char_classifier_at(start_anchor.to_offset(&snapshot))
3874 .ignore_punctuation(true);
3875 classifier.is_word(char)
3876 });
3877
3878 if is_word_char {
3879 if let Some(ranges) = self
3880 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3881 {
3882 for (buffer, edits) in ranges {
3883 linked_edits
3884 .entry(buffer.clone())
3885 .or_default()
3886 .extend(edits.into_iter().map(|range| (range, text.clone())));
3887 }
3888 }
3889 } else {
3890 clear_linked_edit_ranges = true;
3891 }
3892 }
3893
3894 new_selections.push((selection.map(|_| anchor), 0));
3895 edits.push((selection.start..selection.end, text.clone()));
3896 }
3897
3898 drop(snapshot);
3899
3900 self.transact(window, cx, |this, window, cx| {
3901 if clear_linked_edit_ranges {
3902 this.linked_edit_ranges.clear();
3903 }
3904 let initial_buffer_versions =
3905 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3906
3907 this.buffer.update(cx, |buffer, cx| {
3908 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3909 });
3910 for (buffer, edits) in linked_edits {
3911 buffer.update(cx, |buffer, cx| {
3912 let snapshot = buffer.snapshot();
3913 let edits = edits
3914 .into_iter()
3915 .map(|(range, text)| {
3916 use text::ToPoint as TP;
3917 let end_point = TP::to_point(&range.end, &snapshot);
3918 let start_point = TP::to_point(&range.start, &snapshot);
3919 (start_point..end_point, text)
3920 })
3921 .sorted_by_key(|(range, _)| range.start);
3922 buffer.edit(edits, None, cx);
3923 })
3924 }
3925 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3926 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3927 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3928 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3929 .zip(new_selection_deltas)
3930 .map(|(selection, delta)| Selection {
3931 id: selection.id,
3932 start: selection.start + delta,
3933 end: selection.end + delta,
3934 reversed: selection.reversed,
3935 goal: SelectionGoal::None,
3936 })
3937 .collect::<Vec<_>>();
3938
3939 let mut i = 0;
3940 for (position, delta, selection_id, pair) in new_autoclose_regions {
3941 let position = position.to_offset(&map.buffer_snapshot) + delta;
3942 let start = map.buffer_snapshot.anchor_before(position);
3943 let end = map.buffer_snapshot.anchor_after(position);
3944 while let Some(existing_state) = this.autoclose_regions.get(i) {
3945 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3946 Ordering::Less => i += 1,
3947 Ordering::Greater => break,
3948 Ordering::Equal => {
3949 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3950 Ordering::Less => i += 1,
3951 Ordering::Equal => break,
3952 Ordering::Greater => break,
3953 }
3954 }
3955 }
3956 }
3957 this.autoclose_regions.insert(
3958 i,
3959 AutocloseRegion {
3960 selection_id,
3961 range: start..end,
3962 pair,
3963 },
3964 );
3965 }
3966
3967 let had_active_inline_completion = this.has_active_inline_completion();
3968 this.change_selections_without_updating_completions(
3969 Some(Autoscroll::fit()),
3970 window,
3971 cx,
3972 |s| s.select(new_selections),
3973 );
3974
3975 if !bracket_inserted {
3976 if let Some(on_type_format_task) =
3977 this.trigger_on_type_formatting(text.to_string(), window, cx)
3978 {
3979 on_type_format_task.detach_and_log_err(cx);
3980 }
3981 }
3982
3983 let editor_settings = EditorSettings::get_global(cx);
3984 if bracket_inserted
3985 && (editor_settings.auto_signature_help
3986 || editor_settings.show_signature_help_after_edits)
3987 {
3988 this.show_signature_help(&ShowSignatureHelp, window, cx);
3989 }
3990
3991 let trigger_in_words =
3992 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3993 if this.hard_wrap.is_some() {
3994 let latest: Range<Point> = this.selections.newest(cx).range();
3995 if latest.is_empty()
3996 && this
3997 .buffer()
3998 .read(cx)
3999 .snapshot(cx)
4000 .line_len(MultiBufferRow(latest.start.row))
4001 == latest.start.column
4002 {
4003 this.rewrap_impl(
4004 RewrapOptions {
4005 override_language_settings: true,
4006 preserve_existing_whitespace: true,
4007 },
4008 cx,
4009 )
4010 }
4011 }
4012 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4013 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4014 this.refresh_inline_completion(true, false, window, cx);
4015 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4016 });
4017 }
4018
4019 fn find_possible_emoji_shortcode_at_position(
4020 snapshot: &MultiBufferSnapshot,
4021 position: Point,
4022 ) -> Option<String> {
4023 let mut chars = Vec::new();
4024 let mut found_colon = false;
4025 for char in snapshot.reversed_chars_at(position).take(100) {
4026 // Found a possible emoji shortcode in the middle of the buffer
4027 if found_colon {
4028 if char.is_whitespace() {
4029 chars.reverse();
4030 return Some(chars.iter().collect());
4031 }
4032 // If the previous character is not a whitespace, we are in the middle of a word
4033 // and we only want to complete the shortcode if the word is made up of other emojis
4034 let mut containing_word = String::new();
4035 for ch in snapshot
4036 .reversed_chars_at(position)
4037 .skip(chars.len() + 1)
4038 .take(100)
4039 {
4040 if ch.is_whitespace() {
4041 break;
4042 }
4043 containing_word.push(ch);
4044 }
4045 let containing_word = containing_word.chars().rev().collect::<String>();
4046 if util::word_consists_of_emojis(containing_word.as_str()) {
4047 chars.reverse();
4048 return Some(chars.iter().collect());
4049 }
4050 }
4051
4052 if char.is_whitespace() || !char.is_ascii() {
4053 return None;
4054 }
4055 if char == ':' {
4056 found_colon = true;
4057 } else {
4058 chars.push(char);
4059 }
4060 }
4061 // Found a possible emoji shortcode at the beginning of the buffer
4062 chars.reverse();
4063 Some(chars.iter().collect())
4064 }
4065
4066 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4067 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4068 self.transact(window, cx, |this, window, cx| {
4069 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4070 let selections = this.selections.all::<usize>(cx);
4071 let multi_buffer = this.buffer.read(cx);
4072 let buffer = multi_buffer.snapshot(cx);
4073 selections
4074 .iter()
4075 .map(|selection| {
4076 let start_point = selection.start.to_point(&buffer);
4077 let mut existing_indent =
4078 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4079 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4080 let start = selection.start;
4081 let end = selection.end;
4082 let selection_is_empty = start == end;
4083 let language_scope = buffer.language_scope_at(start);
4084 let (
4085 comment_delimiter,
4086 doc_delimiter,
4087 insert_extra_newline,
4088 indent_on_newline,
4089 indent_on_extra_newline,
4090 ) = if let Some(language) = &language_scope {
4091 let mut insert_extra_newline =
4092 insert_extra_newline_brackets(&buffer, start..end, language)
4093 || insert_extra_newline_tree_sitter(&buffer, start..end);
4094
4095 // Comment extension on newline is allowed only for cursor selections
4096 let comment_delimiter = maybe!({
4097 if !selection_is_empty {
4098 return None;
4099 }
4100
4101 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4102 return None;
4103 }
4104
4105 let delimiters = language.line_comment_prefixes();
4106 let max_len_of_delimiter =
4107 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4108 let (snapshot, range) =
4109 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4110
4111 let num_of_whitespaces = snapshot
4112 .chars_for_range(range.clone())
4113 .take_while(|c| c.is_whitespace())
4114 .count();
4115 let comment_candidate = snapshot
4116 .chars_for_range(range)
4117 .skip(num_of_whitespaces)
4118 .take(max_len_of_delimiter)
4119 .collect::<String>();
4120 let (delimiter, trimmed_len) = delimiters
4121 .iter()
4122 .filter_map(|delimiter| {
4123 let prefix = delimiter.trim_end();
4124 if comment_candidate.starts_with(prefix) {
4125 Some((delimiter, prefix.len()))
4126 } else {
4127 None
4128 }
4129 })
4130 .max_by_key(|(_, len)| *len)?;
4131
4132 let cursor_is_placed_after_comment_marker =
4133 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4134 if cursor_is_placed_after_comment_marker {
4135 Some(delimiter.clone())
4136 } else {
4137 None
4138 }
4139 });
4140
4141 let mut indent_on_newline = IndentSize::spaces(0);
4142 let mut indent_on_extra_newline = IndentSize::spaces(0);
4143
4144 let doc_delimiter = maybe!({
4145 if !selection_is_empty {
4146 return None;
4147 }
4148
4149 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4150 return None;
4151 }
4152
4153 let DocumentationConfig {
4154 start: start_tag,
4155 end: end_tag,
4156 prefix: delimiter,
4157 tab_size: len,
4158 } = language.documentation()?;
4159
4160 let is_within_block_comment = buffer
4161 .language_scope_at(start_point)
4162 .is_some_and(|scope| scope.override_name() == Some("comment"));
4163 if !is_within_block_comment {
4164 return None;
4165 }
4166
4167 let (snapshot, range) =
4168 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4169
4170 let num_of_whitespaces = snapshot
4171 .chars_for_range(range.clone())
4172 .take_while(|c| c.is_whitespace())
4173 .count();
4174
4175 // 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.
4176 let column = start_point.column;
4177 let cursor_is_after_start_tag = {
4178 let start_tag_len = start_tag.len();
4179 let start_tag_line = snapshot
4180 .chars_for_range(range.clone())
4181 .skip(num_of_whitespaces)
4182 .take(start_tag_len)
4183 .collect::<String>();
4184 if start_tag_line.starts_with(start_tag.as_ref()) {
4185 num_of_whitespaces + start_tag_len <= column as usize
4186 } else {
4187 false
4188 }
4189 };
4190
4191 let cursor_is_after_delimiter = {
4192 let delimiter_trim = delimiter.trim_end();
4193 let delimiter_line = snapshot
4194 .chars_for_range(range.clone())
4195 .skip(num_of_whitespaces)
4196 .take(delimiter_trim.len())
4197 .collect::<String>();
4198 if delimiter_line.starts_with(delimiter_trim) {
4199 num_of_whitespaces + delimiter_trim.len() <= column as usize
4200 } else {
4201 false
4202 }
4203 };
4204
4205 let cursor_is_before_end_tag_if_exists = {
4206 let mut char_position = 0u32;
4207 let mut end_tag_offset = None;
4208
4209 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4210 if let Some(byte_pos) = chunk.find(&**end_tag) {
4211 let chars_before_match =
4212 chunk[..byte_pos].chars().count() as u32;
4213 end_tag_offset =
4214 Some(char_position + chars_before_match);
4215 break 'outer;
4216 }
4217 char_position += chunk.chars().count() as u32;
4218 }
4219
4220 if let Some(end_tag_offset) = end_tag_offset {
4221 let cursor_is_before_end_tag = column <= end_tag_offset;
4222 if cursor_is_after_start_tag {
4223 if cursor_is_before_end_tag {
4224 insert_extra_newline = true;
4225 }
4226 let cursor_is_at_start_of_end_tag =
4227 column == end_tag_offset;
4228 if cursor_is_at_start_of_end_tag {
4229 indent_on_extra_newline.len = (*len).into();
4230 }
4231 }
4232 cursor_is_before_end_tag
4233 } else {
4234 true
4235 }
4236 };
4237
4238 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4239 && cursor_is_before_end_tag_if_exists
4240 {
4241 if cursor_is_after_start_tag {
4242 indent_on_newline.len = (*len).into();
4243 }
4244 Some(delimiter.clone())
4245 } else {
4246 None
4247 }
4248 });
4249
4250 (
4251 comment_delimiter,
4252 doc_delimiter,
4253 insert_extra_newline,
4254 indent_on_newline,
4255 indent_on_extra_newline,
4256 )
4257 } else {
4258 (
4259 None,
4260 None,
4261 false,
4262 IndentSize::default(),
4263 IndentSize::default(),
4264 )
4265 };
4266
4267 let prevent_auto_indent = doc_delimiter.is_some();
4268 let delimiter = comment_delimiter.or(doc_delimiter);
4269
4270 let capacity_for_delimiter =
4271 delimiter.as_deref().map(str::len).unwrap_or_default();
4272 let mut new_text = String::with_capacity(
4273 1 + capacity_for_delimiter
4274 + existing_indent.len as usize
4275 + indent_on_newline.len as usize
4276 + indent_on_extra_newline.len as usize,
4277 );
4278 new_text.push('\n');
4279 new_text.extend(existing_indent.chars());
4280 new_text.extend(indent_on_newline.chars());
4281
4282 if let Some(delimiter) = &delimiter {
4283 new_text.push_str(delimiter);
4284 }
4285
4286 if insert_extra_newline {
4287 new_text.push('\n');
4288 new_text.extend(existing_indent.chars());
4289 new_text.extend(indent_on_extra_newline.chars());
4290 }
4291
4292 let anchor = buffer.anchor_after(end);
4293 let new_selection = selection.map(|_| anchor);
4294 (
4295 ((start..end, new_text), prevent_auto_indent),
4296 (insert_extra_newline, new_selection),
4297 )
4298 })
4299 .unzip()
4300 };
4301
4302 let mut auto_indent_edits = Vec::new();
4303 let mut edits = Vec::new();
4304 for (edit, prevent_auto_indent) in edits_with_flags {
4305 if prevent_auto_indent {
4306 edits.push(edit);
4307 } else {
4308 auto_indent_edits.push(edit);
4309 }
4310 }
4311 if !edits.is_empty() {
4312 this.edit(edits, cx);
4313 }
4314 if !auto_indent_edits.is_empty() {
4315 this.edit_with_autoindent(auto_indent_edits, cx);
4316 }
4317
4318 let buffer = this.buffer.read(cx).snapshot(cx);
4319 let new_selections = selection_info
4320 .into_iter()
4321 .map(|(extra_newline_inserted, new_selection)| {
4322 let mut cursor = new_selection.end.to_point(&buffer);
4323 if extra_newline_inserted {
4324 cursor.row -= 1;
4325 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4326 }
4327 new_selection.map(|_| cursor)
4328 })
4329 .collect();
4330
4331 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4332 s.select(new_selections)
4333 });
4334 this.refresh_inline_completion(true, false, window, cx);
4335 });
4336 }
4337
4338 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4339 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4340
4341 let buffer = self.buffer.read(cx);
4342 let snapshot = buffer.snapshot(cx);
4343
4344 let mut edits = Vec::new();
4345 let mut rows = Vec::new();
4346
4347 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4348 let cursor = selection.head();
4349 let row = cursor.row;
4350
4351 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4352
4353 let newline = "\n".to_string();
4354 edits.push((start_of_line..start_of_line, newline));
4355
4356 rows.push(row + rows_inserted as u32);
4357 }
4358
4359 self.transact(window, cx, |editor, window, cx| {
4360 editor.edit(edits, cx);
4361
4362 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4363 let mut index = 0;
4364 s.move_cursors_with(|map, _, _| {
4365 let row = rows[index];
4366 index += 1;
4367
4368 let point = Point::new(row, 0);
4369 let boundary = map.next_line_boundary(point).1;
4370 let clipped = map.clip_point(boundary, Bias::Left);
4371
4372 (clipped, SelectionGoal::None)
4373 });
4374 });
4375
4376 let mut indent_edits = Vec::new();
4377 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4378 for row in rows {
4379 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4380 for (row, indent) in indents {
4381 if indent.len == 0 {
4382 continue;
4383 }
4384
4385 let text = match indent.kind {
4386 IndentKind::Space => " ".repeat(indent.len as usize),
4387 IndentKind::Tab => "\t".repeat(indent.len as usize),
4388 };
4389 let point = Point::new(row.0, 0);
4390 indent_edits.push((point..point, text));
4391 }
4392 }
4393 editor.edit(indent_edits, cx);
4394 });
4395 }
4396
4397 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4398 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4399
4400 let buffer = self.buffer.read(cx);
4401 let snapshot = buffer.snapshot(cx);
4402
4403 let mut edits = Vec::new();
4404 let mut rows = Vec::new();
4405 let mut rows_inserted = 0;
4406
4407 for selection in self.selections.all_adjusted(cx) {
4408 let cursor = selection.head();
4409 let row = cursor.row;
4410
4411 let point = Point::new(row + 1, 0);
4412 let start_of_line = snapshot.clip_point(point, Bias::Left);
4413
4414 let newline = "\n".to_string();
4415 edits.push((start_of_line..start_of_line, newline));
4416
4417 rows_inserted += 1;
4418 rows.push(row + rows_inserted);
4419 }
4420
4421 self.transact(window, cx, |editor, window, cx| {
4422 editor.edit(edits, cx);
4423
4424 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4425 let mut index = 0;
4426 s.move_cursors_with(|map, _, _| {
4427 let row = rows[index];
4428 index += 1;
4429
4430 let point = Point::new(row, 0);
4431 let boundary = map.next_line_boundary(point).1;
4432 let clipped = map.clip_point(boundary, Bias::Left);
4433
4434 (clipped, SelectionGoal::None)
4435 });
4436 });
4437
4438 let mut indent_edits = Vec::new();
4439 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4440 for row in rows {
4441 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4442 for (row, indent) in indents {
4443 if indent.len == 0 {
4444 continue;
4445 }
4446
4447 let text = match indent.kind {
4448 IndentKind::Space => " ".repeat(indent.len as usize),
4449 IndentKind::Tab => "\t".repeat(indent.len as usize),
4450 };
4451 let point = Point::new(row.0, 0);
4452 indent_edits.push((point..point, text));
4453 }
4454 }
4455 editor.edit(indent_edits, cx);
4456 });
4457 }
4458
4459 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4460 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4461 original_indent_columns: Vec::new(),
4462 });
4463 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4464 }
4465
4466 fn insert_with_autoindent_mode(
4467 &mut self,
4468 text: &str,
4469 autoindent_mode: Option<AutoindentMode>,
4470 window: &mut Window,
4471 cx: &mut Context<Self>,
4472 ) {
4473 if self.read_only(cx) {
4474 return;
4475 }
4476
4477 let text: Arc<str> = text.into();
4478 self.transact(window, cx, |this, window, cx| {
4479 let old_selections = this.selections.all_adjusted(cx);
4480 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4481 let anchors = {
4482 let snapshot = buffer.read(cx);
4483 old_selections
4484 .iter()
4485 .map(|s| {
4486 let anchor = snapshot.anchor_after(s.head());
4487 s.map(|_| anchor)
4488 })
4489 .collect::<Vec<_>>()
4490 };
4491 buffer.edit(
4492 old_selections
4493 .iter()
4494 .map(|s| (s.start..s.end, text.clone())),
4495 autoindent_mode,
4496 cx,
4497 );
4498 anchors
4499 });
4500
4501 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4502 s.select_anchors(selection_anchors);
4503 });
4504
4505 cx.notify();
4506 });
4507 }
4508
4509 fn trigger_completion_on_input(
4510 &mut self,
4511 text: &str,
4512 trigger_in_words: bool,
4513 window: &mut Window,
4514 cx: &mut Context<Self>,
4515 ) {
4516 let completions_source = self
4517 .context_menu
4518 .borrow()
4519 .as_ref()
4520 .and_then(|menu| match menu {
4521 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4522 CodeContextMenu::CodeActions(_) => None,
4523 });
4524
4525 match completions_source {
4526 Some(CompletionsMenuSource::Words) => {
4527 self.show_word_completions(&ShowWordCompletions, window, cx)
4528 }
4529 Some(CompletionsMenuSource::Normal)
4530 | Some(CompletionsMenuSource::SnippetChoices)
4531 | None
4532 if self.is_completion_trigger(
4533 text,
4534 trigger_in_words,
4535 completions_source.is_some(),
4536 cx,
4537 ) =>
4538 {
4539 self.show_completions(
4540 &ShowCompletions {
4541 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4542 },
4543 window,
4544 cx,
4545 )
4546 }
4547 _ => {
4548 self.hide_context_menu(window, cx);
4549 }
4550 }
4551 }
4552
4553 fn is_completion_trigger(
4554 &self,
4555 text: &str,
4556 trigger_in_words: bool,
4557 menu_is_open: bool,
4558 cx: &mut Context<Self>,
4559 ) -> bool {
4560 let position = self.selections.newest_anchor().head();
4561 let multibuffer = self.buffer.read(cx);
4562 let Some(buffer) = position
4563 .buffer_id
4564 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4565 else {
4566 return false;
4567 };
4568
4569 if let Some(completion_provider) = &self.completion_provider {
4570 completion_provider.is_completion_trigger(
4571 &buffer,
4572 position.text_anchor,
4573 text,
4574 trigger_in_words,
4575 menu_is_open,
4576 cx,
4577 )
4578 } else {
4579 false
4580 }
4581 }
4582
4583 /// If any empty selections is touching the start of its innermost containing autoclose
4584 /// region, expand it to select the brackets.
4585 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4586 let selections = self.selections.all::<usize>(cx);
4587 let buffer = self.buffer.read(cx).read(cx);
4588 let new_selections = self
4589 .selections_with_autoclose_regions(selections, &buffer)
4590 .map(|(mut selection, region)| {
4591 if !selection.is_empty() {
4592 return selection;
4593 }
4594
4595 if let Some(region) = region {
4596 let mut range = region.range.to_offset(&buffer);
4597 if selection.start == range.start && range.start >= region.pair.start.len() {
4598 range.start -= region.pair.start.len();
4599 if buffer.contains_str_at(range.start, ®ion.pair.start)
4600 && buffer.contains_str_at(range.end, ®ion.pair.end)
4601 {
4602 range.end += region.pair.end.len();
4603 selection.start = range.start;
4604 selection.end = range.end;
4605
4606 return selection;
4607 }
4608 }
4609 }
4610
4611 let always_treat_brackets_as_autoclosed = buffer
4612 .language_settings_at(selection.start, cx)
4613 .always_treat_brackets_as_autoclosed;
4614
4615 if !always_treat_brackets_as_autoclosed {
4616 return selection;
4617 }
4618
4619 if let Some(scope) = buffer.language_scope_at(selection.start) {
4620 for (pair, enabled) in scope.brackets() {
4621 if !enabled || !pair.close {
4622 continue;
4623 }
4624
4625 if buffer.contains_str_at(selection.start, &pair.end) {
4626 let pair_start_len = pair.start.len();
4627 if buffer.contains_str_at(
4628 selection.start.saturating_sub(pair_start_len),
4629 &pair.start,
4630 ) {
4631 selection.start -= pair_start_len;
4632 selection.end += pair.end.len();
4633
4634 return selection;
4635 }
4636 }
4637 }
4638 }
4639
4640 selection
4641 })
4642 .collect();
4643
4644 drop(buffer);
4645 self.change_selections(None, window, cx, |selections| {
4646 selections.select(new_selections)
4647 });
4648 }
4649
4650 /// Iterate the given selections, and for each one, find the smallest surrounding
4651 /// autoclose region. This uses the ordering of the selections and the autoclose
4652 /// regions to avoid repeated comparisons.
4653 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4654 &'a self,
4655 selections: impl IntoIterator<Item = Selection<D>>,
4656 buffer: &'a MultiBufferSnapshot,
4657 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4658 let mut i = 0;
4659 let mut regions = self.autoclose_regions.as_slice();
4660 selections.into_iter().map(move |selection| {
4661 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4662
4663 let mut enclosing = None;
4664 while let Some(pair_state) = regions.get(i) {
4665 if pair_state.range.end.to_offset(buffer) < range.start {
4666 regions = ®ions[i + 1..];
4667 i = 0;
4668 } else if pair_state.range.start.to_offset(buffer) > range.end {
4669 break;
4670 } else {
4671 if pair_state.selection_id == selection.id {
4672 enclosing = Some(pair_state);
4673 }
4674 i += 1;
4675 }
4676 }
4677
4678 (selection, enclosing)
4679 })
4680 }
4681
4682 /// Remove any autoclose regions that no longer contain their selection.
4683 fn invalidate_autoclose_regions(
4684 &mut self,
4685 mut selections: &[Selection<Anchor>],
4686 buffer: &MultiBufferSnapshot,
4687 ) {
4688 self.autoclose_regions.retain(|state| {
4689 let mut i = 0;
4690 while let Some(selection) = selections.get(i) {
4691 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4692 selections = &selections[1..];
4693 continue;
4694 }
4695 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4696 break;
4697 }
4698 if selection.id == state.selection_id {
4699 return true;
4700 } else {
4701 i += 1;
4702 }
4703 }
4704 false
4705 });
4706 }
4707
4708 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4709 let offset = position.to_offset(buffer);
4710 let (word_range, kind) = buffer.surrounding_word(offset, true);
4711 if offset > word_range.start && kind == Some(CharKind::Word) {
4712 Some(
4713 buffer
4714 .text_for_range(word_range.start..offset)
4715 .collect::<String>(),
4716 )
4717 } else {
4718 None
4719 }
4720 }
4721
4722 pub fn toggle_inline_values(
4723 &mut self,
4724 _: &ToggleInlineValues,
4725 _: &mut Window,
4726 cx: &mut Context<Self>,
4727 ) {
4728 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4729
4730 self.refresh_inline_values(cx);
4731 }
4732
4733 pub fn toggle_inlay_hints(
4734 &mut self,
4735 _: &ToggleInlayHints,
4736 _: &mut Window,
4737 cx: &mut Context<Self>,
4738 ) {
4739 self.refresh_inlay_hints(
4740 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4741 cx,
4742 );
4743 }
4744
4745 pub fn inlay_hints_enabled(&self) -> bool {
4746 self.inlay_hint_cache.enabled
4747 }
4748
4749 pub fn inline_values_enabled(&self) -> bool {
4750 self.inline_value_cache.enabled
4751 }
4752
4753 #[cfg(any(test, feature = "test-support"))]
4754 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4755 self.display_map
4756 .read(cx)
4757 .current_inlays()
4758 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4759 .cloned()
4760 .collect()
4761 }
4762
4763 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4764 if self.semantics_provider.is_none() || !self.mode.is_full() {
4765 return;
4766 }
4767
4768 let reason_description = reason.description();
4769 let ignore_debounce = matches!(
4770 reason,
4771 InlayHintRefreshReason::SettingsChange(_)
4772 | InlayHintRefreshReason::Toggle(_)
4773 | InlayHintRefreshReason::ExcerptsRemoved(_)
4774 | InlayHintRefreshReason::ModifiersChanged(_)
4775 );
4776 let (invalidate_cache, required_languages) = match reason {
4777 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4778 match self.inlay_hint_cache.modifiers_override(enabled) {
4779 Some(enabled) => {
4780 if enabled {
4781 (InvalidationStrategy::RefreshRequested, None)
4782 } else {
4783 self.splice_inlays(
4784 &self
4785 .visible_inlay_hints(cx)
4786 .iter()
4787 .map(|inlay| inlay.id)
4788 .collect::<Vec<InlayId>>(),
4789 Vec::new(),
4790 cx,
4791 );
4792 return;
4793 }
4794 }
4795 None => return,
4796 }
4797 }
4798 InlayHintRefreshReason::Toggle(enabled) => {
4799 if self.inlay_hint_cache.toggle(enabled) {
4800 if enabled {
4801 (InvalidationStrategy::RefreshRequested, None)
4802 } else {
4803 self.splice_inlays(
4804 &self
4805 .visible_inlay_hints(cx)
4806 .iter()
4807 .map(|inlay| inlay.id)
4808 .collect::<Vec<InlayId>>(),
4809 Vec::new(),
4810 cx,
4811 );
4812 return;
4813 }
4814 } else {
4815 return;
4816 }
4817 }
4818 InlayHintRefreshReason::SettingsChange(new_settings) => {
4819 match self.inlay_hint_cache.update_settings(
4820 &self.buffer,
4821 new_settings,
4822 self.visible_inlay_hints(cx),
4823 cx,
4824 ) {
4825 ControlFlow::Break(Some(InlaySplice {
4826 to_remove,
4827 to_insert,
4828 })) => {
4829 self.splice_inlays(&to_remove, to_insert, cx);
4830 return;
4831 }
4832 ControlFlow::Break(None) => return,
4833 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4834 }
4835 }
4836 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4837 if let Some(InlaySplice {
4838 to_remove,
4839 to_insert,
4840 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4841 {
4842 self.splice_inlays(&to_remove, to_insert, cx);
4843 }
4844 self.display_map.update(cx, |display_map, _| {
4845 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4846 });
4847 return;
4848 }
4849 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4850 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4851 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4852 }
4853 InlayHintRefreshReason::RefreshRequested => {
4854 (InvalidationStrategy::RefreshRequested, None)
4855 }
4856 };
4857
4858 if let Some(InlaySplice {
4859 to_remove,
4860 to_insert,
4861 }) = self.inlay_hint_cache.spawn_hint_refresh(
4862 reason_description,
4863 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4864 invalidate_cache,
4865 ignore_debounce,
4866 cx,
4867 ) {
4868 self.splice_inlays(&to_remove, to_insert, cx);
4869 }
4870 }
4871
4872 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4873 self.display_map
4874 .read(cx)
4875 .current_inlays()
4876 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4877 .cloned()
4878 .collect()
4879 }
4880
4881 pub fn excerpts_for_inlay_hints_query(
4882 &self,
4883 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4884 cx: &mut Context<Editor>,
4885 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4886 let Some(project) = self.project.as_ref() else {
4887 return HashMap::default();
4888 };
4889 let project = project.read(cx);
4890 let multi_buffer = self.buffer().read(cx);
4891 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4892 let multi_buffer_visible_start = self
4893 .scroll_manager
4894 .anchor()
4895 .anchor
4896 .to_point(&multi_buffer_snapshot);
4897 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4898 multi_buffer_visible_start
4899 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4900 Bias::Left,
4901 );
4902 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4903 multi_buffer_snapshot
4904 .range_to_buffer_ranges(multi_buffer_visible_range)
4905 .into_iter()
4906 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4907 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4908 let buffer_file = project::File::from_dyn(buffer.file())?;
4909 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4910 let worktree_entry = buffer_worktree
4911 .read(cx)
4912 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4913 if worktree_entry.is_ignored {
4914 return None;
4915 }
4916
4917 let language = buffer.language()?;
4918 if let Some(restrict_to_languages) = restrict_to_languages {
4919 if !restrict_to_languages.contains(language) {
4920 return None;
4921 }
4922 }
4923 Some((
4924 excerpt_id,
4925 (
4926 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4927 buffer.version().clone(),
4928 excerpt_visible_range,
4929 ),
4930 ))
4931 })
4932 .collect()
4933 }
4934
4935 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4936 TextLayoutDetails {
4937 text_system: window.text_system().clone(),
4938 editor_style: self.style.clone().unwrap(),
4939 rem_size: window.rem_size(),
4940 scroll_anchor: self.scroll_manager.anchor(),
4941 visible_rows: self.visible_line_count(),
4942 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4943 }
4944 }
4945
4946 pub fn splice_inlays(
4947 &self,
4948 to_remove: &[InlayId],
4949 to_insert: Vec<Inlay>,
4950 cx: &mut Context<Self>,
4951 ) {
4952 self.display_map.update(cx, |display_map, cx| {
4953 display_map.splice_inlays(to_remove, to_insert, cx)
4954 });
4955 cx.notify();
4956 }
4957
4958 fn trigger_on_type_formatting(
4959 &self,
4960 input: String,
4961 window: &mut Window,
4962 cx: &mut Context<Self>,
4963 ) -> Option<Task<Result<()>>> {
4964 if input.len() != 1 {
4965 return None;
4966 }
4967
4968 let project = self.project.as_ref()?;
4969 let position = self.selections.newest_anchor().head();
4970 let (buffer, buffer_position) = self
4971 .buffer
4972 .read(cx)
4973 .text_anchor_for_position(position, cx)?;
4974
4975 let settings = language_settings::language_settings(
4976 buffer
4977 .read(cx)
4978 .language_at(buffer_position)
4979 .map(|l| l.name()),
4980 buffer.read(cx).file(),
4981 cx,
4982 );
4983 if !settings.use_on_type_format {
4984 return None;
4985 }
4986
4987 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4988 // hence we do LSP request & edit on host side only — add formats to host's history.
4989 let push_to_lsp_host_history = true;
4990 // If this is not the host, append its history with new edits.
4991 let push_to_client_history = project.read(cx).is_via_collab();
4992
4993 let on_type_formatting = project.update(cx, |project, cx| {
4994 project.on_type_format(
4995 buffer.clone(),
4996 buffer_position,
4997 input,
4998 push_to_lsp_host_history,
4999 cx,
5000 )
5001 });
5002 Some(cx.spawn_in(window, async move |editor, cx| {
5003 if let Some(transaction) = on_type_formatting.await? {
5004 if push_to_client_history {
5005 buffer
5006 .update(cx, |buffer, _| {
5007 buffer.push_transaction(transaction, Instant::now());
5008 buffer.finalize_last_transaction();
5009 })
5010 .ok();
5011 }
5012 editor.update(cx, |editor, cx| {
5013 editor.refresh_document_highlights(cx);
5014 })?;
5015 }
5016 Ok(())
5017 }))
5018 }
5019
5020 pub fn show_word_completions(
5021 &mut self,
5022 _: &ShowWordCompletions,
5023 window: &mut Window,
5024 cx: &mut Context<Self>,
5025 ) {
5026 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5027 }
5028
5029 pub fn show_completions(
5030 &mut self,
5031 options: &ShowCompletions,
5032 window: &mut Window,
5033 cx: &mut Context<Self>,
5034 ) {
5035 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5036 }
5037
5038 fn open_or_update_completions_menu(
5039 &mut self,
5040 requested_source: Option<CompletionsMenuSource>,
5041 trigger: Option<&str>,
5042 window: &mut Window,
5043 cx: &mut Context<Self>,
5044 ) {
5045 if self.pending_rename.is_some() {
5046 return;
5047 }
5048
5049 let position = self.selections.newest_anchor().head();
5050 if position.diff_base_anchor.is_some() {
5051 return;
5052 }
5053 let (buffer, buffer_position) =
5054 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5055 output
5056 } else {
5057 return;
5058 };
5059 let buffer_snapshot = buffer.read(cx).snapshot();
5060
5061 let query: Option<Arc<String>> =
5062 Self::completion_query(&self.buffer.read(cx).read(cx), position)
5063 .map(|query| query.into());
5064
5065 let provider = match requested_source {
5066 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5067 Some(CompletionsMenuSource::Words) => None,
5068 Some(CompletionsMenuSource::SnippetChoices) => {
5069 log::error!("bug: SnippetChoices requested_source is not handled");
5070 None
5071 }
5072 };
5073
5074 let sort_completions = provider
5075 .as_ref()
5076 .map_or(false, |provider| provider.sort_completions());
5077
5078 let filter_completions = provider
5079 .as_ref()
5080 .map_or(true, |provider| provider.filter_completions());
5081
5082 // When `is_incomplete` is false, can filter completions instead of re-querying when the
5083 // current query is a suffix of the initial query.
5084 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5085 if !menu.is_incomplete && filter_completions {
5086 // If the new query is a suffix of the old query (typing more characters) and
5087 // the previous result was complete, the existing completions can be filtered.
5088 //
5089 // Note that this is always true for snippet completions.
5090 let query_matches = match (&menu.initial_query, &query) {
5091 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5092 (None, _) => true,
5093 _ => false,
5094 };
5095 if query_matches {
5096 let position_matches = if menu.initial_position == position {
5097 true
5098 } else {
5099 let snapshot = self.buffer.read(cx).read(cx);
5100 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5101 };
5102 if position_matches {
5103 menu.filter(query.clone(), provider.clone(), window, cx);
5104 return;
5105 }
5106 }
5107 }
5108 };
5109
5110 let trigger_kind = match trigger {
5111 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5112 CompletionTriggerKind::TRIGGER_CHARACTER
5113 }
5114 _ => CompletionTriggerKind::INVOKED,
5115 };
5116 let completion_context = CompletionContext {
5117 trigger_character: trigger.and_then(|trigger| {
5118 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5119 Some(String::from(trigger))
5120 } else {
5121 None
5122 }
5123 }),
5124 trigger_kind,
5125 };
5126
5127 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5128 buffer_snapshot.surrounding_word(buffer_position)
5129 {
5130 let word_to_exclude = buffer_snapshot
5131 .text_for_range(word_range.clone())
5132 .collect::<String>();
5133 (
5134 buffer_snapshot.anchor_before(word_range.start)
5135 ..buffer_snapshot.anchor_after(buffer_position),
5136 Some(word_to_exclude),
5137 )
5138 } else {
5139 (buffer_position..buffer_position, None)
5140 };
5141
5142 let language = buffer_snapshot
5143 .language_at(buffer_position)
5144 .map(|language| language.name());
5145
5146 let completion_settings =
5147 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5148
5149 let show_completion_documentation = buffer_snapshot
5150 .settings_at(buffer_position, cx)
5151 .show_completion_documentation;
5152
5153 // The document can be large, so stay in reasonable bounds when searching for words,
5154 // otherwise completion pop-up might be slow to appear.
5155 const WORD_LOOKUP_ROWS: u32 = 5_000;
5156 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5157 let min_word_search = buffer_snapshot.clip_point(
5158 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5159 Bias::Left,
5160 );
5161 let max_word_search = buffer_snapshot.clip_point(
5162 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5163 Bias::Right,
5164 );
5165 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5166 ..buffer_snapshot.point_to_offset(max_word_search);
5167
5168 let skip_digits = query
5169 .as_ref()
5170 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5171
5172 let (mut words, provider_responses) = match &provider {
5173 Some(provider) => {
5174 let provider_responses = provider.completions(
5175 position.excerpt_id,
5176 &buffer,
5177 buffer_position,
5178 completion_context,
5179 window,
5180 cx,
5181 );
5182
5183 let words = match completion_settings.words {
5184 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5185 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5186 .background_spawn(async move {
5187 buffer_snapshot.words_in_range(WordsQuery {
5188 fuzzy_contents: None,
5189 range: word_search_range,
5190 skip_digits,
5191 })
5192 }),
5193 };
5194
5195 (words, provider_responses)
5196 }
5197 None => (
5198 cx.background_spawn(async move {
5199 buffer_snapshot.words_in_range(WordsQuery {
5200 fuzzy_contents: None,
5201 range: word_search_range,
5202 skip_digits,
5203 })
5204 }),
5205 Task::ready(Ok(Vec::new())),
5206 ),
5207 };
5208
5209 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5210
5211 let id = post_inc(&mut self.next_completion_id);
5212 let task = cx.spawn_in(window, async move |editor, cx| {
5213 let Ok(()) = editor.update(cx, |this, _| {
5214 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5215 }) else {
5216 return;
5217 };
5218
5219 // TODO: Ideally completions from different sources would be selectively re-queried, so
5220 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5221 let mut completions = Vec::new();
5222 let mut is_incomplete = false;
5223 if let Some(provider_responses) = provider_responses.await.log_err() {
5224 if !provider_responses.is_empty() {
5225 for response in provider_responses {
5226 completions.extend(response.completions);
5227 is_incomplete = is_incomplete || response.is_incomplete;
5228 }
5229 if completion_settings.words == WordsCompletionMode::Fallback {
5230 words = Task::ready(BTreeMap::default());
5231 }
5232 }
5233 }
5234
5235 let mut words = words.await;
5236 if let Some(word_to_exclude) = &word_to_exclude {
5237 words.remove(word_to_exclude);
5238 }
5239 for lsp_completion in &completions {
5240 words.remove(&lsp_completion.new_text);
5241 }
5242 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5243 replace_range: word_replace_range.clone(),
5244 new_text: word.clone(),
5245 label: CodeLabel::plain(word, None),
5246 icon_path: None,
5247 documentation: None,
5248 source: CompletionSource::BufferWord {
5249 word_range,
5250 resolved: false,
5251 },
5252 insert_text_mode: Some(InsertTextMode::AS_IS),
5253 confirm: None,
5254 }));
5255
5256 let menu = if completions.is_empty() {
5257 None
5258 } else {
5259 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5260 let languages = editor
5261 .workspace
5262 .as_ref()
5263 .and_then(|(workspace, _)| workspace.upgrade())
5264 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5265 let menu = CompletionsMenu::new(
5266 id,
5267 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5268 sort_completions,
5269 show_completion_documentation,
5270 position,
5271 query.clone(),
5272 is_incomplete,
5273 buffer.clone(),
5274 completions.into(),
5275 snippet_sort_order,
5276 languages,
5277 language,
5278 cx,
5279 );
5280
5281 let query = if filter_completions { query } else { None };
5282 let matches_task = if let Some(query) = query {
5283 menu.do_async_filtering(query, cx)
5284 } else {
5285 Task::ready(menu.unfiltered_matches())
5286 };
5287 (menu, matches_task)
5288 }) else {
5289 return;
5290 };
5291
5292 let matches = matches_task.await;
5293
5294 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5295 // Newer menu already set, so exit.
5296 match editor.context_menu.borrow().as_ref() {
5297 Some(CodeContextMenu::Completions(prev_menu)) => {
5298 if prev_menu.id > id {
5299 return;
5300 }
5301 }
5302 _ => {}
5303 };
5304
5305 // Only valid to take prev_menu because it the new menu is immediately set
5306 // below, or the menu is hidden.
5307 match editor.context_menu.borrow_mut().take() {
5308 Some(CodeContextMenu::Completions(prev_menu)) => {
5309 let position_matches =
5310 if prev_menu.initial_position == menu.initial_position {
5311 true
5312 } else {
5313 let snapshot = editor.buffer.read(cx).read(cx);
5314 prev_menu.initial_position.to_offset(&snapshot)
5315 == menu.initial_position.to_offset(&snapshot)
5316 };
5317 if position_matches {
5318 // Preserve markdown cache before `set_filter_results` because it will
5319 // try to populate the documentation cache.
5320 menu.preserve_markdown_cache(prev_menu);
5321 }
5322 }
5323 _ => {}
5324 };
5325
5326 menu.set_filter_results(matches, provider, window, cx);
5327 }) else {
5328 return;
5329 };
5330
5331 menu.visible().then_some(menu)
5332 };
5333
5334 editor
5335 .update_in(cx, |editor, window, cx| {
5336 if editor.focus_handle.is_focused(window) {
5337 if let Some(menu) = menu {
5338 *editor.context_menu.borrow_mut() =
5339 Some(CodeContextMenu::Completions(menu));
5340
5341 crate::hover_popover::hide_hover(editor, cx);
5342 if editor.show_edit_predictions_in_menu() {
5343 editor.update_visible_inline_completion(window, cx);
5344 } else {
5345 editor.discard_inline_completion(false, cx);
5346 }
5347
5348 cx.notify();
5349 return;
5350 }
5351 }
5352
5353 if editor.completion_tasks.len() <= 1 {
5354 // If there are no more completion tasks and the last menu was empty, we should hide it.
5355 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5356 // If it was already hidden and we don't show inline completions in the menu, we should
5357 // also show the inline-completion when available.
5358 if was_hidden && editor.show_edit_predictions_in_menu() {
5359 editor.update_visible_inline_completion(window, cx);
5360 }
5361 }
5362 })
5363 .ok();
5364 });
5365
5366 self.completion_tasks.push((id, task));
5367 }
5368
5369 #[cfg(feature = "test-support")]
5370 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5371 let menu = self.context_menu.borrow();
5372 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5373 let completions = menu.completions.borrow();
5374 Some(completions.to_vec())
5375 } else {
5376 None
5377 }
5378 }
5379
5380 pub fn with_completions_menu_matching_id<R>(
5381 &self,
5382 id: CompletionId,
5383 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5384 ) -> R {
5385 let mut context_menu = self.context_menu.borrow_mut();
5386 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5387 return f(None);
5388 };
5389 if completions_menu.id != id {
5390 return f(None);
5391 }
5392 f(Some(completions_menu))
5393 }
5394
5395 pub fn confirm_completion(
5396 &mut self,
5397 action: &ConfirmCompletion,
5398 window: &mut Window,
5399 cx: &mut Context<Self>,
5400 ) -> Option<Task<Result<()>>> {
5401 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5402 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5403 }
5404
5405 pub fn confirm_completion_insert(
5406 &mut self,
5407 _: &ConfirmCompletionInsert,
5408 window: &mut Window,
5409 cx: &mut Context<Self>,
5410 ) -> Option<Task<Result<()>>> {
5411 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5412 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5413 }
5414
5415 pub fn confirm_completion_replace(
5416 &mut self,
5417 _: &ConfirmCompletionReplace,
5418 window: &mut Window,
5419 cx: &mut Context<Self>,
5420 ) -> Option<Task<Result<()>>> {
5421 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5422 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5423 }
5424
5425 pub fn compose_completion(
5426 &mut self,
5427 action: &ComposeCompletion,
5428 window: &mut Window,
5429 cx: &mut Context<Self>,
5430 ) -> Option<Task<Result<()>>> {
5431 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5432 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5433 }
5434
5435 fn do_completion(
5436 &mut self,
5437 item_ix: Option<usize>,
5438 intent: CompletionIntent,
5439 window: &mut Window,
5440 cx: &mut Context<Editor>,
5441 ) -> Option<Task<Result<()>>> {
5442 use language::ToOffset as _;
5443
5444 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5445 else {
5446 return None;
5447 };
5448
5449 let candidate_id = {
5450 let entries = completions_menu.entries.borrow();
5451 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5452 if self.show_edit_predictions_in_menu() {
5453 self.discard_inline_completion(true, cx);
5454 }
5455 mat.candidate_id
5456 };
5457
5458 let completion = completions_menu
5459 .completions
5460 .borrow()
5461 .get(candidate_id)?
5462 .clone();
5463 cx.stop_propagation();
5464
5465 let buffer_handle = completions_menu.buffer.clone();
5466
5467 let CompletionEdit {
5468 new_text,
5469 snippet,
5470 replace_range,
5471 } = process_completion_for_edit(
5472 &completion,
5473 intent,
5474 &buffer_handle,
5475 &completions_menu.initial_position.text_anchor,
5476 cx,
5477 );
5478
5479 let buffer = buffer_handle.read(cx);
5480 let snapshot = self.buffer.read(cx).snapshot(cx);
5481 let newest_anchor = self.selections.newest_anchor();
5482 let replace_range_multibuffer = {
5483 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5484 let multibuffer_anchor = snapshot
5485 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5486 .unwrap()
5487 ..snapshot
5488 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5489 .unwrap();
5490 multibuffer_anchor.start.to_offset(&snapshot)
5491 ..multibuffer_anchor.end.to_offset(&snapshot)
5492 };
5493 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5494 return None;
5495 }
5496
5497 let old_text = buffer
5498 .text_for_range(replace_range.clone())
5499 .collect::<String>();
5500 let lookbehind = newest_anchor
5501 .start
5502 .text_anchor
5503 .to_offset(buffer)
5504 .saturating_sub(replace_range.start);
5505 let lookahead = replace_range
5506 .end
5507 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5508 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5509 let suffix = &old_text[lookbehind.min(old_text.len())..];
5510
5511 let selections = self.selections.all::<usize>(cx);
5512 let mut ranges = Vec::new();
5513 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5514
5515 for selection in &selections {
5516 let range = if selection.id == newest_anchor.id {
5517 replace_range_multibuffer.clone()
5518 } else {
5519 let mut range = selection.range();
5520
5521 // if prefix is present, don't duplicate it
5522 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5523 range.start = range.start.saturating_sub(lookbehind);
5524
5525 // if suffix is also present, mimic the newest cursor and replace it
5526 if selection.id != newest_anchor.id
5527 && snapshot.contains_str_at(range.end, suffix)
5528 {
5529 range.end += lookahead;
5530 }
5531 }
5532 range
5533 };
5534
5535 ranges.push(range.clone());
5536
5537 if !self.linked_edit_ranges.is_empty() {
5538 let start_anchor = snapshot.anchor_before(range.start);
5539 let end_anchor = snapshot.anchor_after(range.end);
5540 if let Some(ranges) = self
5541 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5542 {
5543 for (buffer, edits) in ranges {
5544 linked_edits
5545 .entry(buffer.clone())
5546 .or_default()
5547 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5548 }
5549 }
5550 }
5551 }
5552
5553 let common_prefix_len = old_text
5554 .chars()
5555 .zip(new_text.chars())
5556 .take_while(|(a, b)| a == b)
5557 .map(|(a, _)| a.len_utf8())
5558 .sum::<usize>();
5559
5560 cx.emit(EditorEvent::InputHandled {
5561 utf16_range_to_replace: None,
5562 text: new_text[common_prefix_len..].into(),
5563 });
5564
5565 self.transact(window, cx, |this, window, cx| {
5566 if let Some(mut snippet) = snippet {
5567 snippet.text = new_text.to_string();
5568 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5569 } else {
5570 this.buffer.update(cx, |buffer, cx| {
5571 let auto_indent = match completion.insert_text_mode {
5572 Some(InsertTextMode::AS_IS) => None,
5573 _ => this.autoindent_mode.clone(),
5574 };
5575 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5576 buffer.edit(edits, auto_indent, cx);
5577 });
5578 }
5579 for (buffer, edits) in linked_edits {
5580 buffer.update(cx, |buffer, cx| {
5581 let snapshot = buffer.snapshot();
5582 let edits = edits
5583 .into_iter()
5584 .map(|(range, text)| {
5585 use text::ToPoint as TP;
5586 let end_point = TP::to_point(&range.end, &snapshot);
5587 let start_point = TP::to_point(&range.start, &snapshot);
5588 (start_point..end_point, text)
5589 })
5590 .sorted_by_key(|(range, _)| range.start);
5591 buffer.edit(edits, None, cx);
5592 })
5593 }
5594
5595 this.refresh_inline_completion(true, false, window, cx);
5596 });
5597
5598 let show_new_completions_on_confirm = completion
5599 .confirm
5600 .as_ref()
5601 .map_or(false, |confirm| confirm(intent, window, cx));
5602 if show_new_completions_on_confirm {
5603 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5604 }
5605
5606 let provider = self.completion_provider.as_ref()?;
5607 drop(completion);
5608 let apply_edits = provider.apply_additional_edits_for_completion(
5609 buffer_handle,
5610 completions_menu.completions.clone(),
5611 candidate_id,
5612 true,
5613 cx,
5614 );
5615
5616 let editor_settings = EditorSettings::get_global(cx);
5617 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5618 // After the code completion is finished, users often want to know what signatures are needed.
5619 // so we should automatically call signature_help
5620 self.show_signature_help(&ShowSignatureHelp, window, cx);
5621 }
5622
5623 Some(cx.foreground_executor().spawn(async move {
5624 apply_edits.await?;
5625 Ok(())
5626 }))
5627 }
5628
5629 pub fn toggle_code_actions(
5630 &mut self,
5631 action: &ToggleCodeActions,
5632 window: &mut Window,
5633 cx: &mut Context<Self>,
5634 ) {
5635 let quick_launch = action.quick_launch;
5636 let mut context_menu = self.context_menu.borrow_mut();
5637 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5638 if code_actions.deployed_from == action.deployed_from {
5639 // Toggle if we're selecting the same one
5640 *context_menu = None;
5641 cx.notify();
5642 return;
5643 } else {
5644 // Otherwise, clear it and start a new one
5645 *context_menu = None;
5646 cx.notify();
5647 }
5648 }
5649 drop(context_menu);
5650 let snapshot = self.snapshot(window, cx);
5651 let deployed_from = action.deployed_from.clone();
5652 let mut task = self.code_actions_task.take();
5653 let action = action.clone();
5654 cx.spawn_in(window, async move |editor, cx| {
5655 while let Some(prev_task) = task {
5656 prev_task.await.log_err();
5657 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5658 }
5659
5660 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5661 if editor.focus_handle.is_focused(window) {
5662 let multibuffer_point = match &action.deployed_from {
5663 Some(CodeActionSource::Indicator(row)) => {
5664 DisplayPoint::new(*row, 0).to_point(&snapshot)
5665 }
5666 _ => editor.selections.newest::<Point>(cx).head(),
5667 };
5668 let (buffer, buffer_row) = snapshot
5669 .buffer_snapshot
5670 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5671 .and_then(|(buffer_snapshot, range)| {
5672 editor
5673 .buffer
5674 .read(cx)
5675 .buffer(buffer_snapshot.remote_id())
5676 .map(|buffer| (buffer, range.start.row))
5677 })?;
5678 let (_, code_actions) = editor
5679 .available_code_actions
5680 .clone()
5681 .and_then(|(location, code_actions)| {
5682 let snapshot = location.buffer.read(cx).snapshot();
5683 let point_range = location.range.to_point(&snapshot);
5684 let point_range = point_range.start.row..=point_range.end.row;
5685 if point_range.contains(&buffer_row) {
5686 Some((location, code_actions))
5687 } else {
5688 None
5689 }
5690 })
5691 .unzip();
5692 let buffer_id = buffer.read(cx).remote_id();
5693 let tasks = editor
5694 .tasks
5695 .get(&(buffer_id, buffer_row))
5696 .map(|t| Arc::new(t.to_owned()));
5697 if tasks.is_none() && code_actions.is_none() {
5698 return None;
5699 }
5700
5701 editor.completion_tasks.clear();
5702 editor.discard_inline_completion(false, cx);
5703 let task_context =
5704 tasks
5705 .as_ref()
5706 .zip(editor.project.clone())
5707 .map(|(tasks, project)| {
5708 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5709 });
5710
5711 Some(cx.spawn_in(window, async move |editor, cx| {
5712 let task_context = match task_context {
5713 Some(task_context) => task_context.await,
5714 None => None,
5715 };
5716 let resolved_tasks =
5717 tasks
5718 .zip(task_context.clone())
5719 .map(|(tasks, task_context)| ResolvedTasks {
5720 templates: tasks.resolve(&task_context).collect(),
5721 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5722 multibuffer_point.row,
5723 tasks.column,
5724 )),
5725 });
5726 let debug_scenarios = editor.update(cx, |editor, cx| {
5727 if cx.has_flag::<DebuggerFeatureFlag>() {
5728 maybe!({
5729 let project = editor.project.as_ref()?;
5730 let dap_store = project.read(cx).dap_store();
5731 let mut scenarios = vec![];
5732 let resolved_tasks = resolved_tasks.as_ref()?;
5733 let buffer = buffer.read(cx);
5734 let language = buffer.language()?;
5735 let file = buffer.file();
5736 let debug_adapter =
5737 language_settings(language.name().into(), file, cx)
5738 .debuggers
5739 .first()
5740 .map(SharedString::from)
5741 .or_else(|| {
5742 language
5743 .config()
5744 .debuggers
5745 .first()
5746 .map(SharedString::from)
5747 })?;
5748
5749 dap_store.update(cx, |dap_store, cx| {
5750 for (_, task) in &resolved_tasks.templates {
5751 if let Some(scenario) = dap_store
5752 .debug_scenario_for_build_task(
5753 task.original_task().clone(),
5754 debug_adapter.clone().into(),
5755 task.display_label().to_owned().into(),
5756 cx,
5757 )
5758 {
5759 scenarios.push(scenario);
5760 }
5761 }
5762 });
5763 Some(scenarios)
5764 })
5765 .unwrap_or_default()
5766 } else {
5767 vec![]
5768 }
5769 })?;
5770 let spawn_straight_away = quick_launch
5771 && resolved_tasks
5772 .as_ref()
5773 .map_or(false, |tasks| tasks.templates.len() == 1)
5774 && code_actions
5775 .as_ref()
5776 .map_or(true, |actions| actions.is_empty())
5777 && debug_scenarios.is_empty();
5778 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5779 crate::hover_popover::hide_hover(editor, cx);
5780 *editor.context_menu.borrow_mut() =
5781 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5782 buffer,
5783 actions: CodeActionContents::new(
5784 resolved_tasks,
5785 code_actions,
5786 debug_scenarios,
5787 task_context.unwrap_or_default(),
5788 ),
5789 selected_item: Default::default(),
5790 scroll_handle: UniformListScrollHandle::default(),
5791 deployed_from,
5792 }));
5793 if spawn_straight_away {
5794 if let Some(task) = editor.confirm_code_action(
5795 &ConfirmCodeAction { item_ix: Some(0) },
5796 window,
5797 cx,
5798 ) {
5799 cx.notify();
5800 return task;
5801 }
5802 }
5803 cx.notify();
5804 Task::ready(Ok(()))
5805 }) {
5806 task.await
5807 } else {
5808 Ok(())
5809 }
5810 }))
5811 } else {
5812 Some(Task::ready(Ok(())))
5813 }
5814 })?;
5815 if let Some(task) = spawned_test_task {
5816 task.await?;
5817 }
5818
5819 anyhow::Ok(())
5820 })
5821 .detach_and_log_err(cx);
5822 }
5823
5824 pub fn confirm_code_action(
5825 &mut self,
5826 action: &ConfirmCodeAction,
5827 window: &mut Window,
5828 cx: &mut Context<Self>,
5829 ) -> Option<Task<Result<()>>> {
5830 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5831
5832 let actions_menu =
5833 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5834 menu
5835 } else {
5836 return None;
5837 };
5838
5839 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5840 let action = actions_menu.actions.get(action_ix)?;
5841 let title = action.label();
5842 let buffer = actions_menu.buffer;
5843 let workspace = self.workspace()?;
5844
5845 match action {
5846 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5847 workspace.update(cx, |workspace, cx| {
5848 workspace.schedule_resolved_task(
5849 task_source_kind,
5850 resolved_task,
5851 false,
5852 window,
5853 cx,
5854 );
5855
5856 Some(Task::ready(Ok(())))
5857 })
5858 }
5859 CodeActionsItem::CodeAction {
5860 excerpt_id,
5861 action,
5862 provider,
5863 } => {
5864 let apply_code_action =
5865 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5866 let workspace = workspace.downgrade();
5867 Some(cx.spawn_in(window, async move |editor, cx| {
5868 let project_transaction = apply_code_action.await?;
5869 Self::open_project_transaction(
5870 &editor,
5871 workspace,
5872 project_transaction,
5873 title,
5874 cx,
5875 )
5876 .await
5877 }))
5878 }
5879 CodeActionsItem::DebugScenario(scenario) => {
5880 let context = actions_menu.actions.context.clone();
5881
5882 workspace.update(cx, |workspace, cx| {
5883 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5884 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5885 });
5886 Some(Task::ready(Ok(())))
5887 }
5888 }
5889 }
5890
5891 pub async fn open_project_transaction(
5892 this: &WeakEntity<Editor>,
5893 workspace: WeakEntity<Workspace>,
5894 transaction: ProjectTransaction,
5895 title: String,
5896 cx: &mut AsyncWindowContext,
5897 ) -> Result<()> {
5898 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5899 cx.update(|_, cx| {
5900 entries.sort_unstable_by_key(|(buffer, _)| {
5901 buffer.read(cx).file().map(|f| f.path().clone())
5902 });
5903 })?;
5904
5905 // If the project transaction's edits are all contained within this editor, then
5906 // avoid opening a new editor to display them.
5907
5908 if let Some((buffer, transaction)) = entries.first() {
5909 if entries.len() == 1 {
5910 let excerpt = this.update(cx, |editor, cx| {
5911 editor
5912 .buffer()
5913 .read(cx)
5914 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5915 })?;
5916 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5917 if excerpted_buffer == *buffer {
5918 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5919 let excerpt_range = excerpt_range.to_offset(buffer);
5920 buffer
5921 .edited_ranges_for_transaction::<usize>(transaction)
5922 .all(|range| {
5923 excerpt_range.start <= range.start
5924 && excerpt_range.end >= range.end
5925 })
5926 })?;
5927
5928 if all_edits_within_excerpt {
5929 return Ok(());
5930 }
5931 }
5932 }
5933 }
5934 } else {
5935 return Ok(());
5936 }
5937
5938 let mut ranges_to_highlight = Vec::new();
5939 let excerpt_buffer = cx.new(|cx| {
5940 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5941 for (buffer_handle, transaction) in &entries {
5942 let edited_ranges = buffer_handle
5943 .read(cx)
5944 .edited_ranges_for_transaction::<Point>(transaction)
5945 .collect::<Vec<_>>();
5946 let (ranges, _) = multibuffer.set_excerpts_for_path(
5947 PathKey::for_buffer(buffer_handle, cx),
5948 buffer_handle.clone(),
5949 edited_ranges,
5950 DEFAULT_MULTIBUFFER_CONTEXT,
5951 cx,
5952 );
5953
5954 ranges_to_highlight.extend(ranges);
5955 }
5956 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5957 multibuffer
5958 })?;
5959
5960 workspace.update_in(cx, |workspace, window, cx| {
5961 let project = workspace.project().clone();
5962 let editor =
5963 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5964 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5965 editor.update(cx, |editor, cx| {
5966 editor.highlight_background::<Self>(
5967 &ranges_to_highlight,
5968 |theme| theme.editor_highlighted_line_background,
5969 cx,
5970 );
5971 });
5972 })?;
5973
5974 Ok(())
5975 }
5976
5977 pub fn clear_code_action_providers(&mut self) {
5978 self.code_action_providers.clear();
5979 self.available_code_actions.take();
5980 }
5981
5982 pub fn add_code_action_provider(
5983 &mut self,
5984 provider: Rc<dyn CodeActionProvider>,
5985 window: &mut Window,
5986 cx: &mut Context<Self>,
5987 ) {
5988 if self
5989 .code_action_providers
5990 .iter()
5991 .any(|existing_provider| existing_provider.id() == provider.id())
5992 {
5993 return;
5994 }
5995
5996 self.code_action_providers.push(provider);
5997 self.refresh_code_actions(window, cx);
5998 }
5999
6000 pub fn remove_code_action_provider(
6001 &mut self,
6002 id: Arc<str>,
6003 window: &mut Window,
6004 cx: &mut Context<Self>,
6005 ) {
6006 self.code_action_providers
6007 .retain(|provider| provider.id() != id);
6008 self.refresh_code_actions(window, cx);
6009 }
6010
6011 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6012 !self.code_action_providers.is_empty()
6013 && EditorSettings::get_global(cx).toolbar.code_actions
6014 }
6015
6016 pub fn has_available_code_actions(&self) -> bool {
6017 self.available_code_actions
6018 .as_ref()
6019 .is_some_and(|(_, actions)| !actions.is_empty())
6020 }
6021
6022 fn render_inline_code_actions(
6023 &self,
6024 icon_size: ui::IconSize,
6025 display_row: DisplayRow,
6026 is_active: bool,
6027 cx: &mut Context<Self>,
6028 ) -> AnyElement {
6029 let show_tooltip = !self.context_menu_visible();
6030 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6031 .icon_size(icon_size)
6032 .shape(ui::IconButtonShape::Square)
6033 .style(ButtonStyle::Transparent)
6034 .icon_color(ui::Color::Hidden)
6035 .toggle_state(is_active)
6036 .when(show_tooltip, |this| {
6037 this.tooltip({
6038 let focus_handle = self.focus_handle.clone();
6039 move |window, cx| {
6040 Tooltip::for_action_in(
6041 "Toggle Code Actions",
6042 &ToggleCodeActions {
6043 deployed_from: None,
6044 quick_launch: false,
6045 },
6046 &focus_handle,
6047 window,
6048 cx,
6049 )
6050 }
6051 })
6052 })
6053 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6054 window.focus(&editor.focus_handle(cx));
6055 editor.toggle_code_actions(
6056 &crate::actions::ToggleCodeActions {
6057 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6058 display_row,
6059 )),
6060 quick_launch: false,
6061 },
6062 window,
6063 cx,
6064 );
6065 }))
6066 .into_any_element()
6067 }
6068
6069 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6070 &self.context_menu
6071 }
6072
6073 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6074 let newest_selection = self.selections.newest_anchor().clone();
6075 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6076 let buffer = self.buffer.read(cx);
6077 if newest_selection.head().diff_base_anchor.is_some() {
6078 return None;
6079 }
6080 let (start_buffer, start) =
6081 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6082 let (end_buffer, end) =
6083 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6084 if start_buffer != end_buffer {
6085 return None;
6086 }
6087
6088 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6089 cx.background_executor()
6090 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6091 .await;
6092
6093 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6094 let providers = this.code_action_providers.clone();
6095 let tasks = this
6096 .code_action_providers
6097 .iter()
6098 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6099 .collect::<Vec<_>>();
6100 (providers, tasks)
6101 })?;
6102
6103 let mut actions = Vec::new();
6104 for (provider, provider_actions) in
6105 providers.into_iter().zip(future::join_all(tasks).await)
6106 {
6107 if let Some(provider_actions) = provider_actions.log_err() {
6108 actions.extend(provider_actions.into_iter().map(|action| {
6109 AvailableCodeAction {
6110 excerpt_id: newest_selection.start.excerpt_id,
6111 action,
6112 provider: provider.clone(),
6113 }
6114 }));
6115 }
6116 }
6117
6118 this.update(cx, |this, cx| {
6119 this.available_code_actions = if actions.is_empty() {
6120 None
6121 } else {
6122 Some((
6123 Location {
6124 buffer: start_buffer,
6125 range: start..end,
6126 },
6127 actions.into(),
6128 ))
6129 };
6130 cx.notify();
6131 })
6132 }));
6133 None
6134 }
6135
6136 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6137 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6138 self.show_git_blame_inline = false;
6139
6140 self.show_git_blame_inline_delay_task =
6141 Some(cx.spawn_in(window, async move |this, cx| {
6142 cx.background_executor().timer(delay).await;
6143
6144 this.update(cx, |this, cx| {
6145 this.show_git_blame_inline = true;
6146 cx.notify();
6147 })
6148 .log_err();
6149 }));
6150 }
6151 }
6152
6153 fn show_blame_popover(
6154 &mut self,
6155 blame_entry: &BlameEntry,
6156 position: gpui::Point<Pixels>,
6157 cx: &mut Context<Self>,
6158 ) {
6159 if let Some(state) = &mut self.inline_blame_popover {
6160 state.hide_task.take();
6161 cx.notify();
6162 } else {
6163 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6164 let show_task = cx.spawn(async move |editor, cx| {
6165 cx.background_executor()
6166 .timer(std::time::Duration::from_millis(delay))
6167 .await;
6168 editor
6169 .update(cx, |editor, cx| {
6170 if let Some(state) = &mut editor.inline_blame_popover {
6171 state.show_task = None;
6172 cx.notify();
6173 }
6174 })
6175 .ok();
6176 });
6177 let Some(blame) = self.blame.as_ref() else {
6178 return;
6179 };
6180 let blame = blame.read(cx);
6181 let details = blame.details_for_entry(&blame_entry);
6182 let markdown = cx.new(|cx| {
6183 Markdown::new(
6184 details
6185 .as_ref()
6186 .map(|message| message.message.clone())
6187 .unwrap_or_default(),
6188 None,
6189 None,
6190 cx,
6191 )
6192 });
6193 self.inline_blame_popover = Some(InlineBlamePopover {
6194 position,
6195 show_task: Some(show_task),
6196 hide_task: None,
6197 popover_bounds: None,
6198 popover_state: InlineBlamePopoverState {
6199 scroll_handle: ScrollHandle::new(),
6200 commit_message: details,
6201 markdown,
6202 },
6203 });
6204 }
6205 }
6206
6207 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6208 if let Some(state) = &mut self.inline_blame_popover {
6209 if state.show_task.is_some() {
6210 self.inline_blame_popover.take();
6211 cx.notify();
6212 } else {
6213 let hide_task = cx.spawn(async move |editor, cx| {
6214 cx.background_executor()
6215 .timer(std::time::Duration::from_millis(100))
6216 .await;
6217 editor
6218 .update(cx, |editor, cx| {
6219 editor.inline_blame_popover.take();
6220 cx.notify();
6221 })
6222 .ok();
6223 });
6224 state.hide_task = Some(hide_task);
6225 }
6226 }
6227 }
6228
6229 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6230 if self.pending_rename.is_some() {
6231 return None;
6232 }
6233
6234 let provider = self.semantics_provider.clone()?;
6235 let buffer = self.buffer.read(cx);
6236 let newest_selection = self.selections.newest_anchor().clone();
6237 let cursor_position = newest_selection.head();
6238 let (cursor_buffer, cursor_buffer_position) =
6239 buffer.text_anchor_for_position(cursor_position, cx)?;
6240 let (tail_buffer, tail_buffer_position) =
6241 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6242 if cursor_buffer != tail_buffer {
6243 return None;
6244 }
6245
6246 let snapshot = cursor_buffer.read(cx).snapshot();
6247 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6248 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6249 if start_word_range != end_word_range {
6250 self.document_highlights_task.take();
6251 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6252 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6253 return None;
6254 }
6255
6256 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6257 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6258 cx.background_executor()
6259 .timer(Duration::from_millis(debounce))
6260 .await;
6261
6262 let highlights = if let Some(highlights) = cx
6263 .update(|cx| {
6264 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6265 })
6266 .ok()
6267 .flatten()
6268 {
6269 highlights.await.log_err()
6270 } else {
6271 None
6272 };
6273
6274 if let Some(highlights) = highlights {
6275 this.update(cx, |this, cx| {
6276 if this.pending_rename.is_some() {
6277 return;
6278 }
6279
6280 let buffer_id = cursor_position.buffer_id;
6281 let buffer = this.buffer.read(cx);
6282 if !buffer
6283 .text_anchor_for_position(cursor_position, cx)
6284 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6285 {
6286 return;
6287 }
6288
6289 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6290 let mut write_ranges = Vec::new();
6291 let mut read_ranges = Vec::new();
6292 for highlight in highlights {
6293 for (excerpt_id, excerpt_range) in
6294 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6295 {
6296 let start = highlight
6297 .range
6298 .start
6299 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6300 let end = highlight
6301 .range
6302 .end
6303 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6304 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6305 continue;
6306 }
6307
6308 let range = Anchor {
6309 buffer_id,
6310 excerpt_id,
6311 text_anchor: start,
6312 diff_base_anchor: None,
6313 }..Anchor {
6314 buffer_id,
6315 excerpt_id,
6316 text_anchor: end,
6317 diff_base_anchor: None,
6318 };
6319 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6320 write_ranges.push(range);
6321 } else {
6322 read_ranges.push(range);
6323 }
6324 }
6325 }
6326
6327 this.highlight_background::<DocumentHighlightRead>(
6328 &read_ranges,
6329 |theme| theme.editor_document_highlight_read_background,
6330 cx,
6331 );
6332 this.highlight_background::<DocumentHighlightWrite>(
6333 &write_ranges,
6334 |theme| theme.editor_document_highlight_write_background,
6335 cx,
6336 );
6337 cx.notify();
6338 })
6339 .log_err();
6340 }
6341 }));
6342 None
6343 }
6344
6345 fn prepare_highlight_query_from_selection(
6346 &mut self,
6347 cx: &mut Context<Editor>,
6348 ) -> Option<(String, Range<Anchor>)> {
6349 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6350 return None;
6351 }
6352 if !EditorSettings::get_global(cx).selection_highlight {
6353 return None;
6354 }
6355 if self.selections.count() != 1 || self.selections.line_mode {
6356 return None;
6357 }
6358 let selection = self.selections.newest::<Point>(cx);
6359 if selection.is_empty() || selection.start.row != selection.end.row {
6360 return None;
6361 }
6362 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6363 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6364 let query = multi_buffer_snapshot
6365 .text_for_range(selection_anchor_range.clone())
6366 .collect::<String>();
6367 if query.trim().is_empty() {
6368 return None;
6369 }
6370 Some((query, selection_anchor_range))
6371 }
6372
6373 fn update_selection_occurrence_highlights(
6374 &mut self,
6375 query_text: String,
6376 query_range: Range<Anchor>,
6377 multi_buffer_range_to_query: Range<Point>,
6378 use_debounce: bool,
6379 window: &mut Window,
6380 cx: &mut Context<Editor>,
6381 ) -> Task<()> {
6382 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6383 cx.spawn_in(window, async move |editor, cx| {
6384 if use_debounce {
6385 cx.background_executor()
6386 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6387 .await;
6388 }
6389 let match_task = cx.background_spawn(async move {
6390 let buffer_ranges = multi_buffer_snapshot
6391 .range_to_buffer_ranges(multi_buffer_range_to_query)
6392 .into_iter()
6393 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6394 let mut match_ranges = Vec::new();
6395 let Ok(regex) = project::search::SearchQuery::text(
6396 query_text.clone(),
6397 false,
6398 false,
6399 false,
6400 Default::default(),
6401 Default::default(),
6402 false,
6403 None,
6404 ) else {
6405 return Vec::default();
6406 };
6407 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6408 match_ranges.extend(
6409 regex
6410 .search(&buffer_snapshot, Some(search_range.clone()))
6411 .await
6412 .into_iter()
6413 .filter_map(|match_range| {
6414 let match_start = buffer_snapshot
6415 .anchor_after(search_range.start + match_range.start);
6416 let match_end = buffer_snapshot
6417 .anchor_before(search_range.start + match_range.end);
6418 let match_anchor_range = Anchor::range_in_buffer(
6419 excerpt_id,
6420 buffer_snapshot.remote_id(),
6421 match_start..match_end,
6422 );
6423 (match_anchor_range != query_range).then_some(match_anchor_range)
6424 }),
6425 );
6426 }
6427 match_ranges
6428 });
6429 let match_ranges = match_task.await;
6430 editor
6431 .update_in(cx, |editor, _, cx| {
6432 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6433 if !match_ranges.is_empty() {
6434 editor.highlight_background::<SelectedTextHighlight>(
6435 &match_ranges,
6436 |theme| theme.editor_document_highlight_bracket_background,
6437 cx,
6438 )
6439 }
6440 })
6441 .log_err();
6442 })
6443 }
6444
6445 fn refresh_selected_text_highlights(
6446 &mut self,
6447 on_buffer_edit: bool,
6448 window: &mut Window,
6449 cx: &mut Context<Editor>,
6450 ) {
6451 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6452 else {
6453 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6454 self.quick_selection_highlight_task.take();
6455 self.debounced_selection_highlight_task.take();
6456 return;
6457 };
6458 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6459 if on_buffer_edit
6460 || self
6461 .quick_selection_highlight_task
6462 .as_ref()
6463 .map_or(true, |(prev_anchor_range, _)| {
6464 prev_anchor_range != &query_range
6465 })
6466 {
6467 let multi_buffer_visible_start = self
6468 .scroll_manager
6469 .anchor()
6470 .anchor
6471 .to_point(&multi_buffer_snapshot);
6472 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6473 multi_buffer_visible_start
6474 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6475 Bias::Left,
6476 );
6477 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6478 self.quick_selection_highlight_task = Some((
6479 query_range.clone(),
6480 self.update_selection_occurrence_highlights(
6481 query_text.clone(),
6482 query_range.clone(),
6483 multi_buffer_visible_range,
6484 false,
6485 window,
6486 cx,
6487 ),
6488 ));
6489 }
6490 if on_buffer_edit
6491 || self
6492 .debounced_selection_highlight_task
6493 .as_ref()
6494 .map_or(true, |(prev_anchor_range, _)| {
6495 prev_anchor_range != &query_range
6496 })
6497 {
6498 let multi_buffer_start = multi_buffer_snapshot
6499 .anchor_before(0)
6500 .to_point(&multi_buffer_snapshot);
6501 let multi_buffer_end = multi_buffer_snapshot
6502 .anchor_after(multi_buffer_snapshot.len())
6503 .to_point(&multi_buffer_snapshot);
6504 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6505 self.debounced_selection_highlight_task = Some((
6506 query_range.clone(),
6507 self.update_selection_occurrence_highlights(
6508 query_text,
6509 query_range,
6510 multi_buffer_full_range,
6511 true,
6512 window,
6513 cx,
6514 ),
6515 ));
6516 }
6517 }
6518
6519 pub fn refresh_inline_completion(
6520 &mut self,
6521 debounce: bool,
6522 user_requested: bool,
6523 window: &mut Window,
6524 cx: &mut Context<Self>,
6525 ) -> Option<()> {
6526 let provider = self.edit_prediction_provider()?;
6527 let cursor = self.selections.newest_anchor().head();
6528 let (buffer, cursor_buffer_position) =
6529 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6530
6531 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6532 self.discard_inline_completion(false, cx);
6533 return None;
6534 }
6535
6536 if !user_requested
6537 && (!self.should_show_edit_predictions()
6538 || !self.is_focused(window)
6539 || buffer.read(cx).is_empty())
6540 {
6541 self.discard_inline_completion(false, cx);
6542 return None;
6543 }
6544
6545 self.update_visible_inline_completion(window, cx);
6546 provider.refresh(
6547 self.project.clone(),
6548 buffer,
6549 cursor_buffer_position,
6550 debounce,
6551 cx,
6552 );
6553 Some(())
6554 }
6555
6556 fn show_edit_predictions_in_menu(&self) -> bool {
6557 match self.edit_prediction_settings {
6558 EditPredictionSettings::Disabled => false,
6559 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6560 }
6561 }
6562
6563 pub fn edit_predictions_enabled(&self) -> bool {
6564 match self.edit_prediction_settings {
6565 EditPredictionSettings::Disabled => false,
6566 EditPredictionSettings::Enabled { .. } => true,
6567 }
6568 }
6569
6570 fn edit_prediction_requires_modifier(&self) -> bool {
6571 match self.edit_prediction_settings {
6572 EditPredictionSettings::Disabled => false,
6573 EditPredictionSettings::Enabled {
6574 preview_requires_modifier,
6575 ..
6576 } => preview_requires_modifier,
6577 }
6578 }
6579
6580 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6581 if self.edit_prediction_provider.is_none() {
6582 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6583 } else {
6584 let selection = self.selections.newest_anchor();
6585 let cursor = selection.head();
6586
6587 if let Some((buffer, cursor_buffer_position)) =
6588 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6589 {
6590 self.edit_prediction_settings =
6591 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6592 }
6593 }
6594 }
6595
6596 fn edit_prediction_settings_at_position(
6597 &self,
6598 buffer: &Entity<Buffer>,
6599 buffer_position: language::Anchor,
6600 cx: &App,
6601 ) -> EditPredictionSettings {
6602 if !self.mode.is_full()
6603 || !self.show_inline_completions_override.unwrap_or(true)
6604 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6605 {
6606 return EditPredictionSettings::Disabled;
6607 }
6608
6609 let buffer = buffer.read(cx);
6610
6611 let file = buffer.file();
6612
6613 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6614 return EditPredictionSettings::Disabled;
6615 };
6616
6617 let by_provider = matches!(
6618 self.menu_inline_completions_policy,
6619 MenuInlineCompletionsPolicy::ByProvider
6620 );
6621
6622 let show_in_menu = by_provider
6623 && self
6624 .edit_prediction_provider
6625 .as_ref()
6626 .map_or(false, |provider| {
6627 provider.provider.show_completions_in_menu()
6628 });
6629
6630 let preview_requires_modifier =
6631 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6632
6633 EditPredictionSettings::Enabled {
6634 show_in_menu,
6635 preview_requires_modifier,
6636 }
6637 }
6638
6639 fn should_show_edit_predictions(&self) -> bool {
6640 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6641 }
6642
6643 pub fn edit_prediction_preview_is_active(&self) -> bool {
6644 matches!(
6645 self.edit_prediction_preview,
6646 EditPredictionPreview::Active { .. }
6647 )
6648 }
6649
6650 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6651 let cursor = self.selections.newest_anchor().head();
6652 if let Some((buffer, cursor_position)) =
6653 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6654 {
6655 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6656 } else {
6657 false
6658 }
6659 }
6660
6661 pub fn supports_minimap(&self, cx: &App) -> bool {
6662 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6663 }
6664
6665 fn edit_predictions_enabled_in_buffer(
6666 &self,
6667 buffer: &Entity<Buffer>,
6668 buffer_position: language::Anchor,
6669 cx: &App,
6670 ) -> bool {
6671 maybe!({
6672 if self.read_only(cx) {
6673 return Some(false);
6674 }
6675 let provider = self.edit_prediction_provider()?;
6676 if !provider.is_enabled(&buffer, buffer_position, cx) {
6677 return Some(false);
6678 }
6679 let buffer = buffer.read(cx);
6680 let Some(file) = buffer.file() else {
6681 return Some(true);
6682 };
6683 let settings = all_language_settings(Some(file), cx);
6684 Some(settings.edit_predictions_enabled_for_file(file, cx))
6685 })
6686 .unwrap_or(false)
6687 }
6688
6689 fn cycle_inline_completion(
6690 &mut self,
6691 direction: Direction,
6692 window: &mut Window,
6693 cx: &mut Context<Self>,
6694 ) -> Option<()> {
6695 let provider = self.edit_prediction_provider()?;
6696 let cursor = self.selections.newest_anchor().head();
6697 let (buffer, cursor_buffer_position) =
6698 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6699 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6700 return None;
6701 }
6702
6703 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6704 self.update_visible_inline_completion(window, cx);
6705
6706 Some(())
6707 }
6708
6709 pub fn show_inline_completion(
6710 &mut self,
6711 _: &ShowEditPrediction,
6712 window: &mut Window,
6713 cx: &mut Context<Self>,
6714 ) {
6715 if !self.has_active_inline_completion() {
6716 self.refresh_inline_completion(false, true, window, cx);
6717 return;
6718 }
6719
6720 self.update_visible_inline_completion(window, cx);
6721 }
6722
6723 pub fn display_cursor_names(
6724 &mut self,
6725 _: &DisplayCursorNames,
6726 window: &mut Window,
6727 cx: &mut Context<Self>,
6728 ) {
6729 self.show_cursor_names(window, cx);
6730 }
6731
6732 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6733 self.show_cursor_names = true;
6734 cx.notify();
6735 cx.spawn_in(window, async move |this, cx| {
6736 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6737 this.update(cx, |this, cx| {
6738 this.show_cursor_names = false;
6739 cx.notify()
6740 })
6741 .ok()
6742 })
6743 .detach();
6744 }
6745
6746 pub fn next_edit_prediction(
6747 &mut self,
6748 _: &NextEditPrediction,
6749 window: &mut Window,
6750 cx: &mut Context<Self>,
6751 ) {
6752 if self.has_active_inline_completion() {
6753 self.cycle_inline_completion(Direction::Next, window, cx);
6754 } else {
6755 let is_copilot_disabled = self
6756 .refresh_inline_completion(false, true, window, cx)
6757 .is_none();
6758 if is_copilot_disabled {
6759 cx.propagate();
6760 }
6761 }
6762 }
6763
6764 pub fn previous_edit_prediction(
6765 &mut self,
6766 _: &PreviousEditPrediction,
6767 window: &mut Window,
6768 cx: &mut Context<Self>,
6769 ) {
6770 if self.has_active_inline_completion() {
6771 self.cycle_inline_completion(Direction::Prev, window, cx);
6772 } else {
6773 let is_copilot_disabled = self
6774 .refresh_inline_completion(false, true, window, cx)
6775 .is_none();
6776 if is_copilot_disabled {
6777 cx.propagate();
6778 }
6779 }
6780 }
6781
6782 pub fn accept_edit_prediction(
6783 &mut self,
6784 _: &AcceptEditPrediction,
6785 window: &mut Window,
6786 cx: &mut Context<Self>,
6787 ) {
6788 if self.show_edit_predictions_in_menu() {
6789 self.hide_context_menu(window, cx);
6790 }
6791
6792 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6793 return;
6794 };
6795
6796 self.report_inline_completion_event(
6797 active_inline_completion.completion_id.clone(),
6798 true,
6799 cx,
6800 );
6801
6802 match &active_inline_completion.completion {
6803 InlineCompletion::Move { target, .. } => {
6804 let target = *target;
6805
6806 if let Some(position_map) = &self.last_position_map {
6807 if position_map
6808 .visible_row_range
6809 .contains(&target.to_display_point(&position_map.snapshot).row())
6810 || !self.edit_prediction_requires_modifier()
6811 {
6812 self.unfold_ranges(&[target..target], true, false, cx);
6813 // Note that this is also done in vim's handler of the Tab action.
6814 self.change_selections(
6815 Some(Autoscroll::newest()),
6816 window,
6817 cx,
6818 |selections| {
6819 selections.select_anchor_ranges([target..target]);
6820 },
6821 );
6822 self.clear_row_highlights::<EditPredictionPreview>();
6823
6824 self.edit_prediction_preview
6825 .set_previous_scroll_position(None);
6826 } else {
6827 self.edit_prediction_preview
6828 .set_previous_scroll_position(Some(
6829 position_map.snapshot.scroll_anchor,
6830 ));
6831
6832 self.highlight_rows::<EditPredictionPreview>(
6833 target..target,
6834 cx.theme().colors().editor_highlighted_line_background,
6835 RowHighlightOptions {
6836 autoscroll: true,
6837 ..Default::default()
6838 },
6839 cx,
6840 );
6841 self.request_autoscroll(Autoscroll::fit(), cx);
6842 }
6843 }
6844 }
6845 InlineCompletion::Edit { edits, .. } => {
6846 if let Some(provider) = self.edit_prediction_provider() {
6847 provider.accept(cx);
6848 }
6849
6850 // Store the transaction ID and selections before applying the edit
6851 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6852
6853 let snapshot = self.buffer.read(cx).snapshot(cx);
6854 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6855
6856 self.buffer.update(cx, |buffer, cx| {
6857 buffer.edit(edits.iter().cloned(), None, cx)
6858 });
6859
6860 self.change_selections(None, window, cx, |s| {
6861 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6862 });
6863
6864 let selections = self.selections.disjoint_anchors();
6865 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6866 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6867 if has_new_transaction {
6868 self.selection_history
6869 .insert_transaction(transaction_id_now, selections);
6870 }
6871 }
6872
6873 self.update_visible_inline_completion(window, cx);
6874 if self.active_inline_completion.is_none() {
6875 self.refresh_inline_completion(true, true, window, cx);
6876 }
6877
6878 cx.notify();
6879 }
6880 }
6881
6882 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6883 }
6884
6885 pub fn accept_partial_inline_completion(
6886 &mut self,
6887 _: &AcceptPartialEditPrediction,
6888 window: &mut Window,
6889 cx: &mut Context<Self>,
6890 ) {
6891 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6892 return;
6893 };
6894 if self.selections.count() != 1 {
6895 return;
6896 }
6897
6898 self.report_inline_completion_event(
6899 active_inline_completion.completion_id.clone(),
6900 true,
6901 cx,
6902 );
6903
6904 match &active_inline_completion.completion {
6905 InlineCompletion::Move { target, .. } => {
6906 let target = *target;
6907 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6908 selections.select_anchor_ranges([target..target]);
6909 });
6910 }
6911 InlineCompletion::Edit { edits, .. } => {
6912 // Find an insertion that starts at the cursor position.
6913 let snapshot = self.buffer.read(cx).snapshot(cx);
6914 let cursor_offset = self.selections.newest::<usize>(cx).head();
6915 let insertion = edits.iter().find_map(|(range, text)| {
6916 let range = range.to_offset(&snapshot);
6917 if range.is_empty() && range.start == cursor_offset {
6918 Some(text)
6919 } else {
6920 None
6921 }
6922 });
6923
6924 if let Some(text) = insertion {
6925 let mut partial_completion = text
6926 .chars()
6927 .by_ref()
6928 .take_while(|c| c.is_alphabetic())
6929 .collect::<String>();
6930 if partial_completion.is_empty() {
6931 partial_completion = text
6932 .chars()
6933 .by_ref()
6934 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6935 .collect::<String>();
6936 }
6937
6938 cx.emit(EditorEvent::InputHandled {
6939 utf16_range_to_replace: None,
6940 text: partial_completion.clone().into(),
6941 });
6942
6943 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6944
6945 self.refresh_inline_completion(true, true, window, cx);
6946 cx.notify();
6947 } else {
6948 self.accept_edit_prediction(&Default::default(), window, cx);
6949 }
6950 }
6951 }
6952 }
6953
6954 fn discard_inline_completion(
6955 &mut self,
6956 should_report_inline_completion_event: bool,
6957 cx: &mut Context<Self>,
6958 ) -> bool {
6959 if should_report_inline_completion_event {
6960 let completion_id = self
6961 .active_inline_completion
6962 .as_ref()
6963 .and_then(|active_completion| active_completion.completion_id.clone());
6964
6965 self.report_inline_completion_event(completion_id, false, cx);
6966 }
6967
6968 if let Some(provider) = self.edit_prediction_provider() {
6969 provider.discard(cx);
6970 }
6971
6972 self.take_active_inline_completion(cx)
6973 }
6974
6975 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6976 let Some(provider) = self.edit_prediction_provider() else {
6977 return;
6978 };
6979
6980 let Some((_, buffer, _)) = self
6981 .buffer
6982 .read(cx)
6983 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6984 else {
6985 return;
6986 };
6987
6988 let extension = buffer
6989 .read(cx)
6990 .file()
6991 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6992
6993 let event_type = match accepted {
6994 true => "Edit Prediction Accepted",
6995 false => "Edit Prediction Discarded",
6996 };
6997 telemetry::event!(
6998 event_type,
6999 provider = provider.name(),
7000 prediction_id = id,
7001 suggestion_accepted = accepted,
7002 file_extension = extension,
7003 );
7004 }
7005
7006 pub fn has_active_inline_completion(&self) -> bool {
7007 self.active_inline_completion.is_some()
7008 }
7009
7010 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7011 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7012 return false;
7013 };
7014
7015 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7016 self.clear_highlights::<InlineCompletionHighlight>(cx);
7017 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7018 true
7019 }
7020
7021 /// Returns true when we're displaying the edit prediction popover below the cursor
7022 /// like we are not previewing and the LSP autocomplete menu is visible
7023 /// or we are in `when_holding_modifier` mode.
7024 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7025 if self.edit_prediction_preview_is_active()
7026 || !self.show_edit_predictions_in_menu()
7027 || !self.edit_predictions_enabled()
7028 {
7029 return false;
7030 }
7031
7032 if self.has_visible_completions_menu() {
7033 return true;
7034 }
7035
7036 has_completion && self.edit_prediction_requires_modifier()
7037 }
7038
7039 fn handle_modifiers_changed(
7040 &mut self,
7041 modifiers: Modifiers,
7042 position_map: &PositionMap,
7043 window: &mut Window,
7044 cx: &mut Context<Self>,
7045 ) {
7046 if self.show_edit_predictions_in_menu() {
7047 self.update_edit_prediction_preview(&modifiers, window, cx);
7048 }
7049
7050 self.update_selection_mode(&modifiers, position_map, window, cx);
7051
7052 let mouse_position = window.mouse_position();
7053 if !position_map.text_hitbox.is_hovered(window) {
7054 return;
7055 }
7056
7057 self.update_hovered_link(
7058 position_map.point_for_position(mouse_position),
7059 &position_map.snapshot,
7060 modifiers,
7061 window,
7062 cx,
7063 )
7064 }
7065
7066 fn update_selection_mode(
7067 &mut self,
7068 modifiers: &Modifiers,
7069 position_map: &PositionMap,
7070 window: &mut Window,
7071 cx: &mut Context<Self>,
7072 ) {
7073 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
7074 return;
7075 }
7076
7077 let mouse_position = window.mouse_position();
7078 let point_for_position = position_map.point_for_position(mouse_position);
7079 let position = point_for_position.previous_valid;
7080
7081 self.select(
7082 SelectPhase::BeginColumnar {
7083 position,
7084 reset: false,
7085 goal_column: point_for_position.exact_unclipped.column(),
7086 },
7087 window,
7088 cx,
7089 );
7090 }
7091
7092 fn update_edit_prediction_preview(
7093 &mut self,
7094 modifiers: &Modifiers,
7095 window: &mut Window,
7096 cx: &mut Context<Self>,
7097 ) {
7098 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
7099 let Some(accept_keystroke) = accept_keybind.keystroke() else {
7100 return;
7101 };
7102
7103 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
7104 if matches!(
7105 self.edit_prediction_preview,
7106 EditPredictionPreview::Inactive { .. }
7107 ) {
7108 self.edit_prediction_preview = EditPredictionPreview::Active {
7109 previous_scroll_position: None,
7110 since: Instant::now(),
7111 };
7112
7113 self.update_visible_inline_completion(window, cx);
7114 cx.notify();
7115 }
7116 } else if let EditPredictionPreview::Active {
7117 previous_scroll_position,
7118 since,
7119 } = self.edit_prediction_preview
7120 {
7121 if let (Some(previous_scroll_position), Some(position_map)) =
7122 (previous_scroll_position, self.last_position_map.as_ref())
7123 {
7124 self.set_scroll_position(
7125 previous_scroll_position
7126 .scroll_position(&position_map.snapshot.display_snapshot),
7127 window,
7128 cx,
7129 );
7130 }
7131
7132 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7133 released_too_fast: since.elapsed() < Duration::from_millis(200),
7134 };
7135 self.clear_row_highlights::<EditPredictionPreview>();
7136 self.update_visible_inline_completion(window, cx);
7137 cx.notify();
7138 }
7139 }
7140
7141 fn update_visible_inline_completion(
7142 &mut self,
7143 _window: &mut Window,
7144 cx: &mut Context<Self>,
7145 ) -> Option<()> {
7146 let selection = self.selections.newest_anchor();
7147 let cursor = selection.head();
7148 let multibuffer = self.buffer.read(cx).snapshot(cx);
7149 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7150 let excerpt_id = cursor.excerpt_id;
7151
7152 let show_in_menu = self.show_edit_predictions_in_menu();
7153 let completions_menu_has_precedence = !show_in_menu
7154 && (self.context_menu.borrow().is_some()
7155 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7156
7157 if completions_menu_has_precedence
7158 || !offset_selection.is_empty()
7159 || self
7160 .active_inline_completion
7161 .as_ref()
7162 .map_or(false, |completion| {
7163 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7164 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7165 !invalidation_range.contains(&offset_selection.head())
7166 })
7167 {
7168 self.discard_inline_completion(false, cx);
7169 return None;
7170 }
7171
7172 self.take_active_inline_completion(cx);
7173 let Some(provider) = self.edit_prediction_provider() else {
7174 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7175 return None;
7176 };
7177
7178 let (buffer, cursor_buffer_position) =
7179 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7180
7181 self.edit_prediction_settings =
7182 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7183
7184 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7185
7186 if self.edit_prediction_indent_conflict {
7187 let cursor_point = cursor.to_point(&multibuffer);
7188
7189 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7190
7191 if let Some((_, indent)) = indents.iter().next() {
7192 if indent.len == cursor_point.column {
7193 self.edit_prediction_indent_conflict = false;
7194 }
7195 }
7196 }
7197
7198 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7199 let edits = inline_completion
7200 .edits
7201 .into_iter()
7202 .flat_map(|(range, new_text)| {
7203 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7204 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7205 Some((start..end, new_text))
7206 })
7207 .collect::<Vec<_>>();
7208 if edits.is_empty() {
7209 return None;
7210 }
7211
7212 let first_edit_start = edits.first().unwrap().0.start;
7213 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7214 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7215
7216 let last_edit_end = edits.last().unwrap().0.end;
7217 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7218 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7219
7220 let cursor_row = cursor.to_point(&multibuffer).row;
7221
7222 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7223
7224 let mut inlay_ids = Vec::new();
7225 let invalidation_row_range;
7226 let move_invalidation_row_range = if cursor_row < edit_start_row {
7227 Some(cursor_row..edit_end_row)
7228 } else if cursor_row > edit_end_row {
7229 Some(edit_start_row..cursor_row)
7230 } else {
7231 None
7232 };
7233 let is_move =
7234 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7235 let completion = if is_move {
7236 invalidation_row_range =
7237 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7238 let target = first_edit_start;
7239 InlineCompletion::Move { target, snapshot }
7240 } else {
7241 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7242 && !self.inline_completions_hidden_for_vim_mode;
7243
7244 if show_completions_in_buffer {
7245 if edits
7246 .iter()
7247 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7248 {
7249 let mut inlays = Vec::new();
7250 for (range, new_text) in &edits {
7251 let inlay = Inlay::inline_completion(
7252 post_inc(&mut self.next_inlay_id),
7253 range.start,
7254 new_text.as_str(),
7255 );
7256 inlay_ids.push(inlay.id);
7257 inlays.push(inlay);
7258 }
7259
7260 self.splice_inlays(&[], inlays, cx);
7261 } else {
7262 let background_color = cx.theme().status().deleted_background;
7263 self.highlight_text::<InlineCompletionHighlight>(
7264 edits.iter().map(|(range, _)| range.clone()).collect(),
7265 HighlightStyle {
7266 background_color: Some(background_color),
7267 ..Default::default()
7268 },
7269 cx,
7270 );
7271 }
7272 }
7273
7274 invalidation_row_range = edit_start_row..edit_end_row;
7275
7276 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7277 if provider.show_tab_accept_marker() {
7278 EditDisplayMode::TabAccept
7279 } else {
7280 EditDisplayMode::Inline
7281 }
7282 } else {
7283 EditDisplayMode::DiffPopover
7284 };
7285
7286 InlineCompletion::Edit {
7287 edits,
7288 edit_preview: inline_completion.edit_preview,
7289 display_mode,
7290 snapshot,
7291 }
7292 };
7293
7294 let invalidation_range = multibuffer
7295 .anchor_before(Point::new(invalidation_row_range.start, 0))
7296 ..multibuffer.anchor_after(Point::new(
7297 invalidation_row_range.end,
7298 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7299 ));
7300
7301 self.stale_inline_completion_in_menu = None;
7302 self.active_inline_completion = Some(InlineCompletionState {
7303 inlay_ids,
7304 completion,
7305 completion_id: inline_completion.id,
7306 invalidation_range,
7307 });
7308
7309 cx.notify();
7310
7311 Some(())
7312 }
7313
7314 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7315 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7316 }
7317
7318 fn clear_tasks(&mut self) {
7319 self.tasks.clear()
7320 }
7321
7322 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7323 if self.tasks.insert(key, value).is_some() {
7324 // This case should hopefully be rare, but just in case...
7325 log::error!(
7326 "multiple different run targets found on a single line, only the last target will be rendered"
7327 )
7328 }
7329 }
7330
7331 /// Get all display points of breakpoints that will be rendered within editor
7332 ///
7333 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7334 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7335 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7336 fn active_breakpoints(
7337 &self,
7338 range: Range<DisplayRow>,
7339 window: &mut Window,
7340 cx: &mut Context<Self>,
7341 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7342 let mut breakpoint_display_points = HashMap::default();
7343
7344 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7345 return breakpoint_display_points;
7346 };
7347
7348 let snapshot = self.snapshot(window, cx);
7349
7350 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7351 let Some(project) = self.project.as_ref() else {
7352 return breakpoint_display_points;
7353 };
7354
7355 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7356 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7357
7358 for (buffer_snapshot, range, excerpt_id) in
7359 multi_buffer_snapshot.range_to_buffer_ranges(range)
7360 {
7361 let Some(buffer) = project
7362 .read(cx)
7363 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7364 else {
7365 continue;
7366 };
7367 let breakpoints = breakpoint_store.read(cx).breakpoints(
7368 &buffer,
7369 Some(
7370 buffer_snapshot.anchor_before(range.start)
7371 ..buffer_snapshot.anchor_after(range.end),
7372 ),
7373 buffer_snapshot,
7374 cx,
7375 );
7376 for (breakpoint, state) in breakpoints {
7377 let multi_buffer_anchor =
7378 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7379 let position = multi_buffer_anchor
7380 .to_point(&multi_buffer_snapshot)
7381 .to_display_point(&snapshot);
7382
7383 breakpoint_display_points.insert(
7384 position.row(),
7385 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7386 );
7387 }
7388 }
7389
7390 breakpoint_display_points
7391 }
7392
7393 fn breakpoint_context_menu(
7394 &self,
7395 anchor: Anchor,
7396 window: &mut Window,
7397 cx: &mut Context<Self>,
7398 ) -> Entity<ui::ContextMenu> {
7399 let weak_editor = cx.weak_entity();
7400 let focus_handle = self.focus_handle(cx);
7401
7402 let row = self
7403 .buffer
7404 .read(cx)
7405 .snapshot(cx)
7406 .summary_for_anchor::<Point>(&anchor)
7407 .row;
7408
7409 let breakpoint = self
7410 .breakpoint_at_row(row, window, cx)
7411 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7412
7413 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7414 "Edit Log Breakpoint"
7415 } else {
7416 "Set Log Breakpoint"
7417 };
7418
7419 let condition_breakpoint_msg = if breakpoint
7420 .as_ref()
7421 .is_some_and(|bp| bp.1.condition.is_some())
7422 {
7423 "Edit Condition Breakpoint"
7424 } else {
7425 "Set Condition Breakpoint"
7426 };
7427
7428 let hit_condition_breakpoint_msg = if breakpoint
7429 .as_ref()
7430 .is_some_and(|bp| bp.1.hit_condition.is_some())
7431 {
7432 "Edit Hit Condition Breakpoint"
7433 } else {
7434 "Set Hit Condition Breakpoint"
7435 };
7436
7437 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7438 "Unset Breakpoint"
7439 } else {
7440 "Set Breakpoint"
7441 };
7442
7443 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7444 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7445
7446 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7447 BreakpointState::Enabled => Some("Disable"),
7448 BreakpointState::Disabled => Some("Enable"),
7449 });
7450
7451 let (anchor, breakpoint) =
7452 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7453
7454 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7455 menu.on_blur_subscription(Subscription::new(|| {}))
7456 .context(focus_handle)
7457 .when(run_to_cursor, |this| {
7458 let weak_editor = weak_editor.clone();
7459 this.entry("Run to cursor", None, move |window, cx| {
7460 weak_editor
7461 .update(cx, |editor, cx| {
7462 editor.change_selections(None, window, cx, |s| {
7463 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7464 });
7465 })
7466 .ok();
7467
7468 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7469 })
7470 .separator()
7471 })
7472 .when_some(toggle_state_msg, |this, msg| {
7473 this.entry(msg, None, {
7474 let weak_editor = weak_editor.clone();
7475 let breakpoint = breakpoint.clone();
7476 move |_window, cx| {
7477 weak_editor
7478 .update(cx, |this, cx| {
7479 this.edit_breakpoint_at_anchor(
7480 anchor,
7481 breakpoint.as_ref().clone(),
7482 BreakpointEditAction::InvertState,
7483 cx,
7484 );
7485 })
7486 .log_err();
7487 }
7488 })
7489 })
7490 .entry(set_breakpoint_msg, None, {
7491 let weak_editor = weak_editor.clone();
7492 let breakpoint = breakpoint.clone();
7493 move |_window, cx| {
7494 weak_editor
7495 .update(cx, |this, cx| {
7496 this.edit_breakpoint_at_anchor(
7497 anchor,
7498 breakpoint.as_ref().clone(),
7499 BreakpointEditAction::Toggle,
7500 cx,
7501 );
7502 })
7503 .log_err();
7504 }
7505 })
7506 .entry(log_breakpoint_msg, None, {
7507 let breakpoint = breakpoint.clone();
7508 let weak_editor = weak_editor.clone();
7509 move |window, cx| {
7510 weak_editor
7511 .update(cx, |this, cx| {
7512 this.add_edit_breakpoint_block(
7513 anchor,
7514 breakpoint.as_ref(),
7515 BreakpointPromptEditAction::Log,
7516 window,
7517 cx,
7518 );
7519 })
7520 .log_err();
7521 }
7522 })
7523 .entry(condition_breakpoint_msg, None, {
7524 let breakpoint = breakpoint.clone();
7525 let weak_editor = weak_editor.clone();
7526 move |window, cx| {
7527 weak_editor
7528 .update(cx, |this, cx| {
7529 this.add_edit_breakpoint_block(
7530 anchor,
7531 breakpoint.as_ref(),
7532 BreakpointPromptEditAction::Condition,
7533 window,
7534 cx,
7535 );
7536 })
7537 .log_err();
7538 }
7539 })
7540 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7541 weak_editor
7542 .update(cx, |this, cx| {
7543 this.add_edit_breakpoint_block(
7544 anchor,
7545 breakpoint.as_ref(),
7546 BreakpointPromptEditAction::HitCondition,
7547 window,
7548 cx,
7549 );
7550 })
7551 .log_err();
7552 })
7553 })
7554 }
7555
7556 fn render_breakpoint(
7557 &self,
7558 position: Anchor,
7559 row: DisplayRow,
7560 breakpoint: &Breakpoint,
7561 state: Option<BreakpointSessionState>,
7562 cx: &mut Context<Self>,
7563 ) -> IconButton {
7564 let is_rejected = state.is_some_and(|s| !s.verified);
7565 // Is it a breakpoint that shows up when hovering over gutter?
7566 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7567 (false, false),
7568 |PhantomBreakpointIndicator {
7569 is_active,
7570 display_row,
7571 collides_with_existing_breakpoint,
7572 }| {
7573 (
7574 is_active && display_row == row,
7575 collides_with_existing_breakpoint,
7576 )
7577 },
7578 );
7579
7580 let (color, icon) = {
7581 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7582 (false, false) => ui::IconName::DebugBreakpoint,
7583 (true, false) => ui::IconName::DebugLogBreakpoint,
7584 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7585 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7586 };
7587
7588 let color = if is_phantom {
7589 Color::Hint
7590 } else if is_rejected {
7591 Color::Disabled
7592 } else {
7593 Color::Debugger
7594 };
7595
7596 (color, icon)
7597 };
7598
7599 let breakpoint = Arc::from(breakpoint.clone());
7600
7601 let alt_as_text = gpui::Keystroke {
7602 modifiers: Modifiers::secondary_key(),
7603 ..Default::default()
7604 };
7605 let primary_action_text = if breakpoint.is_disabled() {
7606 "Enable breakpoint"
7607 } else if is_phantom && !collides_with_existing {
7608 "Set breakpoint"
7609 } else {
7610 "Unset breakpoint"
7611 };
7612 let focus_handle = self.focus_handle.clone();
7613
7614 let meta = if is_rejected {
7615 SharedString::from("No executable code is associated with this line.")
7616 } else if collides_with_existing && !breakpoint.is_disabled() {
7617 SharedString::from(format!(
7618 "{alt_as_text}-click to disable,\nright-click for more options."
7619 ))
7620 } else {
7621 SharedString::from("Right-click for more options.")
7622 };
7623 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7624 .icon_size(IconSize::XSmall)
7625 .size(ui::ButtonSize::None)
7626 .when(is_rejected, |this| {
7627 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7628 })
7629 .icon_color(color)
7630 .style(ButtonStyle::Transparent)
7631 .on_click(cx.listener({
7632 let breakpoint = breakpoint.clone();
7633
7634 move |editor, event: &ClickEvent, window, cx| {
7635 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7636 BreakpointEditAction::InvertState
7637 } else {
7638 BreakpointEditAction::Toggle
7639 };
7640
7641 window.focus(&editor.focus_handle(cx));
7642 editor.edit_breakpoint_at_anchor(
7643 position,
7644 breakpoint.as_ref().clone(),
7645 edit_action,
7646 cx,
7647 );
7648 }
7649 }))
7650 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7651 editor.set_breakpoint_context_menu(
7652 row,
7653 Some(position),
7654 event.down.position,
7655 window,
7656 cx,
7657 );
7658 }))
7659 .tooltip(move |window, cx| {
7660 Tooltip::with_meta_in(
7661 primary_action_text,
7662 Some(&ToggleBreakpoint),
7663 meta.clone(),
7664 &focus_handle,
7665 window,
7666 cx,
7667 )
7668 })
7669 }
7670
7671 fn build_tasks_context(
7672 project: &Entity<Project>,
7673 buffer: &Entity<Buffer>,
7674 buffer_row: u32,
7675 tasks: &Arc<RunnableTasks>,
7676 cx: &mut Context<Self>,
7677 ) -> Task<Option<task::TaskContext>> {
7678 let position = Point::new(buffer_row, tasks.column);
7679 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7680 let location = Location {
7681 buffer: buffer.clone(),
7682 range: range_start..range_start,
7683 };
7684 // Fill in the environmental variables from the tree-sitter captures
7685 let mut captured_task_variables = TaskVariables::default();
7686 for (capture_name, value) in tasks.extra_variables.clone() {
7687 captured_task_variables.insert(
7688 task::VariableName::Custom(capture_name.into()),
7689 value.clone(),
7690 );
7691 }
7692 project.update(cx, |project, cx| {
7693 project.task_store().update(cx, |task_store, cx| {
7694 task_store.task_context_for_location(captured_task_variables, location, cx)
7695 })
7696 })
7697 }
7698
7699 pub fn spawn_nearest_task(
7700 &mut self,
7701 action: &SpawnNearestTask,
7702 window: &mut Window,
7703 cx: &mut Context<Self>,
7704 ) {
7705 let Some((workspace, _)) = self.workspace.clone() else {
7706 return;
7707 };
7708 let Some(project) = self.project.clone() else {
7709 return;
7710 };
7711
7712 // Try to find a closest, enclosing node using tree-sitter that has a
7713 // task
7714 let Some((buffer, buffer_row, tasks)) = self
7715 .find_enclosing_node_task(cx)
7716 // Or find the task that's closest in row-distance.
7717 .or_else(|| self.find_closest_task(cx))
7718 else {
7719 return;
7720 };
7721
7722 let reveal_strategy = action.reveal;
7723 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7724 cx.spawn_in(window, async move |_, cx| {
7725 let context = task_context.await?;
7726 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7727
7728 let resolved = &mut resolved_task.resolved;
7729 resolved.reveal = reveal_strategy;
7730
7731 workspace
7732 .update_in(cx, |workspace, window, cx| {
7733 workspace.schedule_resolved_task(
7734 task_source_kind,
7735 resolved_task,
7736 false,
7737 window,
7738 cx,
7739 );
7740 })
7741 .ok()
7742 })
7743 .detach();
7744 }
7745
7746 fn find_closest_task(
7747 &mut self,
7748 cx: &mut Context<Self>,
7749 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7750 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7751
7752 let ((buffer_id, row), tasks) = self
7753 .tasks
7754 .iter()
7755 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7756
7757 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7758 let tasks = Arc::new(tasks.to_owned());
7759 Some((buffer, *row, tasks))
7760 }
7761
7762 fn find_enclosing_node_task(
7763 &mut self,
7764 cx: &mut Context<Self>,
7765 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7766 let snapshot = self.buffer.read(cx).snapshot(cx);
7767 let offset = self.selections.newest::<usize>(cx).head();
7768 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7769 let buffer_id = excerpt.buffer().remote_id();
7770
7771 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7772 let mut cursor = layer.node().walk();
7773
7774 while cursor.goto_first_child_for_byte(offset).is_some() {
7775 if cursor.node().end_byte() == offset {
7776 cursor.goto_next_sibling();
7777 }
7778 }
7779
7780 // Ascend to the smallest ancestor that contains the range and has a task.
7781 loop {
7782 let node = cursor.node();
7783 let node_range = node.byte_range();
7784 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7785
7786 // Check if this node contains our offset
7787 if node_range.start <= offset && node_range.end >= offset {
7788 // If it contains offset, check for task
7789 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7790 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7791 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7792 }
7793 }
7794
7795 if !cursor.goto_parent() {
7796 break;
7797 }
7798 }
7799 None
7800 }
7801
7802 fn render_run_indicator(
7803 &self,
7804 _style: &EditorStyle,
7805 is_active: bool,
7806 row: DisplayRow,
7807 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7808 cx: &mut Context<Self>,
7809 ) -> IconButton {
7810 let color = Color::Muted;
7811 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7812
7813 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7814 .shape(ui::IconButtonShape::Square)
7815 .icon_size(IconSize::XSmall)
7816 .icon_color(color)
7817 .toggle_state(is_active)
7818 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7819 let quick_launch = e.down.button == MouseButton::Left;
7820 window.focus(&editor.focus_handle(cx));
7821 editor.toggle_code_actions(
7822 &ToggleCodeActions {
7823 deployed_from: Some(CodeActionSource::Indicator(row)),
7824 quick_launch,
7825 },
7826 window,
7827 cx,
7828 );
7829 }))
7830 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7831 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7832 }))
7833 }
7834
7835 pub fn context_menu_visible(&self) -> bool {
7836 !self.edit_prediction_preview_is_active()
7837 && self
7838 .context_menu
7839 .borrow()
7840 .as_ref()
7841 .map_or(false, |menu| menu.visible())
7842 }
7843
7844 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7845 self.context_menu
7846 .borrow()
7847 .as_ref()
7848 .map(|menu| menu.origin())
7849 }
7850
7851 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7852 self.context_menu_options = Some(options);
7853 }
7854
7855 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7856 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7857
7858 fn render_edit_prediction_popover(
7859 &mut self,
7860 text_bounds: &Bounds<Pixels>,
7861 content_origin: gpui::Point<Pixels>,
7862 right_margin: Pixels,
7863 editor_snapshot: &EditorSnapshot,
7864 visible_row_range: Range<DisplayRow>,
7865 scroll_top: f32,
7866 scroll_bottom: f32,
7867 line_layouts: &[LineWithInvisibles],
7868 line_height: Pixels,
7869 scroll_pixel_position: gpui::Point<Pixels>,
7870 newest_selection_head: Option<DisplayPoint>,
7871 editor_width: Pixels,
7872 style: &EditorStyle,
7873 window: &mut Window,
7874 cx: &mut App,
7875 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7876 if self.mode().is_minimap() {
7877 return None;
7878 }
7879 let active_inline_completion = self.active_inline_completion.as_ref()?;
7880
7881 if self.edit_prediction_visible_in_cursor_popover(true) {
7882 return None;
7883 }
7884
7885 match &active_inline_completion.completion {
7886 InlineCompletion::Move { target, .. } => {
7887 let target_display_point = target.to_display_point(editor_snapshot);
7888
7889 if self.edit_prediction_requires_modifier() {
7890 if !self.edit_prediction_preview_is_active() {
7891 return None;
7892 }
7893
7894 self.render_edit_prediction_modifier_jump_popover(
7895 text_bounds,
7896 content_origin,
7897 visible_row_range,
7898 line_layouts,
7899 line_height,
7900 scroll_pixel_position,
7901 newest_selection_head,
7902 target_display_point,
7903 window,
7904 cx,
7905 )
7906 } else {
7907 self.render_edit_prediction_eager_jump_popover(
7908 text_bounds,
7909 content_origin,
7910 editor_snapshot,
7911 visible_row_range,
7912 scroll_top,
7913 scroll_bottom,
7914 line_height,
7915 scroll_pixel_position,
7916 target_display_point,
7917 editor_width,
7918 window,
7919 cx,
7920 )
7921 }
7922 }
7923 InlineCompletion::Edit {
7924 display_mode: EditDisplayMode::Inline,
7925 ..
7926 } => None,
7927 InlineCompletion::Edit {
7928 display_mode: EditDisplayMode::TabAccept,
7929 edits,
7930 ..
7931 } => {
7932 let range = &edits.first()?.0;
7933 let target_display_point = range.end.to_display_point(editor_snapshot);
7934
7935 self.render_edit_prediction_end_of_line_popover(
7936 "Accept",
7937 editor_snapshot,
7938 visible_row_range,
7939 target_display_point,
7940 line_height,
7941 scroll_pixel_position,
7942 content_origin,
7943 editor_width,
7944 window,
7945 cx,
7946 )
7947 }
7948 InlineCompletion::Edit {
7949 edits,
7950 edit_preview,
7951 display_mode: EditDisplayMode::DiffPopover,
7952 snapshot,
7953 } => self.render_edit_prediction_diff_popover(
7954 text_bounds,
7955 content_origin,
7956 right_margin,
7957 editor_snapshot,
7958 visible_row_range,
7959 line_layouts,
7960 line_height,
7961 scroll_pixel_position,
7962 newest_selection_head,
7963 editor_width,
7964 style,
7965 edits,
7966 edit_preview,
7967 snapshot,
7968 window,
7969 cx,
7970 ),
7971 }
7972 }
7973
7974 fn render_edit_prediction_modifier_jump_popover(
7975 &mut self,
7976 text_bounds: &Bounds<Pixels>,
7977 content_origin: gpui::Point<Pixels>,
7978 visible_row_range: Range<DisplayRow>,
7979 line_layouts: &[LineWithInvisibles],
7980 line_height: Pixels,
7981 scroll_pixel_position: gpui::Point<Pixels>,
7982 newest_selection_head: Option<DisplayPoint>,
7983 target_display_point: DisplayPoint,
7984 window: &mut Window,
7985 cx: &mut App,
7986 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7987 let scrolled_content_origin =
7988 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7989
7990 const SCROLL_PADDING_Y: Pixels = px(12.);
7991
7992 if target_display_point.row() < visible_row_range.start {
7993 return self.render_edit_prediction_scroll_popover(
7994 |_| SCROLL_PADDING_Y,
7995 IconName::ArrowUp,
7996 visible_row_range,
7997 line_layouts,
7998 newest_selection_head,
7999 scrolled_content_origin,
8000 window,
8001 cx,
8002 );
8003 } else if target_display_point.row() >= visible_row_range.end {
8004 return self.render_edit_prediction_scroll_popover(
8005 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8006 IconName::ArrowDown,
8007 visible_row_range,
8008 line_layouts,
8009 newest_selection_head,
8010 scrolled_content_origin,
8011 window,
8012 cx,
8013 );
8014 }
8015
8016 const POLE_WIDTH: Pixels = px(2.);
8017
8018 let line_layout =
8019 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8020 let target_column = target_display_point.column() as usize;
8021
8022 let target_x = line_layout.x_for_index(target_column);
8023 let target_y =
8024 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8025
8026 let flag_on_right = target_x < text_bounds.size.width / 2.;
8027
8028 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8029 border_color.l += 0.001;
8030
8031 let mut element = v_flex()
8032 .items_end()
8033 .when(flag_on_right, |el| el.items_start())
8034 .child(if flag_on_right {
8035 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8036 .rounded_bl(px(0.))
8037 .rounded_tl(px(0.))
8038 .border_l_2()
8039 .border_color(border_color)
8040 } else {
8041 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8042 .rounded_br(px(0.))
8043 .rounded_tr(px(0.))
8044 .border_r_2()
8045 .border_color(border_color)
8046 })
8047 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8048 .into_any();
8049
8050 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8051
8052 let mut origin = scrolled_content_origin + point(target_x, target_y)
8053 - point(
8054 if flag_on_right {
8055 POLE_WIDTH
8056 } else {
8057 size.width - POLE_WIDTH
8058 },
8059 size.height - line_height,
8060 );
8061
8062 origin.x = origin.x.max(content_origin.x);
8063
8064 element.prepaint_at(origin, window, cx);
8065
8066 Some((element, origin))
8067 }
8068
8069 fn render_edit_prediction_scroll_popover(
8070 &mut self,
8071 to_y: impl Fn(Size<Pixels>) -> Pixels,
8072 scroll_icon: IconName,
8073 visible_row_range: Range<DisplayRow>,
8074 line_layouts: &[LineWithInvisibles],
8075 newest_selection_head: Option<DisplayPoint>,
8076 scrolled_content_origin: gpui::Point<Pixels>,
8077 window: &mut Window,
8078 cx: &mut App,
8079 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8080 let mut element = self
8081 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8082 .into_any();
8083
8084 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8085
8086 let cursor = newest_selection_head?;
8087 let cursor_row_layout =
8088 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8089 let cursor_column = cursor.column() as usize;
8090
8091 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8092
8093 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8094
8095 element.prepaint_at(origin, window, cx);
8096 Some((element, origin))
8097 }
8098
8099 fn render_edit_prediction_eager_jump_popover(
8100 &mut self,
8101 text_bounds: &Bounds<Pixels>,
8102 content_origin: gpui::Point<Pixels>,
8103 editor_snapshot: &EditorSnapshot,
8104 visible_row_range: Range<DisplayRow>,
8105 scroll_top: f32,
8106 scroll_bottom: f32,
8107 line_height: Pixels,
8108 scroll_pixel_position: gpui::Point<Pixels>,
8109 target_display_point: DisplayPoint,
8110 editor_width: Pixels,
8111 window: &mut Window,
8112 cx: &mut App,
8113 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8114 if target_display_point.row().as_f32() < scroll_top {
8115 let mut element = self
8116 .render_edit_prediction_line_popover(
8117 "Jump to Edit",
8118 Some(IconName::ArrowUp),
8119 window,
8120 cx,
8121 )?
8122 .into_any();
8123
8124 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8125 let offset = point(
8126 (text_bounds.size.width - size.width) / 2.,
8127 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8128 );
8129
8130 let origin = text_bounds.origin + offset;
8131 element.prepaint_at(origin, window, cx);
8132 Some((element, origin))
8133 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8134 let mut element = self
8135 .render_edit_prediction_line_popover(
8136 "Jump to Edit",
8137 Some(IconName::ArrowDown),
8138 window,
8139 cx,
8140 )?
8141 .into_any();
8142
8143 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8144 let offset = point(
8145 (text_bounds.size.width - size.width) / 2.,
8146 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8147 );
8148
8149 let origin = text_bounds.origin + offset;
8150 element.prepaint_at(origin, window, cx);
8151 Some((element, origin))
8152 } else {
8153 self.render_edit_prediction_end_of_line_popover(
8154 "Jump to Edit",
8155 editor_snapshot,
8156 visible_row_range,
8157 target_display_point,
8158 line_height,
8159 scroll_pixel_position,
8160 content_origin,
8161 editor_width,
8162 window,
8163 cx,
8164 )
8165 }
8166 }
8167
8168 fn render_edit_prediction_end_of_line_popover(
8169 self: &mut Editor,
8170 label: &'static str,
8171 editor_snapshot: &EditorSnapshot,
8172 visible_row_range: Range<DisplayRow>,
8173 target_display_point: DisplayPoint,
8174 line_height: Pixels,
8175 scroll_pixel_position: gpui::Point<Pixels>,
8176 content_origin: gpui::Point<Pixels>,
8177 editor_width: Pixels,
8178 window: &mut Window,
8179 cx: &mut App,
8180 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8181 let target_line_end = DisplayPoint::new(
8182 target_display_point.row(),
8183 editor_snapshot.line_len(target_display_point.row()),
8184 );
8185
8186 let mut element = self
8187 .render_edit_prediction_line_popover(label, None, window, cx)?
8188 .into_any();
8189
8190 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8191
8192 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8193
8194 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8195 let mut origin = start_point
8196 + line_origin
8197 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8198 origin.x = origin.x.max(content_origin.x);
8199
8200 let max_x = content_origin.x + editor_width - size.width;
8201
8202 if origin.x > max_x {
8203 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8204
8205 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8206 origin.y += offset;
8207 IconName::ArrowUp
8208 } else {
8209 origin.y -= offset;
8210 IconName::ArrowDown
8211 };
8212
8213 element = self
8214 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8215 .into_any();
8216
8217 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8218
8219 origin.x = content_origin.x + editor_width - size.width - px(2.);
8220 }
8221
8222 element.prepaint_at(origin, window, cx);
8223 Some((element, origin))
8224 }
8225
8226 fn render_edit_prediction_diff_popover(
8227 self: &Editor,
8228 text_bounds: &Bounds<Pixels>,
8229 content_origin: gpui::Point<Pixels>,
8230 right_margin: Pixels,
8231 editor_snapshot: &EditorSnapshot,
8232 visible_row_range: Range<DisplayRow>,
8233 line_layouts: &[LineWithInvisibles],
8234 line_height: Pixels,
8235 scroll_pixel_position: gpui::Point<Pixels>,
8236 newest_selection_head: Option<DisplayPoint>,
8237 editor_width: Pixels,
8238 style: &EditorStyle,
8239 edits: &Vec<(Range<Anchor>, String)>,
8240 edit_preview: &Option<language::EditPreview>,
8241 snapshot: &language::BufferSnapshot,
8242 window: &mut Window,
8243 cx: &mut App,
8244 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8245 let edit_start = edits
8246 .first()
8247 .unwrap()
8248 .0
8249 .start
8250 .to_display_point(editor_snapshot);
8251 let edit_end = edits
8252 .last()
8253 .unwrap()
8254 .0
8255 .end
8256 .to_display_point(editor_snapshot);
8257
8258 let is_visible = visible_row_range.contains(&edit_start.row())
8259 || visible_row_range.contains(&edit_end.row());
8260 if !is_visible {
8261 return None;
8262 }
8263
8264 let highlighted_edits =
8265 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8266
8267 let styled_text = highlighted_edits.to_styled_text(&style.text);
8268 let line_count = highlighted_edits.text.lines().count();
8269
8270 const BORDER_WIDTH: Pixels = px(1.);
8271
8272 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8273 let has_keybind = keybind.is_some();
8274
8275 let mut element = h_flex()
8276 .items_start()
8277 .child(
8278 h_flex()
8279 .bg(cx.theme().colors().editor_background)
8280 .border(BORDER_WIDTH)
8281 .shadow_sm()
8282 .border_color(cx.theme().colors().border)
8283 .rounded_l_lg()
8284 .when(line_count > 1, |el| el.rounded_br_lg())
8285 .pr_1()
8286 .child(styled_text),
8287 )
8288 .child(
8289 h_flex()
8290 .h(line_height + BORDER_WIDTH * 2.)
8291 .px_1p5()
8292 .gap_1()
8293 // Workaround: For some reason, there's a gap if we don't do this
8294 .ml(-BORDER_WIDTH)
8295 .shadow(vec![gpui::BoxShadow {
8296 color: gpui::black().opacity(0.05),
8297 offset: point(px(1.), px(1.)),
8298 blur_radius: px(2.),
8299 spread_radius: px(0.),
8300 }])
8301 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8302 .border(BORDER_WIDTH)
8303 .border_color(cx.theme().colors().border)
8304 .rounded_r_lg()
8305 .id("edit_prediction_diff_popover_keybind")
8306 .when(!has_keybind, |el| {
8307 let status_colors = cx.theme().status();
8308
8309 el.bg(status_colors.error_background)
8310 .border_color(status_colors.error.opacity(0.6))
8311 .child(Icon::new(IconName::Info).color(Color::Error))
8312 .cursor_default()
8313 .hoverable_tooltip(move |_window, cx| {
8314 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8315 })
8316 })
8317 .children(keybind),
8318 )
8319 .into_any();
8320
8321 let longest_row =
8322 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8323 let longest_line_width = if visible_row_range.contains(&longest_row) {
8324 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8325 } else {
8326 layout_line(
8327 longest_row,
8328 editor_snapshot,
8329 style,
8330 editor_width,
8331 |_| false,
8332 window,
8333 cx,
8334 )
8335 .width
8336 };
8337
8338 let viewport_bounds =
8339 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8340 right: -right_margin,
8341 ..Default::default()
8342 });
8343
8344 let x_after_longest =
8345 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8346 - scroll_pixel_position.x;
8347
8348 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8349
8350 // Fully visible if it can be displayed within the window (allow overlapping other
8351 // panes). However, this is only allowed if the popover starts within text_bounds.
8352 let can_position_to_the_right = x_after_longest < text_bounds.right()
8353 && x_after_longest + element_bounds.width < viewport_bounds.right();
8354
8355 let mut origin = if can_position_to_the_right {
8356 point(
8357 x_after_longest,
8358 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8359 - scroll_pixel_position.y,
8360 )
8361 } else {
8362 let cursor_row = newest_selection_head.map(|head| head.row());
8363 let above_edit = edit_start
8364 .row()
8365 .0
8366 .checked_sub(line_count as u32)
8367 .map(DisplayRow);
8368 let below_edit = Some(edit_end.row() + 1);
8369 let above_cursor =
8370 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8371 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8372
8373 // Place the edit popover adjacent to the edit if there is a location
8374 // available that is onscreen and does not obscure the cursor. Otherwise,
8375 // place it adjacent to the cursor.
8376 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8377 .into_iter()
8378 .flatten()
8379 .find(|&start_row| {
8380 let end_row = start_row + line_count as u32;
8381 visible_row_range.contains(&start_row)
8382 && visible_row_range.contains(&end_row)
8383 && cursor_row.map_or(true, |cursor_row| {
8384 !((start_row..end_row).contains(&cursor_row))
8385 })
8386 })?;
8387
8388 content_origin
8389 + point(
8390 -scroll_pixel_position.x,
8391 row_target.as_f32() * line_height - scroll_pixel_position.y,
8392 )
8393 };
8394
8395 origin.x -= BORDER_WIDTH;
8396
8397 window.defer_draw(element, origin, 1);
8398
8399 // Do not return an element, since it will already be drawn due to defer_draw.
8400 None
8401 }
8402
8403 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8404 px(30.)
8405 }
8406
8407 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8408 if self.read_only(cx) {
8409 cx.theme().players().read_only()
8410 } else {
8411 self.style.as_ref().unwrap().local_player
8412 }
8413 }
8414
8415 fn render_edit_prediction_accept_keybind(
8416 &self,
8417 window: &mut Window,
8418 cx: &App,
8419 ) -> Option<AnyElement> {
8420 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8421 let accept_keystroke = accept_binding.keystroke()?;
8422
8423 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8424
8425 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8426 Color::Accent
8427 } else {
8428 Color::Muted
8429 };
8430
8431 h_flex()
8432 .px_0p5()
8433 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8434 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8435 .text_size(TextSize::XSmall.rems(cx))
8436 .child(h_flex().children(ui::render_modifiers(
8437 &accept_keystroke.modifiers,
8438 PlatformStyle::platform(),
8439 Some(modifiers_color),
8440 Some(IconSize::XSmall.rems().into()),
8441 true,
8442 )))
8443 .when(is_platform_style_mac, |parent| {
8444 parent.child(accept_keystroke.key.clone())
8445 })
8446 .when(!is_platform_style_mac, |parent| {
8447 parent.child(
8448 Key::new(
8449 util::capitalize(&accept_keystroke.key),
8450 Some(Color::Default),
8451 )
8452 .size(Some(IconSize::XSmall.rems().into())),
8453 )
8454 })
8455 .into_any()
8456 .into()
8457 }
8458
8459 fn render_edit_prediction_line_popover(
8460 &self,
8461 label: impl Into<SharedString>,
8462 icon: Option<IconName>,
8463 window: &mut Window,
8464 cx: &App,
8465 ) -> Option<Stateful<Div>> {
8466 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8467
8468 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8469 let has_keybind = keybind.is_some();
8470
8471 let result = h_flex()
8472 .id("ep-line-popover")
8473 .py_0p5()
8474 .pl_1()
8475 .pr(padding_right)
8476 .gap_1()
8477 .rounded_md()
8478 .border_1()
8479 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8480 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8481 .shadow_sm()
8482 .when(!has_keybind, |el| {
8483 let status_colors = cx.theme().status();
8484
8485 el.bg(status_colors.error_background)
8486 .border_color(status_colors.error.opacity(0.6))
8487 .pl_2()
8488 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8489 .cursor_default()
8490 .hoverable_tooltip(move |_window, cx| {
8491 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8492 })
8493 })
8494 .children(keybind)
8495 .child(
8496 Label::new(label)
8497 .size(LabelSize::Small)
8498 .when(!has_keybind, |el| {
8499 el.color(cx.theme().status().error.into()).strikethrough()
8500 }),
8501 )
8502 .when(!has_keybind, |el| {
8503 el.child(
8504 h_flex().ml_1().child(
8505 Icon::new(IconName::Info)
8506 .size(IconSize::Small)
8507 .color(cx.theme().status().error.into()),
8508 ),
8509 )
8510 })
8511 .when_some(icon, |element, icon| {
8512 element.child(
8513 div()
8514 .mt(px(1.5))
8515 .child(Icon::new(icon).size(IconSize::Small)),
8516 )
8517 });
8518
8519 Some(result)
8520 }
8521
8522 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8523 let accent_color = cx.theme().colors().text_accent;
8524 let editor_bg_color = cx.theme().colors().editor_background;
8525 editor_bg_color.blend(accent_color.opacity(0.1))
8526 }
8527
8528 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8529 let accent_color = cx.theme().colors().text_accent;
8530 let editor_bg_color = cx.theme().colors().editor_background;
8531 editor_bg_color.blend(accent_color.opacity(0.6))
8532 }
8533
8534 fn render_edit_prediction_cursor_popover(
8535 &self,
8536 min_width: Pixels,
8537 max_width: Pixels,
8538 cursor_point: Point,
8539 style: &EditorStyle,
8540 accept_keystroke: Option<&gpui::Keystroke>,
8541 _window: &Window,
8542 cx: &mut Context<Editor>,
8543 ) -> Option<AnyElement> {
8544 let provider = self.edit_prediction_provider.as_ref()?;
8545
8546 if provider.provider.needs_terms_acceptance(cx) {
8547 return Some(
8548 h_flex()
8549 .min_w(min_width)
8550 .flex_1()
8551 .px_2()
8552 .py_1()
8553 .gap_3()
8554 .elevation_2(cx)
8555 .hover(|style| style.bg(cx.theme().colors().element_hover))
8556 .id("accept-terms")
8557 .cursor_pointer()
8558 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8559 .on_click(cx.listener(|this, _event, window, cx| {
8560 cx.stop_propagation();
8561 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8562 window.dispatch_action(
8563 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8564 cx,
8565 );
8566 }))
8567 .child(
8568 h_flex()
8569 .flex_1()
8570 .gap_2()
8571 .child(Icon::new(IconName::ZedPredict))
8572 .child(Label::new("Accept Terms of Service"))
8573 .child(div().w_full())
8574 .child(
8575 Icon::new(IconName::ArrowUpRight)
8576 .color(Color::Muted)
8577 .size(IconSize::Small),
8578 )
8579 .into_any_element(),
8580 )
8581 .into_any(),
8582 );
8583 }
8584
8585 let is_refreshing = provider.provider.is_refreshing(cx);
8586
8587 fn pending_completion_container() -> Div {
8588 h_flex()
8589 .h_full()
8590 .flex_1()
8591 .gap_2()
8592 .child(Icon::new(IconName::ZedPredict))
8593 }
8594
8595 let completion = match &self.active_inline_completion {
8596 Some(prediction) => {
8597 if !self.has_visible_completions_menu() {
8598 const RADIUS: Pixels = px(6.);
8599 const BORDER_WIDTH: Pixels = px(1.);
8600
8601 return Some(
8602 h_flex()
8603 .elevation_2(cx)
8604 .border(BORDER_WIDTH)
8605 .border_color(cx.theme().colors().border)
8606 .when(accept_keystroke.is_none(), |el| {
8607 el.border_color(cx.theme().status().error)
8608 })
8609 .rounded(RADIUS)
8610 .rounded_tl(px(0.))
8611 .overflow_hidden()
8612 .child(div().px_1p5().child(match &prediction.completion {
8613 InlineCompletion::Move { target, snapshot } => {
8614 use text::ToPoint as _;
8615 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8616 {
8617 Icon::new(IconName::ZedPredictDown)
8618 } else {
8619 Icon::new(IconName::ZedPredictUp)
8620 }
8621 }
8622 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8623 }))
8624 .child(
8625 h_flex()
8626 .gap_1()
8627 .py_1()
8628 .px_2()
8629 .rounded_r(RADIUS - BORDER_WIDTH)
8630 .border_l_1()
8631 .border_color(cx.theme().colors().border)
8632 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8633 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8634 el.child(
8635 Label::new("Hold")
8636 .size(LabelSize::Small)
8637 .when(accept_keystroke.is_none(), |el| {
8638 el.strikethrough()
8639 })
8640 .line_height_style(LineHeightStyle::UiLabel),
8641 )
8642 })
8643 .id("edit_prediction_cursor_popover_keybind")
8644 .when(accept_keystroke.is_none(), |el| {
8645 let status_colors = cx.theme().status();
8646
8647 el.bg(status_colors.error_background)
8648 .border_color(status_colors.error.opacity(0.6))
8649 .child(Icon::new(IconName::Info).color(Color::Error))
8650 .cursor_default()
8651 .hoverable_tooltip(move |_window, cx| {
8652 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8653 .into()
8654 })
8655 })
8656 .when_some(
8657 accept_keystroke.as_ref(),
8658 |el, accept_keystroke| {
8659 el.child(h_flex().children(ui::render_modifiers(
8660 &accept_keystroke.modifiers,
8661 PlatformStyle::platform(),
8662 Some(Color::Default),
8663 Some(IconSize::XSmall.rems().into()),
8664 false,
8665 )))
8666 },
8667 ),
8668 )
8669 .into_any(),
8670 );
8671 }
8672
8673 self.render_edit_prediction_cursor_popover_preview(
8674 prediction,
8675 cursor_point,
8676 style,
8677 cx,
8678 )?
8679 }
8680
8681 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8682 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8683 stale_completion,
8684 cursor_point,
8685 style,
8686 cx,
8687 )?,
8688
8689 None => {
8690 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8691 }
8692 },
8693
8694 None => pending_completion_container().child(Label::new("No Prediction")),
8695 };
8696
8697 let completion = if is_refreshing {
8698 completion
8699 .with_animation(
8700 "loading-completion",
8701 Animation::new(Duration::from_secs(2))
8702 .repeat()
8703 .with_easing(pulsating_between(0.4, 0.8)),
8704 |label, delta| label.opacity(delta),
8705 )
8706 .into_any_element()
8707 } else {
8708 completion.into_any_element()
8709 };
8710
8711 let has_completion = self.active_inline_completion.is_some();
8712
8713 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8714 Some(
8715 h_flex()
8716 .min_w(min_width)
8717 .max_w(max_width)
8718 .flex_1()
8719 .elevation_2(cx)
8720 .border_color(cx.theme().colors().border)
8721 .child(
8722 div()
8723 .flex_1()
8724 .py_1()
8725 .px_2()
8726 .overflow_hidden()
8727 .child(completion),
8728 )
8729 .when_some(accept_keystroke, |el, accept_keystroke| {
8730 if !accept_keystroke.modifiers.modified() {
8731 return el;
8732 }
8733
8734 el.child(
8735 h_flex()
8736 .h_full()
8737 .border_l_1()
8738 .rounded_r_lg()
8739 .border_color(cx.theme().colors().border)
8740 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8741 .gap_1()
8742 .py_1()
8743 .px_2()
8744 .child(
8745 h_flex()
8746 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8747 .when(is_platform_style_mac, |parent| parent.gap_1())
8748 .child(h_flex().children(ui::render_modifiers(
8749 &accept_keystroke.modifiers,
8750 PlatformStyle::platform(),
8751 Some(if !has_completion {
8752 Color::Muted
8753 } else {
8754 Color::Default
8755 }),
8756 None,
8757 false,
8758 ))),
8759 )
8760 .child(Label::new("Preview").into_any_element())
8761 .opacity(if has_completion { 1.0 } else { 0.4 }),
8762 )
8763 })
8764 .into_any(),
8765 )
8766 }
8767
8768 fn render_edit_prediction_cursor_popover_preview(
8769 &self,
8770 completion: &InlineCompletionState,
8771 cursor_point: Point,
8772 style: &EditorStyle,
8773 cx: &mut Context<Editor>,
8774 ) -> Option<Div> {
8775 use text::ToPoint as _;
8776
8777 fn render_relative_row_jump(
8778 prefix: impl Into<String>,
8779 current_row: u32,
8780 target_row: u32,
8781 ) -> Div {
8782 let (row_diff, arrow) = if target_row < current_row {
8783 (current_row - target_row, IconName::ArrowUp)
8784 } else {
8785 (target_row - current_row, IconName::ArrowDown)
8786 };
8787
8788 h_flex()
8789 .child(
8790 Label::new(format!("{}{}", prefix.into(), row_diff))
8791 .color(Color::Muted)
8792 .size(LabelSize::Small),
8793 )
8794 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8795 }
8796
8797 match &completion.completion {
8798 InlineCompletion::Move {
8799 target, snapshot, ..
8800 } => Some(
8801 h_flex()
8802 .px_2()
8803 .gap_2()
8804 .flex_1()
8805 .child(
8806 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8807 Icon::new(IconName::ZedPredictDown)
8808 } else {
8809 Icon::new(IconName::ZedPredictUp)
8810 },
8811 )
8812 .child(Label::new("Jump to Edit")),
8813 ),
8814
8815 InlineCompletion::Edit {
8816 edits,
8817 edit_preview,
8818 snapshot,
8819 display_mode: _,
8820 } => {
8821 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8822
8823 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8824 &snapshot,
8825 &edits,
8826 edit_preview.as_ref()?,
8827 true,
8828 cx,
8829 )
8830 .first_line_preview();
8831
8832 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8833 .with_default_highlights(&style.text, highlighted_edits.highlights);
8834
8835 let preview = h_flex()
8836 .gap_1()
8837 .min_w_16()
8838 .child(styled_text)
8839 .when(has_more_lines, |parent| parent.child("…"));
8840
8841 let left = if first_edit_row != cursor_point.row {
8842 render_relative_row_jump("", cursor_point.row, first_edit_row)
8843 .into_any_element()
8844 } else {
8845 Icon::new(IconName::ZedPredict).into_any_element()
8846 };
8847
8848 Some(
8849 h_flex()
8850 .h_full()
8851 .flex_1()
8852 .gap_2()
8853 .pr_1()
8854 .overflow_x_hidden()
8855 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8856 .child(left)
8857 .child(preview),
8858 )
8859 }
8860 }
8861 }
8862
8863 pub fn render_context_menu(
8864 &self,
8865 style: &EditorStyle,
8866 max_height_in_lines: u32,
8867 window: &mut Window,
8868 cx: &mut Context<Editor>,
8869 ) -> Option<AnyElement> {
8870 let menu = self.context_menu.borrow();
8871 let menu = menu.as_ref()?;
8872 if !menu.visible() {
8873 return None;
8874 };
8875 Some(menu.render(style, max_height_in_lines, window, cx))
8876 }
8877
8878 fn render_context_menu_aside(
8879 &mut self,
8880 max_size: Size<Pixels>,
8881 window: &mut Window,
8882 cx: &mut Context<Editor>,
8883 ) -> Option<AnyElement> {
8884 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8885 if menu.visible() {
8886 menu.render_aside(max_size, window, cx)
8887 } else {
8888 None
8889 }
8890 })
8891 }
8892
8893 fn hide_context_menu(
8894 &mut self,
8895 window: &mut Window,
8896 cx: &mut Context<Self>,
8897 ) -> Option<CodeContextMenu> {
8898 cx.notify();
8899 self.completion_tasks.clear();
8900 let context_menu = self.context_menu.borrow_mut().take();
8901 self.stale_inline_completion_in_menu.take();
8902 self.update_visible_inline_completion(window, cx);
8903 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8904 if let Some(completion_provider) = &self.completion_provider {
8905 completion_provider.selection_changed(None, window, cx);
8906 }
8907 }
8908 context_menu
8909 }
8910
8911 fn show_snippet_choices(
8912 &mut self,
8913 choices: &Vec<String>,
8914 selection: Range<Anchor>,
8915 cx: &mut Context<Self>,
8916 ) {
8917 if selection.start.buffer_id.is_none() {
8918 return;
8919 }
8920 let buffer_id = selection.start.buffer_id.unwrap();
8921 let buffer = self.buffer().read(cx).buffer(buffer_id);
8922 let id = post_inc(&mut self.next_completion_id);
8923 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8924
8925 if let Some(buffer) = buffer {
8926 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8927 CompletionsMenu::new_snippet_choices(
8928 id,
8929 true,
8930 choices,
8931 selection,
8932 buffer,
8933 snippet_sort_order,
8934 ),
8935 ));
8936 }
8937 }
8938
8939 pub fn insert_snippet(
8940 &mut self,
8941 insertion_ranges: &[Range<usize>],
8942 snippet: Snippet,
8943 window: &mut Window,
8944 cx: &mut Context<Self>,
8945 ) -> Result<()> {
8946 struct Tabstop<T> {
8947 is_end_tabstop: bool,
8948 ranges: Vec<Range<T>>,
8949 choices: Option<Vec<String>>,
8950 }
8951
8952 let tabstops = self.buffer.update(cx, |buffer, cx| {
8953 let snippet_text: Arc<str> = snippet.text.clone().into();
8954 let edits = insertion_ranges
8955 .iter()
8956 .cloned()
8957 .map(|range| (range, snippet_text.clone()));
8958 let autoindent_mode = AutoindentMode::Block {
8959 original_indent_columns: Vec::new(),
8960 };
8961 buffer.edit(edits, Some(autoindent_mode), cx);
8962
8963 let snapshot = &*buffer.read(cx);
8964 let snippet = &snippet;
8965 snippet
8966 .tabstops
8967 .iter()
8968 .map(|tabstop| {
8969 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8970 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8971 });
8972 let mut tabstop_ranges = tabstop
8973 .ranges
8974 .iter()
8975 .flat_map(|tabstop_range| {
8976 let mut delta = 0_isize;
8977 insertion_ranges.iter().map(move |insertion_range| {
8978 let insertion_start = insertion_range.start as isize + delta;
8979 delta +=
8980 snippet.text.len() as isize - insertion_range.len() as isize;
8981
8982 let start = ((insertion_start + tabstop_range.start) as usize)
8983 .min(snapshot.len());
8984 let end = ((insertion_start + tabstop_range.end) as usize)
8985 .min(snapshot.len());
8986 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8987 })
8988 })
8989 .collect::<Vec<_>>();
8990 // Sort in reverse order so that the first range is the newest created
8991 // selection. Completions will use it and autoscroll will prioritize it.
8992 tabstop_ranges.sort_unstable_by(|a, b| b.start.cmp(&a.start, snapshot));
8993
8994 Tabstop {
8995 is_end_tabstop,
8996 ranges: tabstop_ranges,
8997 choices: tabstop.choices.clone(),
8998 }
8999 })
9000 .collect::<Vec<_>>()
9001 });
9002 if let Some(tabstop) = tabstops.first() {
9003 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9004 s.select_ranges(tabstop.ranges.iter().cloned());
9005 });
9006
9007 if let Some(choices) = &tabstop.choices {
9008 if let Some(selection) = tabstop.ranges.first() {
9009 self.show_snippet_choices(choices, selection.clone(), cx)
9010 }
9011 }
9012
9013 // If we're already at the last tabstop and it's at the end of the snippet,
9014 // we're done, we don't need to keep the state around.
9015 if !tabstop.is_end_tabstop {
9016 let choices = tabstops
9017 .iter()
9018 .map(|tabstop| tabstop.choices.clone())
9019 .collect();
9020
9021 let ranges = tabstops
9022 .into_iter()
9023 .map(|tabstop| tabstop.ranges)
9024 .collect::<Vec<_>>();
9025
9026 self.snippet_stack.push(SnippetState {
9027 active_index: 0,
9028 ranges,
9029 choices,
9030 });
9031 }
9032
9033 // Check whether the just-entered snippet ends with an auto-closable bracket.
9034 if self.autoclose_regions.is_empty() {
9035 let snapshot = self.buffer.read(cx).snapshot(cx);
9036 for selection in &mut self.selections.all::<Point>(cx) {
9037 let selection_head = selection.head();
9038 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9039 continue;
9040 };
9041
9042 let mut bracket_pair = None;
9043 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9044 let prev_chars = snapshot
9045 .reversed_chars_at(selection_head)
9046 .collect::<String>();
9047 for (pair, enabled) in scope.brackets() {
9048 if enabled
9049 && pair.close
9050 && prev_chars.starts_with(pair.start.as_str())
9051 && next_chars.starts_with(pair.end.as_str())
9052 {
9053 bracket_pair = Some(pair.clone());
9054 break;
9055 }
9056 }
9057 if let Some(pair) = bracket_pair {
9058 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9059 let autoclose_enabled =
9060 self.use_autoclose && snapshot_settings.use_autoclose;
9061 if autoclose_enabled {
9062 let start = snapshot.anchor_after(selection_head);
9063 let end = snapshot.anchor_after(selection_head);
9064 self.autoclose_regions.push(AutocloseRegion {
9065 selection_id: selection.id,
9066 range: start..end,
9067 pair,
9068 });
9069 }
9070 }
9071 }
9072 }
9073 }
9074 Ok(())
9075 }
9076
9077 pub fn move_to_next_snippet_tabstop(
9078 &mut self,
9079 window: &mut Window,
9080 cx: &mut Context<Self>,
9081 ) -> bool {
9082 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9083 }
9084
9085 pub fn move_to_prev_snippet_tabstop(
9086 &mut self,
9087 window: &mut Window,
9088 cx: &mut Context<Self>,
9089 ) -> bool {
9090 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9091 }
9092
9093 pub fn move_to_snippet_tabstop(
9094 &mut self,
9095 bias: Bias,
9096 window: &mut Window,
9097 cx: &mut Context<Self>,
9098 ) -> bool {
9099 if let Some(mut snippet) = self.snippet_stack.pop() {
9100 match bias {
9101 Bias::Left => {
9102 if snippet.active_index > 0 {
9103 snippet.active_index -= 1;
9104 } else {
9105 self.snippet_stack.push(snippet);
9106 return false;
9107 }
9108 }
9109 Bias::Right => {
9110 if snippet.active_index + 1 < snippet.ranges.len() {
9111 snippet.active_index += 1;
9112 } else {
9113 self.snippet_stack.push(snippet);
9114 return false;
9115 }
9116 }
9117 }
9118 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9119 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9120 s.select_ranges(current_ranges.iter().cloned())
9121 });
9122
9123 if let Some(choices) = &snippet.choices[snippet.active_index] {
9124 if let Some(selection) = current_ranges.first() {
9125 self.show_snippet_choices(&choices, selection.clone(), cx);
9126 }
9127 }
9128
9129 // If snippet state is not at the last tabstop, push it back on the stack
9130 if snippet.active_index + 1 < snippet.ranges.len() {
9131 self.snippet_stack.push(snippet);
9132 }
9133 return true;
9134 }
9135 }
9136
9137 false
9138 }
9139
9140 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9141 self.transact(window, cx, |this, window, cx| {
9142 this.select_all(&SelectAll, window, cx);
9143 this.insert("", window, cx);
9144 });
9145 }
9146
9147 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9148 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9149 self.transact(window, cx, |this, window, cx| {
9150 this.select_autoclose_pair(window, cx);
9151 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9152 if !this.linked_edit_ranges.is_empty() {
9153 let selections = this.selections.all::<MultiBufferPoint>(cx);
9154 let snapshot = this.buffer.read(cx).snapshot(cx);
9155
9156 for selection in selections.iter() {
9157 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9158 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9159 if selection_start.buffer_id != selection_end.buffer_id {
9160 continue;
9161 }
9162 if let Some(ranges) =
9163 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9164 {
9165 for (buffer, entries) in ranges {
9166 linked_ranges.entry(buffer).or_default().extend(entries);
9167 }
9168 }
9169 }
9170 }
9171
9172 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9173 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9174 for selection in &mut selections {
9175 if selection.is_empty() {
9176 let old_head = selection.head();
9177 let mut new_head =
9178 movement::left(&display_map, old_head.to_display_point(&display_map))
9179 .to_point(&display_map);
9180 if let Some((buffer, line_buffer_range)) = display_map
9181 .buffer_snapshot
9182 .buffer_line_for_row(MultiBufferRow(old_head.row))
9183 {
9184 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9185 let indent_len = match indent_size.kind {
9186 IndentKind::Space => {
9187 buffer.settings_at(line_buffer_range.start, cx).tab_size
9188 }
9189 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9190 };
9191 if old_head.column <= indent_size.len && old_head.column > 0 {
9192 let indent_len = indent_len.get();
9193 new_head = cmp::min(
9194 new_head,
9195 MultiBufferPoint::new(
9196 old_head.row,
9197 ((old_head.column - 1) / indent_len) * indent_len,
9198 ),
9199 );
9200 }
9201 }
9202
9203 selection.set_head(new_head, SelectionGoal::None);
9204 }
9205 }
9206
9207 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9208 s.select(selections)
9209 });
9210 this.insert("", window, cx);
9211 let empty_str: Arc<str> = Arc::from("");
9212 for (buffer, edits) in linked_ranges {
9213 let snapshot = buffer.read(cx).snapshot();
9214 use text::ToPoint as TP;
9215
9216 let edits = edits
9217 .into_iter()
9218 .map(|range| {
9219 let end_point = TP::to_point(&range.end, &snapshot);
9220 let mut start_point = TP::to_point(&range.start, &snapshot);
9221
9222 if end_point == start_point {
9223 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9224 .saturating_sub(1);
9225 start_point =
9226 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9227 };
9228
9229 (start_point..end_point, empty_str.clone())
9230 })
9231 .sorted_by_key(|(range, _)| range.start)
9232 .collect::<Vec<_>>();
9233 buffer.update(cx, |this, cx| {
9234 this.edit(edits, None, cx);
9235 })
9236 }
9237 this.refresh_inline_completion(true, false, window, cx);
9238 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9239 });
9240 }
9241
9242 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9243 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9244 self.transact(window, cx, |this, window, cx| {
9245 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9246 s.move_with(|map, selection| {
9247 if selection.is_empty() {
9248 let cursor = movement::right(map, selection.head());
9249 selection.end = cursor;
9250 selection.reversed = true;
9251 selection.goal = SelectionGoal::None;
9252 }
9253 })
9254 });
9255 this.insert("", window, cx);
9256 this.refresh_inline_completion(true, false, window, cx);
9257 });
9258 }
9259
9260 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9261 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9262 if self.move_to_prev_snippet_tabstop(window, cx) {
9263 return;
9264 }
9265 self.outdent(&Outdent, window, cx);
9266 }
9267
9268 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9269 if self.move_to_next_snippet_tabstop(window, cx) {
9270 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9271 return;
9272 }
9273 if self.read_only(cx) {
9274 return;
9275 }
9276 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9277 let mut selections = self.selections.all_adjusted(cx);
9278 let buffer = self.buffer.read(cx);
9279 let snapshot = buffer.snapshot(cx);
9280 let rows_iter = selections.iter().map(|s| s.head().row);
9281 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9282
9283 let has_some_cursor_in_whitespace = selections
9284 .iter()
9285 .filter(|selection| selection.is_empty())
9286 .any(|selection| {
9287 let cursor = selection.head();
9288 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9289 cursor.column < current_indent.len
9290 });
9291
9292 let mut edits = Vec::new();
9293 let mut prev_edited_row = 0;
9294 let mut row_delta = 0;
9295 for selection in &mut selections {
9296 if selection.start.row != prev_edited_row {
9297 row_delta = 0;
9298 }
9299 prev_edited_row = selection.end.row;
9300
9301 // If the selection is non-empty, then increase the indentation of the selected lines.
9302 if !selection.is_empty() {
9303 row_delta =
9304 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9305 continue;
9306 }
9307
9308 let cursor = selection.head();
9309 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9310 if let Some(suggested_indent) =
9311 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9312 {
9313 // Don't do anything if already at suggested indent
9314 // and there is any other cursor which is not
9315 if has_some_cursor_in_whitespace
9316 && cursor.column == current_indent.len
9317 && current_indent.len == suggested_indent.len
9318 {
9319 continue;
9320 }
9321
9322 // Adjust line and move cursor to suggested indent
9323 // if cursor is not at suggested indent
9324 if cursor.column < suggested_indent.len
9325 && cursor.column <= current_indent.len
9326 && current_indent.len <= suggested_indent.len
9327 {
9328 selection.start = Point::new(cursor.row, suggested_indent.len);
9329 selection.end = selection.start;
9330 if row_delta == 0 {
9331 edits.extend(Buffer::edit_for_indent_size_adjustment(
9332 cursor.row,
9333 current_indent,
9334 suggested_indent,
9335 ));
9336 row_delta = suggested_indent.len - current_indent.len;
9337 }
9338 continue;
9339 }
9340
9341 // If current indent is more than suggested indent
9342 // only move cursor to current indent and skip indent
9343 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9344 selection.start = Point::new(cursor.row, current_indent.len);
9345 selection.end = selection.start;
9346 continue;
9347 }
9348 }
9349
9350 // Otherwise, insert a hard or soft tab.
9351 let settings = buffer.language_settings_at(cursor, cx);
9352 let tab_size = if settings.hard_tabs {
9353 IndentSize::tab()
9354 } else {
9355 let tab_size = settings.tab_size.get();
9356 let indent_remainder = snapshot
9357 .text_for_range(Point::new(cursor.row, 0)..cursor)
9358 .flat_map(str::chars)
9359 .fold(row_delta % tab_size, |counter: u32, c| {
9360 if c == '\t' {
9361 0
9362 } else {
9363 (counter + 1) % tab_size
9364 }
9365 });
9366
9367 let chars_to_next_tab_stop = tab_size - indent_remainder;
9368 IndentSize::spaces(chars_to_next_tab_stop)
9369 };
9370 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9371 selection.end = selection.start;
9372 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9373 row_delta += tab_size.len;
9374 }
9375
9376 self.transact(window, cx, |this, window, cx| {
9377 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9378 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9379 s.select(selections)
9380 });
9381 this.refresh_inline_completion(true, false, window, cx);
9382 });
9383 }
9384
9385 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9386 if self.read_only(cx) {
9387 return;
9388 }
9389 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9390 let mut selections = self.selections.all::<Point>(cx);
9391 let mut prev_edited_row = 0;
9392 let mut row_delta = 0;
9393 let mut edits = Vec::new();
9394 let buffer = self.buffer.read(cx);
9395 let snapshot = buffer.snapshot(cx);
9396 for selection in &mut selections {
9397 if selection.start.row != prev_edited_row {
9398 row_delta = 0;
9399 }
9400 prev_edited_row = selection.end.row;
9401
9402 row_delta =
9403 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9404 }
9405
9406 self.transact(window, cx, |this, window, cx| {
9407 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9408 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9409 s.select(selections)
9410 });
9411 });
9412 }
9413
9414 fn indent_selection(
9415 buffer: &MultiBuffer,
9416 snapshot: &MultiBufferSnapshot,
9417 selection: &mut Selection<Point>,
9418 edits: &mut Vec<(Range<Point>, String)>,
9419 delta_for_start_row: u32,
9420 cx: &App,
9421 ) -> u32 {
9422 let settings = buffer.language_settings_at(selection.start, cx);
9423 let tab_size = settings.tab_size.get();
9424 let indent_kind = if settings.hard_tabs {
9425 IndentKind::Tab
9426 } else {
9427 IndentKind::Space
9428 };
9429 let mut start_row = selection.start.row;
9430 let mut end_row = selection.end.row + 1;
9431
9432 // If a selection ends at the beginning of a line, don't indent
9433 // that last line.
9434 if selection.end.column == 0 && selection.end.row > selection.start.row {
9435 end_row -= 1;
9436 }
9437
9438 // Avoid re-indenting a row that has already been indented by a
9439 // previous selection, but still update this selection's column
9440 // to reflect that indentation.
9441 if delta_for_start_row > 0 {
9442 start_row += 1;
9443 selection.start.column += delta_for_start_row;
9444 if selection.end.row == selection.start.row {
9445 selection.end.column += delta_for_start_row;
9446 }
9447 }
9448
9449 let mut delta_for_end_row = 0;
9450 let has_multiple_rows = start_row + 1 != end_row;
9451 for row in start_row..end_row {
9452 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9453 let indent_delta = match (current_indent.kind, indent_kind) {
9454 (IndentKind::Space, IndentKind::Space) => {
9455 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9456 IndentSize::spaces(columns_to_next_tab_stop)
9457 }
9458 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9459 (_, IndentKind::Tab) => IndentSize::tab(),
9460 };
9461
9462 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9463 0
9464 } else {
9465 selection.start.column
9466 };
9467 let row_start = Point::new(row, start);
9468 edits.push((
9469 row_start..row_start,
9470 indent_delta.chars().collect::<String>(),
9471 ));
9472
9473 // Update this selection's endpoints to reflect the indentation.
9474 if row == selection.start.row {
9475 selection.start.column += indent_delta.len;
9476 }
9477 if row == selection.end.row {
9478 selection.end.column += indent_delta.len;
9479 delta_for_end_row = indent_delta.len;
9480 }
9481 }
9482
9483 if selection.start.row == selection.end.row {
9484 delta_for_start_row + delta_for_end_row
9485 } else {
9486 delta_for_end_row
9487 }
9488 }
9489
9490 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9491 if self.read_only(cx) {
9492 return;
9493 }
9494 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9495 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9496 let selections = self.selections.all::<Point>(cx);
9497 let mut deletion_ranges = Vec::new();
9498 let mut last_outdent = None;
9499 {
9500 let buffer = self.buffer.read(cx);
9501 let snapshot = buffer.snapshot(cx);
9502 for selection in &selections {
9503 let settings = buffer.language_settings_at(selection.start, cx);
9504 let tab_size = settings.tab_size.get();
9505 let mut rows = selection.spanned_rows(false, &display_map);
9506
9507 // Avoid re-outdenting a row that has already been outdented by a
9508 // previous selection.
9509 if let Some(last_row) = last_outdent {
9510 if last_row == rows.start {
9511 rows.start = rows.start.next_row();
9512 }
9513 }
9514 let has_multiple_rows = rows.len() > 1;
9515 for row in rows.iter_rows() {
9516 let indent_size = snapshot.indent_size_for_line(row);
9517 if indent_size.len > 0 {
9518 let deletion_len = match indent_size.kind {
9519 IndentKind::Space => {
9520 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9521 if columns_to_prev_tab_stop == 0 {
9522 tab_size
9523 } else {
9524 columns_to_prev_tab_stop
9525 }
9526 }
9527 IndentKind::Tab => 1,
9528 };
9529 let start = if has_multiple_rows
9530 || deletion_len > selection.start.column
9531 || indent_size.len < selection.start.column
9532 {
9533 0
9534 } else {
9535 selection.start.column - deletion_len
9536 };
9537 deletion_ranges.push(
9538 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9539 );
9540 last_outdent = Some(row);
9541 }
9542 }
9543 }
9544 }
9545
9546 self.transact(window, cx, |this, window, cx| {
9547 this.buffer.update(cx, |buffer, cx| {
9548 let empty_str: Arc<str> = Arc::default();
9549 buffer.edit(
9550 deletion_ranges
9551 .into_iter()
9552 .map(|range| (range, empty_str.clone())),
9553 None,
9554 cx,
9555 );
9556 });
9557 let selections = this.selections.all::<usize>(cx);
9558 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9559 s.select(selections)
9560 });
9561 });
9562 }
9563
9564 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9565 if self.read_only(cx) {
9566 return;
9567 }
9568 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9569 let selections = self
9570 .selections
9571 .all::<usize>(cx)
9572 .into_iter()
9573 .map(|s| s.range());
9574
9575 self.transact(window, cx, |this, window, cx| {
9576 this.buffer.update(cx, |buffer, cx| {
9577 buffer.autoindent_ranges(selections, cx);
9578 });
9579 let selections = this.selections.all::<usize>(cx);
9580 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9581 s.select(selections)
9582 });
9583 });
9584 }
9585
9586 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9587 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9588 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9589 let selections = self.selections.all::<Point>(cx);
9590
9591 let mut new_cursors = Vec::new();
9592 let mut edit_ranges = Vec::new();
9593 let mut selections = selections.iter().peekable();
9594 while let Some(selection) = selections.next() {
9595 let mut rows = selection.spanned_rows(false, &display_map);
9596 let goal_display_column = selection.head().to_display_point(&display_map).column();
9597
9598 // Accumulate contiguous regions of rows that we want to delete.
9599 while let Some(next_selection) = selections.peek() {
9600 let next_rows = next_selection.spanned_rows(false, &display_map);
9601 if next_rows.start <= rows.end {
9602 rows.end = next_rows.end;
9603 selections.next().unwrap();
9604 } else {
9605 break;
9606 }
9607 }
9608
9609 let buffer = &display_map.buffer_snapshot;
9610 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9611 let edit_end;
9612 let cursor_buffer_row;
9613 if buffer.max_point().row >= rows.end.0 {
9614 // If there's a line after the range, delete the \n from the end of the row range
9615 // and position the cursor on the next line.
9616 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9617 cursor_buffer_row = rows.end;
9618 } else {
9619 // If there isn't a line after the range, delete the \n from the line before the
9620 // start of the row range and position the cursor there.
9621 edit_start = edit_start.saturating_sub(1);
9622 edit_end = buffer.len();
9623 cursor_buffer_row = rows.start.previous_row();
9624 }
9625
9626 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9627 *cursor.column_mut() =
9628 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9629
9630 new_cursors.push((
9631 selection.id,
9632 buffer.anchor_after(cursor.to_point(&display_map)),
9633 ));
9634 edit_ranges.push(edit_start..edit_end);
9635 }
9636
9637 self.transact(window, cx, |this, window, cx| {
9638 let buffer = this.buffer.update(cx, |buffer, cx| {
9639 let empty_str: Arc<str> = Arc::default();
9640 buffer.edit(
9641 edit_ranges
9642 .into_iter()
9643 .map(|range| (range, empty_str.clone())),
9644 None,
9645 cx,
9646 );
9647 buffer.snapshot(cx)
9648 });
9649 let new_selections = new_cursors
9650 .into_iter()
9651 .map(|(id, cursor)| {
9652 let cursor = cursor.to_point(&buffer);
9653 Selection {
9654 id,
9655 start: cursor,
9656 end: cursor,
9657 reversed: false,
9658 goal: SelectionGoal::None,
9659 }
9660 })
9661 .collect();
9662
9663 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9664 s.select(new_selections);
9665 });
9666 });
9667 }
9668
9669 pub fn join_lines_impl(
9670 &mut self,
9671 insert_whitespace: bool,
9672 window: &mut Window,
9673 cx: &mut Context<Self>,
9674 ) {
9675 if self.read_only(cx) {
9676 return;
9677 }
9678 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9679 for selection in self.selections.all::<Point>(cx) {
9680 let start = MultiBufferRow(selection.start.row);
9681 // Treat single line selections as if they include the next line. Otherwise this action
9682 // would do nothing for single line selections individual cursors.
9683 let end = if selection.start.row == selection.end.row {
9684 MultiBufferRow(selection.start.row + 1)
9685 } else {
9686 MultiBufferRow(selection.end.row)
9687 };
9688
9689 if let Some(last_row_range) = row_ranges.last_mut() {
9690 if start <= last_row_range.end {
9691 last_row_range.end = end;
9692 continue;
9693 }
9694 }
9695 row_ranges.push(start..end);
9696 }
9697
9698 let snapshot = self.buffer.read(cx).snapshot(cx);
9699 let mut cursor_positions = Vec::new();
9700 for row_range in &row_ranges {
9701 let anchor = snapshot.anchor_before(Point::new(
9702 row_range.end.previous_row().0,
9703 snapshot.line_len(row_range.end.previous_row()),
9704 ));
9705 cursor_positions.push(anchor..anchor);
9706 }
9707
9708 self.transact(window, cx, |this, window, cx| {
9709 for row_range in row_ranges.into_iter().rev() {
9710 for row in row_range.iter_rows().rev() {
9711 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9712 let next_line_row = row.next_row();
9713 let indent = snapshot.indent_size_for_line(next_line_row);
9714 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9715
9716 let replace =
9717 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9718 " "
9719 } else {
9720 ""
9721 };
9722
9723 this.buffer.update(cx, |buffer, cx| {
9724 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9725 });
9726 }
9727 }
9728
9729 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9730 s.select_anchor_ranges(cursor_positions)
9731 });
9732 });
9733 }
9734
9735 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9736 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9737 self.join_lines_impl(true, window, cx);
9738 }
9739
9740 pub fn sort_lines_case_sensitive(
9741 &mut self,
9742 _: &SortLinesCaseSensitive,
9743 window: &mut Window,
9744 cx: &mut Context<Self>,
9745 ) {
9746 self.manipulate_lines(window, cx, |lines| lines.sort())
9747 }
9748
9749 pub fn sort_lines_case_insensitive(
9750 &mut self,
9751 _: &SortLinesCaseInsensitive,
9752 window: &mut Window,
9753 cx: &mut Context<Self>,
9754 ) {
9755 self.manipulate_lines(window, cx, |lines| {
9756 lines.sort_by_key(|line| line.to_lowercase())
9757 })
9758 }
9759
9760 pub fn unique_lines_case_insensitive(
9761 &mut self,
9762 _: &UniqueLinesCaseInsensitive,
9763 window: &mut Window,
9764 cx: &mut Context<Self>,
9765 ) {
9766 self.manipulate_lines(window, cx, |lines| {
9767 let mut seen = HashSet::default();
9768 lines.retain(|line| seen.insert(line.to_lowercase()));
9769 })
9770 }
9771
9772 pub fn unique_lines_case_sensitive(
9773 &mut self,
9774 _: &UniqueLinesCaseSensitive,
9775 window: &mut Window,
9776 cx: &mut Context<Self>,
9777 ) {
9778 self.manipulate_lines(window, cx, |lines| {
9779 let mut seen = HashSet::default();
9780 lines.retain(|line| seen.insert(*line));
9781 })
9782 }
9783
9784 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9785 let Some(project) = self.project.clone() else {
9786 return;
9787 };
9788 self.reload(project, window, cx)
9789 .detach_and_notify_err(window, cx);
9790 }
9791
9792 pub fn restore_file(
9793 &mut self,
9794 _: &::git::RestoreFile,
9795 window: &mut Window,
9796 cx: &mut Context<Self>,
9797 ) {
9798 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9799 let mut buffer_ids = HashSet::default();
9800 let snapshot = self.buffer().read(cx).snapshot(cx);
9801 for selection in self.selections.all::<usize>(cx) {
9802 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9803 }
9804
9805 let buffer = self.buffer().read(cx);
9806 let ranges = buffer_ids
9807 .into_iter()
9808 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9809 .collect::<Vec<_>>();
9810
9811 self.restore_hunks_in_ranges(ranges, window, cx);
9812 }
9813
9814 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9815 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9816 let selections = self
9817 .selections
9818 .all(cx)
9819 .into_iter()
9820 .map(|s| s.range())
9821 .collect();
9822 self.restore_hunks_in_ranges(selections, window, cx);
9823 }
9824
9825 pub fn restore_hunks_in_ranges(
9826 &mut self,
9827 ranges: Vec<Range<Point>>,
9828 window: &mut Window,
9829 cx: &mut Context<Editor>,
9830 ) {
9831 let mut revert_changes = HashMap::default();
9832 let chunk_by = self
9833 .snapshot(window, cx)
9834 .hunks_for_ranges(ranges)
9835 .into_iter()
9836 .chunk_by(|hunk| hunk.buffer_id);
9837 for (buffer_id, hunks) in &chunk_by {
9838 let hunks = hunks.collect::<Vec<_>>();
9839 for hunk in &hunks {
9840 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9841 }
9842 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9843 }
9844 drop(chunk_by);
9845 if !revert_changes.is_empty() {
9846 self.transact(window, cx, |editor, window, cx| {
9847 editor.restore(revert_changes, window, cx);
9848 });
9849 }
9850 }
9851
9852 pub fn open_active_item_in_terminal(
9853 &mut self,
9854 _: &OpenInTerminal,
9855 window: &mut Window,
9856 cx: &mut Context<Self>,
9857 ) {
9858 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9859 let project_path = buffer.read(cx).project_path(cx)?;
9860 let project = self.project.as_ref()?.read(cx);
9861 let entry = project.entry_for_path(&project_path, cx)?;
9862 let parent = match &entry.canonical_path {
9863 Some(canonical_path) => canonical_path.to_path_buf(),
9864 None => project.absolute_path(&project_path, cx)?,
9865 }
9866 .parent()?
9867 .to_path_buf();
9868 Some(parent)
9869 }) {
9870 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9871 }
9872 }
9873
9874 fn set_breakpoint_context_menu(
9875 &mut self,
9876 display_row: DisplayRow,
9877 position: Option<Anchor>,
9878 clicked_point: gpui::Point<Pixels>,
9879 window: &mut Window,
9880 cx: &mut Context<Self>,
9881 ) {
9882 if !cx.has_flag::<DebuggerFeatureFlag>() {
9883 return;
9884 }
9885 let source = self
9886 .buffer
9887 .read(cx)
9888 .snapshot(cx)
9889 .anchor_before(Point::new(display_row.0, 0u32));
9890
9891 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9892
9893 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9894 self,
9895 source,
9896 clicked_point,
9897 context_menu,
9898 window,
9899 cx,
9900 );
9901 }
9902
9903 fn add_edit_breakpoint_block(
9904 &mut self,
9905 anchor: Anchor,
9906 breakpoint: &Breakpoint,
9907 edit_action: BreakpointPromptEditAction,
9908 window: &mut Window,
9909 cx: &mut Context<Self>,
9910 ) {
9911 let weak_editor = cx.weak_entity();
9912 let bp_prompt = cx.new(|cx| {
9913 BreakpointPromptEditor::new(
9914 weak_editor,
9915 anchor,
9916 breakpoint.clone(),
9917 edit_action,
9918 window,
9919 cx,
9920 )
9921 });
9922
9923 let height = bp_prompt.update(cx, |this, cx| {
9924 this.prompt
9925 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9926 });
9927 let cloned_prompt = bp_prompt.clone();
9928 let blocks = vec![BlockProperties {
9929 style: BlockStyle::Sticky,
9930 placement: BlockPlacement::Above(anchor),
9931 height: Some(height),
9932 render: Arc::new(move |cx| {
9933 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9934 cloned_prompt.clone().into_any_element()
9935 }),
9936 priority: 0,
9937 render_in_minimap: true,
9938 }];
9939
9940 let focus_handle = bp_prompt.focus_handle(cx);
9941 window.focus(&focus_handle);
9942
9943 let block_ids = self.insert_blocks(blocks, None, cx);
9944 bp_prompt.update(cx, |prompt, _| {
9945 prompt.add_block_ids(block_ids);
9946 });
9947 }
9948
9949 pub(crate) fn breakpoint_at_row(
9950 &self,
9951 row: u32,
9952 window: &mut Window,
9953 cx: &mut Context<Self>,
9954 ) -> Option<(Anchor, Breakpoint)> {
9955 let snapshot = self.snapshot(window, cx);
9956 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9957
9958 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9959 }
9960
9961 pub(crate) fn breakpoint_at_anchor(
9962 &self,
9963 breakpoint_position: Anchor,
9964 snapshot: &EditorSnapshot,
9965 cx: &mut Context<Self>,
9966 ) -> Option<(Anchor, Breakpoint)> {
9967 let project = self.project.clone()?;
9968
9969 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9970 snapshot
9971 .buffer_snapshot
9972 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9973 })?;
9974
9975 let enclosing_excerpt = breakpoint_position.excerpt_id;
9976 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
9977 let buffer_snapshot = buffer.read(cx).snapshot();
9978
9979 let row = buffer_snapshot
9980 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9981 .row;
9982
9983 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9984 let anchor_end = snapshot
9985 .buffer_snapshot
9986 .anchor_after(Point::new(row, line_len));
9987
9988 let bp = self
9989 .breakpoint_store
9990 .as_ref()?
9991 .read_with(cx, |breakpoint_store, cx| {
9992 breakpoint_store
9993 .breakpoints(
9994 &buffer,
9995 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9996 &buffer_snapshot,
9997 cx,
9998 )
9999 .next()
10000 .and_then(|(bp, _)| {
10001 let breakpoint_row = buffer_snapshot
10002 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10003 .row;
10004
10005 if breakpoint_row == row {
10006 snapshot
10007 .buffer_snapshot
10008 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10009 .map(|position| (position, bp.bp.clone()))
10010 } else {
10011 None
10012 }
10013 })
10014 });
10015 bp
10016 }
10017
10018 pub fn edit_log_breakpoint(
10019 &mut self,
10020 _: &EditLogBreakpoint,
10021 window: &mut Window,
10022 cx: &mut Context<Self>,
10023 ) {
10024 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10025 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10026 message: None,
10027 state: BreakpointState::Enabled,
10028 condition: None,
10029 hit_condition: None,
10030 });
10031
10032 self.add_edit_breakpoint_block(
10033 anchor,
10034 &breakpoint,
10035 BreakpointPromptEditAction::Log,
10036 window,
10037 cx,
10038 );
10039 }
10040 }
10041
10042 fn breakpoints_at_cursors(
10043 &self,
10044 window: &mut Window,
10045 cx: &mut Context<Self>,
10046 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10047 let snapshot = self.snapshot(window, cx);
10048 let cursors = self
10049 .selections
10050 .disjoint_anchors()
10051 .into_iter()
10052 .map(|selection| {
10053 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10054
10055 let breakpoint_position = self
10056 .breakpoint_at_row(cursor_position.row, window, cx)
10057 .map(|bp| bp.0)
10058 .unwrap_or_else(|| {
10059 snapshot
10060 .display_snapshot
10061 .buffer_snapshot
10062 .anchor_after(Point::new(cursor_position.row, 0))
10063 });
10064
10065 let breakpoint = self
10066 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10067 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10068
10069 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10070 })
10071 // 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.
10072 .collect::<HashMap<Anchor, _>>();
10073
10074 cursors.into_iter().collect()
10075 }
10076
10077 pub fn enable_breakpoint(
10078 &mut self,
10079 _: &crate::actions::EnableBreakpoint,
10080 window: &mut Window,
10081 cx: &mut Context<Self>,
10082 ) {
10083 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10084 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10085 continue;
10086 };
10087 self.edit_breakpoint_at_anchor(
10088 anchor,
10089 breakpoint,
10090 BreakpointEditAction::InvertState,
10091 cx,
10092 );
10093 }
10094 }
10095
10096 pub fn disable_breakpoint(
10097 &mut self,
10098 _: &crate::actions::DisableBreakpoint,
10099 window: &mut Window,
10100 cx: &mut Context<Self>,
10101 ) {
10102 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10103 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10104 continue;
10105 };
10106 self.edit_breakpoint_at_anchor(
10107 anchor,
10108 breakpoint,
10109 BreakpointEditAction::InvertState,
10110 cx,
10111 );
10112 }
10113 }
10114
10115 pub fn toggle_breakpoint(
10116 &mut self,
10117 _: &crate::actions::ToggleBreakpoint,
10118 window: &mut Window,
10119 cx: &mut Context<Self>,
10120 ) {
10121 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10122 if let Some(breakpoint) = breakpoint {
10123 self.edit_breakpoint_at_anchor(
10124 anchor,
10125 breakpoint,
10126 BreakpointEditAction::Toggle,
10127 cx,
10128 );
10129 } else {
10130 self.edit_breakpoint_at_anchor(
10131 anchor,
10132 Breakpoint::new_standard(),
10133 BreakpointEditAction::Toggle,
10134 cx,
10135 );
10136 }
10137 }
10138 }
10139
10140 pub fn edit_breakpoint_at_anchor(
10141 &mut self,
10142 breakpoint_position: Anchor,
10143 breakpoint: Breakpoint,
10144 edit_action: BreakpointEditAction,
10145 cx: &mut Context<Self>,
10146 ) {
10147 let Some(breakpoint_store) = &self.breakpoint_store else {
10148 return;
10149 };
10150
10151 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10152 if breakpoint_position == Anchor::min() {
10153 self.buffer()
10154 .read(cx)
10155 .excerpt_buffer_ids()
10156 .into_iter()
10157 .next()
10158 } else {
10159 None
10160 }
10161 }) else {
10162 return;
10163 };
10164
10165 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10166 return;
10167 };
10168
10169 breakpoint_store.update(cx, |breakpoint_store, cx| {
10170 breakpoint_store.toggle_breakpoint(
10171 buffer,
10172 BreakpointWithPosition {
10173 position: breakpoint_position.text_anchor,
10174 bp: breakpoint,
10175 },
10176 edit_action,
10177 cx,
10178 );
10179 });
10180
10181 cx.notify();
10182 }
10183
10184 #[cfg(any(test, feature = "test-support"))]
10185 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10186 self.breakpoint_store.clone()
10187 }
10188
10189 pub fn prepare_restore_change(
10190 &self,
10191 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10192 hunk: &MultiBufferDiffHunk,
10193 cx: &mut App,
10194 ) -> Option<()> {
10195 if hunk.is_created_file() {
10196 return None;
10197 }
10198 let buffer = self.buffer.read(cx);
10199 let diff = buffer.diff_for(hunk.buffer_id)?;
10200 let buffer = buffer.buffer(hunk.buffer_id)?;
10201 let buffer = buffer.read(cx);
10202 let original_text = diff
10203 .read(cx)
10204 .base_text()
10205 .as_rope()
10206 .slice(hunk.diff_base_byte_range.clone());
10207 let buffer_snapshot = buffer.snapshot();
10208 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10209 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10210 probe
10211 .0
10212 .start
10213 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10214 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10215 }) {
10216 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10217 Some(())
10218 } else {
10219 None
10220 }
10221 }
10222
10223 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10224 self.manipulate_lines(window, cx, |lines| lines.reverse())
10225 }
10226
10227 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10228 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10229 }
10230
10231 fn manipulate_lines<Fn>(
10232 &mut self,
10233 window: &mut Window,
10234 cx: &mut Context<Self>,
10235 mut callback: Fn,
10236 ) where
10237 Fn: FnMut(&mut Vec<&str>),
10238 {
10239 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10240
10241 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10242 let buffer = self.buffer.read(cx).snapshot(cx);
10243
10244 let mut edits = Vec::new();
10245
10246 let selections = self.selections.all::<Point>(cx);
10247 let mut selections = selections.iter().peekable();
10248 let mut contiguous_row_selections = Vec::new();
10249 let mut new_selections = Vec::new();
10250 let mut added_lines = 0;
10251 let mut removed_lines = 0;
10252
10253 while let Some(selection) = selections.next() {
10254 let (start_row, end_row) = consume_contiguous_rows(
10255 &mut contiguous_row_selections,
10256 selection,
10257 &display_map,
10258 &mut selections,
10259 );
10260
10261 let start_point = Point::new(start_row.0, 0);
10262 let end_point = Point::new(
10263 end_row.previous_row().0,
10264 buffer.line_len(end_row.previous_row()),
10265 );
10266 let text = buffer
10267 .text_for_range(start_point..end_point)
10268 .collect::<String>();
10269
10270 let mut lines = text.split('\n').collect_vec();
10271
10272 let lines_before = lines.len();
10273 callback(&mut lines);
10274 let lines_after = lines.len();
10275
10276 edits.push((start_point..end_point, lines.join("\n")));
10277
10278 // Selections must change based on added and removed line count
10279 let start_row =
10280 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10281 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10282 new_selections.push(Selection {
10283 id: selection.id,
10284 start: start_row,
10285 end: end_row,
10286 goal: SelectionGoal::None,
10287 reversed: selection.reversed,
10288 });
10289
10290 if lines_after > lines_before {
10291 added_lines += lines_after - lines_before;
10292 } else if lines_before > lines_after {
10293 removed_lines += lines_before - lines_after;
10294 }
10295 }
10296
10297 self.transact(window, cx, |this, window, cx| {
10298 let buffer = this.buffer.update(cx, |buffer, cx| {
10299 buffer.edit(edits, None, cx);
10300 buffer.snapshot(cx)
10301 });
10302
10303 // Recalculate offsets on newly edited buffer
10304 let new_selections = new_selections
10305 .iter()
10306 .map(|s| {
10307 let start_point = Point::new(s.start.0, 0);
10308 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10309 Selection {
10310 id: s.id,
10311 start: buffer.point_to_offset(start_point),
10312 end: buffer.point_to_offset(end_point),
10313 goal: s.goal,
10314 reversed: s.reversed,
10315 }
10316 })
10317 .collect();
10318
10319 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10320 s.select(new_selections);
10321 });
10322
10323 this.request_autoscroll(Autoscroll::fit(), cx);
10324 });
10325 }
10326
10327 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10328 self.manipulate_text(window, cx, |text| {
10329 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10330 if has_upper_case_characters {
10331 text.to_lowercase()
10332 } else {
10333 text.to_uppercase()
10334 }
10335 })
10336 }
10337
10338 pub fn convert_to_upper_case(
10339 &mut self,
10340 _: &ConvertToUpperCase,
10341 window: &mut Window,
10342 cx: &mut Context<Self>,
10343 ) {
10344 self.manipulate_text(window, cx, |text| text.to_uppercase())
10345 }
10346
10347 pub fn convert_to_lower_case(
10348 &mut self,
10349 _: &ConvertToLowerCase,
10350 window: &mut Window,
10351 cx: &mut Context<Self>,
10352 ) {
10353 self.manipulate_text(window, cx, |text| text.to_lowercase())
10354 }
10355
10356 pub fn convert_to_title_case(
10357 &mut self,
10358 _: &ConvertToTitleCase,
10359 window: &mut Window,
10360 cx: &mut Context<Self>,
10361 ) {
10362 self.manipulate_text(window, cx, |text| {
10363 text.split('\n')
10364 .map(|line| line.to_case(Case::Title))
10365 .join("\n")
10366 })
10367 }
10368
10369 pub fn convert_to_snake_case(
10370 &mut self,
10371 _: &ConvertToSnakeCase,
10372 window: &mut Window,
10373 cx: &mut Context<Self>,
10374 ) {
10375 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10376 }
10377
10378 pub fn convert_to_kebab_case(
10379 &mut self,
10380 _: &ConvertToKebabCase,
10381 window: &mut Window,
10382 cx: &mut Context<Self>,
10383 ) {
10384 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10385 }
10386
10387 pub fn convert_to_upper_camel_case(
10388 &mut self,
10389 _: &ConvertToUpperCamelCase,
10390 window: &mut Window,
10391 cx: &mut Context<Self>,
10392 ) {
10393 self.manipulate_text(window, cx, |text| {
10394 text.split('\n')
10395 .map(|line| line.to_case(Case::UpperCamel))
10396 .join("\n")
10397 })
10398 }
10399
10400 pub fn convert_to_lower_camel_case(
10401 &mut self,
10402 _: &ConvertToLowerCamelCase,
10403 window: &mut Window,
10404 cx: &mut Context<Self>,
10405 ) {
10406 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10407 }
10408
10409 pub fn convert_to_opposite_case(
10410 &mut self,
10411 _: &ConvertToOppositeCase,
10412 window: &mut Window,
10413 cx: &mut Context<Self>,
10414 ) {
10415 self.manipulate_text(window, cx, |text| {
10416 text.chars()
10417 .fold(String::with_capacity(text.len()), |mut t, c| {
10418 if c.is_uppercase() {
10419 t.extend(c.to_lowercase());
10420 } else {
10421 t.extend(c.to_uppercase());
10422 }
10423 t
10424 })
10425 })
10426 }
10427
10428 pub fn convert_to_rot13(
10429 &mut self,
10430 _: &ConvertToRot13,
10431 window: &mut Window,
10432 cx: &mut Context<Self>,
10433 ) {
10434 self.manipulate_text(window, cx, |text| {
10435 text.chars()
10436 .map(|c| match c {
10437 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10438 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10439 _ => c,
10440 })
10441 .collect()
10442 })
10443 }
10444
10445 pub fn convert_to_rot47(
10446 &mut self,
10447 _: &ConvertToRot47,
10448 window: &mut Window,
10449 cx: &mut Context<Self>,
10450 ) {
10451 self.manipulate_text(window, cx, |text| {
10452 text.chars()
10453 .map(|c| {
10454 let code_point = c as u32;
10455 if code_point >= 33 && code_point <= 126 {
10456 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10457 }
10458 c
10459 })
10460 .collect()
10461 })
10462 }
10463
10464 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10465 where
10466 Fn: FnMut(&str) -> String,
10467 {
10468 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10469 let buffer = self.buffer.read(cx).snapshot(cx);
10470
10471 let mut new_selections = Vec::new();
10472 let mut edits = Vec::new();
10473 let mut selection_adjustment = 0i32;
10474
10475 for selection in self.selections.all::<usize>(cx) {
10476 let selection_is_empty = selection.is_empty();
10477
10478 let (start, end) = if selection_is_empty {
10479 let word_range = movement::surrounding_word(
10480 &display_map,
10481 selection.start.to_display_point(&display_map),
10482 );
10483 let start = word_range.start.to_offset(&display_map, Bias::Left);
10484 let end = word_range.end.to_offset(&display_map, Bias::Left);
10485 (start, end)
10486 } else {
10487 (selection.start, selection.end)
10488 };
10489
10490 let text = buffer.text_for_range(start..end).collect::<String>();
10491 let old_length = text.len() as i32;
10492 let text = callback(&text);
10493
10494 new_selections.push(Selection {
10495 start: (start as i32 - selection_adjustment) as usize,
10496 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10497 goal: SelectionGoal::None,
10498 ..selection
10499 });
10500
10501 selection_adjustment += old_length - text.len() as i32;
10502
10503 edits.push((start..end, text));
10504 }
10505
10506 self.transact(window, cx, |this, window, cx| {
10507 this.buffer.update(cx, |buffer, cx| {
10508 buffer.edit(edits, None, cx);
10509 });
10510
10511 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10512 s.select(new_selections);
10513 });
10514
10515 this.request_autoscroll(Autoscroll::fit(), cx);
10516 });
10517 }
10518
10519 pub fn duplicate(
10520 &mut self,
10521 upwards: bool,
10522 whole_lines: bool,
10523 window: &mut Window,
10524 cx: &mut Context<Self>,
10525 ) {
10526 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10527
10528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10529 let buffer = &display_map.buffer_snapshot;
10530 let selections = self.selections.all::<Point>(cx);
10531
10532 let mut edits = Vec::new();
10533 let mut selections_iter = selections.iter().peekable();
10534 while let Some(selection) = selections_iter.next() {
10535 let mut rows = selection.spanned_rows(false, &display_map);
10536 // duplicate line-wise
10537 if whole_lines || selection.start == selection.end {
10538 // Avoid duplicating the same lines twice.
10539 while let Some(next_selection) = selections_iter.peek() {
10540 let next_rows = next_selection.spanned_rows(false, &display_map);
10541 if next_rows.start < rows.end {
10542 rows.end = next_rows.end;
10543 selections_iter.next().unwrap();
10544 } else {
10545 break;
10546 }
10547 }
10548
10549 // Copy the text from the selected row region and splice it either at the start
10550 // or end of the region.
10551 let start = Point::new(rows.start.0, 0);
10552 let end = Point::new(
10553 rows.end.previous_row().0,
10554 buffer.line_len(rows.end.previous_row()),
10555 );
10556 let text = buffer
10557 .text_for_range(start..end)
10558 .chain(Some("\n"))
10559 .collect::<String>();
10560 let insert_location = if upwards {
10561 Point::new(rows.end.0, 0)
10562 } else {
10563 start
10564 };
10565 edits.push((insert_location..insert_location, text));
10566 } else {
10567 // duplicate character-wise
10568 let start = selection.start;
10569 let end = selection.end;
10570 let text = buffer.text_for_range(start..end).collect::<String>();
10571 edits.push((selection.end..selection.end, text));
10572 }
10573 }
10574
10575 self.transact(window, cx, |this, _, cx| {
10576 this.buffer.update(cx, |buffer, cx| {
10577 buffer.edit(edits, None, cx);
10578 });
10579
10580 this.request_autoscroll(Autoscroll::fit(), cx);
10581 });
10582 }
10583
10584 pub fn duplicate_line_up(
10585 &mut self,
10586 _: &DuplicateLineUp,
10587 window: &mut Window,
10588 cx: &mut Context<Self>,
10589 ) {
10590 self.duplicate(true, true, window, cx);
10591 }
10592
10593 pub fn duplicate_line_down(
10594 &mut self,
10595 _: &DuplicateLineDown,
10596 window: &mut Window,
10597 cx: &mut Context<Self>,
10598 ) {
10599 self.duplicate(false, true, window, cx);
10600 }
10601
10602 pub fn duplicate_selection(
10603 &mut self,
10604 _: &DuplicateSelection,
10605 window: &mut Window,
10606 cx: &mut Context<Self>,
10607 ) {
10608 self.duplicate(false, false, window, cx);
10609 }
10610
10611 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10612 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10613
10614 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10615 let buffer = self.buffer.read(cx).snapshot(cx);
10616
10617 let mut edits = Vec::new();
10618 let mut unfold_ranges = Vec::new();
10619 let mut refold_creases = Vec::new();
10620
10621 let selections = self.selections.all::<Point>(cx);
10622 let mut selections = selections.iter().peekable();
10623 let mut contiguous_row_selections = Vec::new();
10624 let mut new_selections = Vec::new();
10625
10626 while let Some(selection) = selections.next() {
10627 // Find all the selections that span a contiguous row range
10628 let (start_row, end_row) = consume_contiguous_rows(
10629 &mut contiguous_row_selections,
10630 selection,
10631 &display_map,
10632 &mut selections,
10633 );
10634
10635 // Move the text spanned by the row range to be before the line preceding the row range
10636 if start_row.0 > 0 {
10637 let range_to_move = Point::new(
10638 start_row.previous_row().0,
10639 buffer.line_len(start_row.previous_row()),
10640 )
10641 ..Point::new(
10642 end_row.previous_row().0,
10643 buffer.line_len(end_row.previous_row()),
10644 );
10645 let insertion_point = display_map
10646 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10647 .0;
10648
10649 // Don't move lines across excerpts
10650 if buffer
10651 .excerpt_containing(insertion_point..range_to_move.end)
10652 .is_some()
10653 {
10654 let text = buffer
10655 .text_for_range(range_to_move.clone())
10656 .flat_map(|s| s.chars())
10657 .skip(1)
10658 .chain(['\n'])
10659 .collect::<String>();
10660
10661 edits.push((
10662 buffer.anchor_after(range_to_move.start)
10663 ..buffer.anchor_before(range_to_move.end),
10664 String::new(),
10665 ));
10666 let insertion_anchor = buffer.anchor_after(insertion_point);
10667 edits.push((insertion_anchor..insertion_anchor, text));
10668
10669 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10670
10671 // Move selections up
10672 new_selections.extend(contiguous_row_selections.drain(..).map(
10673 |mut selection| {
10674 selection.start.row -= row_delta;
10675 selection.end.row -= row_delta;
10676 selection
10677 },
10678 ));
10679
10680 // Move folds up
10681 unfold_ranges.push(range_to_move.clone());
10682 for fold in display_map.folds_in_range(
10683 buffer.anchor_before(range_to_move.start)
10684 ..buffer.anchor_after(range_to_move.end),
10685 ) {
10686 let mut start = fold.range.start.to_point(&buffer);
10687 let mut end = fold.range.end.to_point(&buffer);
10688 start.row -= row_delta;
10689 end.row -= row_delta;
10690 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10691 }
10692 }
10693 }
10694
10695 // If we didn't move line(s), preserve the existing selections
10696 new_selections.append(&mut contiguous_row_selections);
10697 }
10698
10699 self.transact(window, cx, |this, window, cx| {
10700 this.unfold_ranges(&unfold_ranges, true, true, cx);
10701 this.buffer.update(cx, |buffer, cx| {
10702 for (range, text) in edits {
10703 buffer.edit([(range, text)], None, cx);
10704 }
10705 });
10706 this.fold_creases(refold_creases, true, window, cx);
10707 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10708 s.select(new_selections);
10709 })
10710 });
10711 }
10712
10713 pub fn move_line_down(
10714 &mut self,
10715 _: &MoveLineDown,
10716 window: &mut Window,
10717 cx: &mut Context<Self>,
10718 ) {
10719 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10720
10721 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10722 let buffer = self.buffer.read(cx).snapshot(cx);
10723
10724 let mut edits = Vec::new();
10725 let mut unfold_ranges = Vec::new();
10726 let mut refold_creases = Vec::new();
10727
10728 let selections = self.selections.all::<Point>(cx);
10729 let mut selections = selections.iter().peekable();
10730 let mut contiguous_row_selections = Vec::new();
10731 let mut new_selections = Vec::new();
10732
10733 while let Some(selection) = selections.next() {
10734 // Find all the selections that span a contiguous row range
10735 let (start_row, end_row) = consume_contiguous_rows(
10736 &mut contiguous_row_selections,
10737 selection,
10738 &display_map,
10739 &mut selections,
10740 );
10741
10742 // Move the text spanned by the row range to be after the last line of the row range
10743 if end_row.0 <= buffer.max_point().row {
10744 let range_to_move =
10745 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10746 let insertion_point = display_map
10747 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10748 .0;
10749
10750 // Don't move lines across excerpt boundaries
10751 if buffer
10752 .excerpt_containing(range_to_move.start..insertion_point)
10753 .is_some()
10754 {
10755 let mut text = String::from("\n");
10756 text.extend(buffer.text_for_range(range_to_move.clone()));
10757 text.pop(); // Drop trailing newline
10758 edits.push((
10759 buffer.anchor_after(range_to_move.start)
10760 ..buffer.anchor_before(range_to_move.end),
10761 String::new(),
10762 ));
10763 let insertion_anchor = buffer.anchor_after(insertion_point);
10764 edits.push((insertion_anchor..insertion_anchor, text));
10765
10766 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10767
10768 // Move selections down
10769 new_selections.extend(contiguous_row_selections.drain(..).map(
10770 |mut selection| {
10771 selection.start.row += row_delta;
10772 selection.end.row += row_delta;
10773 selection
10774 },
10775 ));
10776
10777 // Move folds down
10778 unfold_ranges.push(range_to_move.clone());
10779 for fold in display_map.folds_in_range(
10780 buffer.anchor_before(range_to_move.start)
10781 ..buffer.anchor_after(range_to_move.end),
10782 ) {
10783 let mut start = fold.range.start.to_point(&buffer);
10784 let mut end = fold.range.end.to_point(&buffer);
10785 start.row += row_delta;
10786 end.row += row_delta;
10787 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10788 }
10789 }
10790 }
10791
10792 // If we didn't move line(s), preserve the existing selections
10793 new_selections.append(&mut contiguous_row_selections);
10794 }
10795
10796 self.transact(window, cx, |this, window, cx| {
10797 this.unfold_ranges(&unfold_ranges, true, true, cx);
10798 this.buffer.update(cx, |buffer, cx| {
10799 for (range, text) in edits {
10800 buffer.edit([(range, text)], None, cx);
10801 }
10802 });
10803 this.fold_creases(refold_creases, true, window, cx);
10804 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10805 s.select(new_selections)
10806 });
10807 });
10808 }
10809
10810 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10811 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10812 let text_layout_details = &self.text_layout_details(window);
10813 self.transact(window, cx, |this, window, cx| {
10814 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10815 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10816 s.move_with(|display_map, selection| {
10817 if !selection.is_empty() {
10818 return;
10819 }
10820
10821 let mut head = selection.head();
10822 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10823 if head.column() == display_map.line_len(head.row()) {
10824 transpose_offset = display_map
10825 .buffer_snapshot
10826 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10827 }
10828
10829 if transpose_offset == 0 {
10830 return;
10831 }
10832
10833 *head.column_mut() += 1;
10834 head = display_map.clip_point(head, Bias::Right);
10835 let goal = SelectionGoal::HorizontalPosition(
10836 display_map
10837 .x_for_display_point(head, text_layout_details)
10838 .into(),
10839 );
10840 selection.collapse_to(head, goal);
10841
10842 let transpose_start = display_map
10843 .buffer_snapshot
10844 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10845 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10846 let transpose_end = display_map
10847 .buffer_snapshot
10848 .clip_offset(transpose_offset + 1, Bias::Right);
10849 if let Some(ch) =
10850 display_map.buffer_snapshot.chars_at(transpose_start).next()
10851 {
10852 edits.push((transpose_start..transpose_offset, String::new()));
10853 edits.push((transpose_end..transpose_end, ch.to_string()));
10854 }
10855 }
10856 });
10857 edits
10858 });
10859 this.buffer
10860 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10861 let selections = this.selections.all::<usize>(cx);
10862 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10863 s.select(selections);
10864 });
10865 });
10866 }
10867
10868 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10869 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10870 self.rewrap_impl(RewrapOptions::default(), cx)
10871 }
10872
10873 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10874 let buffer = self.buffer.read(cx).snapshot(cx);
10875 let selections = self.selections.all::<Point>(cx);
10876
10877 // Shrink and split selections to respect paragraph boundaries.
10878 let ranges = selections.into_iter().flat_map(|selection| {
10879 let language_settings = buffer.language_settings_at(selection.head(), cx);
10880 let language_scope = buffer.language_scope_at(selection.head());
10881
10882 let Some(start_row) = (selection.start.row..=selection.end.row)
10883 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
10884 else {
10885 return vec![];
10886 };
10887 let Some(end_row) = (selection.start.row..=selection.end.row)
10888 .rev()
10889 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
10890 else {
10891 return vec![];
10892 };
10893
10894 let mut row = start_row;
10895 let mut ranges = Vec::new();
10896 while let Some(blank_row) =
10897 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
10898 {
10899 let next_paragraph_start = (blank_row + 1..=end_row)
10900 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
10901 .unwrap();
10902 ranges.push((
10903 language_settings.clone(),
10904 language_scope.clone(),
10905 Point::new(row, 0)..Point::new(blank_row - 1, 0),
10906 ));
10907 row = next_paragraph_start;
10908 }
10909 ranges.push((
10910 language_settings.clone(),
10911 language_scope.clone(),
10912 Point::new(row, 0)..Point::new(end_row, 0),
10913 ));
10914
10915 ranges
10916 });
10917
10918 let mut edits = Vec::new();
10919 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10920
10921 for (language_settings, language_scope, range) in ranges {
10922 let mut start_row = range.start.row;
10923 let mut end_row = range.end.row;
10924
10925 // Skip selections that overlap with a range that has already been rewrapped.
10926 let selection_range = start_row..end_row;
10927 if rewrapped_row_ranges
10928 .iter()
10929 .any(|range| range.overlaps(&selection_range))
10930 {
10931 continue;
10932 }
10933
10934 let tab_size = language_settings.tab_size;
10935
10936 // Since not all lines in the selection may be at the same indent
10937 // level, choose the indent size that is the most common between all
10938 // of the lines.
10939 //
10940 // If there is a tie, we use the deepest indent.
10941 let (indent_size, indent_end) = {
10942 let mut indent_size_occurrences = HashMap::default();
10943 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10944
10945 for row in start_row..=end_row {
10946 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10947 rows_by_indent_size.entry(indent).or_default().push(row);
10948 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10949 }
10950
10951 let indent_size = indent_size_occurrences
10952 .into_iter()
10953 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10954 .map(|(indent, _)| indent)
10955 .unwrap_or_default();
10956 let row = rows_by_indent_size[&indent_size][0];
10957 let indent_end = Point::new(row, indent_size.len);
10958
10959 (indent_size, indent_end)
10960 };
10961
10962 let mut line_prefix = indent_size.chars().collect::<String>();
10963
10964 let mut inside_comment = false;
10965 if let Some(comment_prefix) = language_scope.and_then(|language| {
10966 language
10967 .line_comment_prefixes()
10968 .iter()
10969 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10970 .cloned()
10971 }) {
10972 line_prefix.push_str(&comment_prefix);
10973 inside_comment = true;
10974 }
10975
10976 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10977 RewrapBehavior::InComments => inside_comment,
10978 RewrapBehavior::InSelections => !range.is_empty(),
10979 RewrapBehavior::Anywhere => true,
10980 };
10981
10982 let should_rewrap = options.override_language_settings
10983 || allow_rewrap_based_on_language
10984 || self.hard_wrap.is_some();
10985 if !should_rewrap {
10986 continue;
10987 }
10988
10989 if range.is_empty() {
10990 'expand_upwards: while start_row > 0 {
10991 let prev_row = start_row - 1;
10992 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10993 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10994 && !buffer.is_line_blank(MultiBufferRow(prev_row))
10995 {
10996 start_row = prev_row;
10997 } else {
10998 break 'expand_upwards;
10999 }
11000 }
11001
11002 'expand_downwards: while end_row < buffer.max_point().row {
11003 let next_row = end_row + 1;
11004 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11005 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11006 && !buffer.is_line_blank(MultiBufferRow(next_row))
11007 {
11008 end_row = next_row;
11009 } else {
11010 break 'expand_downwards;
11011 }
11012 }
11013 }
11014
11015 let start = Point::new(start_row, 0);
11016 let start_offset = start.to_offset(&buffer);
11017 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11018 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11019 let Some(lines_without_prefixes) = selection_text
11020 .lines()
11021 .map(|line| {
11022 line.strip_prefix(&line_prefix)
11023 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11024 .with_context(|| {
11025 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11026 })
11027 })
11028 .collect::<Result<Vec<_>, _>>()
11029 .log_err()
11030 else {
11031 continue;
11032 };
11033
11034 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11035 buffer
11036 .language_settings_at(Point::new(start_row, 0), cx)
11037 .preferred_line_length as usize
11038 });
11039 let wrapped_text = wrap_with_prefix(
11040 line_prefix,
11041 lines_without_prefixes.join("\n"),
11042 wrap_column,
11043 tab_size,
11044 options.preserve_existing_whitespace,
11045 );
11046
11047 // TODO: should always use char-based diff while still supporting cursor behavior that
11048 // matches vim.
11049 let mut diff_options = DiffOptions::default();
11050 if options.override_language_settings {
11051 diff_options.max_word_diff_len = 0;
11052 diff_options.max_word_diff_line_count = 0;
11053 } else {
11054 diff_options.max_word_diff_len = usize::MAX;
11055 diff_options.max_word_diff_line_count = usize::MAX;
11056 }
11057
11058 for (old_range, new_text) in
11059 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11060 {
11061 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11062 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11063 edits.push((edit_start..edit_end, new_text));
11064 }
11065
11066 rewrapped_row_ranges.push(start_row..=end_row);
11067 }
11068
11069 self.buffer
11070 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11071 }
11072
11073 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11074 let mut text = String::new();
11075 let buffer = self.buffer.read(cx).snapshot(cx);
11076 let mut selections = self.selections.all::<Point>(cx);
11077 let mut clipboard_selections = Vec::with_capacity(selections.len());
11078 {
11079 let max_point = buffer.max_point();
11080 let mut is_first = true;
11081 for selection in &mut selections {
11082 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11083 if is_entire_line {
11084 selection.start = Point::new(selection.start.row, 0);
11085 if !selection.is_empty() && selection.end.column == 0 {
11086 selection.end = cmp::min(max_point, selection.end);
11087 } else {
11088 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11089 }
11090 selection.goal = SelectionGoal::None;
11091 }
11092 if is_first {
11093 is_first = false;
11094 } else {
11095 text += "\n";
11096 }
11097 let mut len = 0;
11098 for chunk in buffer.text_for_range(selection.start..selection.end) {
11099 text.push_str(chunk);
11100 len += chunk.len();
11101 }
11102 clipboard_selections.push(ClipboardSelection {
11103 len,
11104 is_entire_line,
11105 first_line_indent: buffer
11106 .indent_size_for_line(MultiBufferRow(selection.start.row))
11107 .len,
11108 });
11109 }
11110 }
11111
11112 self.transact(window, cx, |this, window, cx| {
11113 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11114 s.select(selections);
11115 });
11116 this.insert("", window, cx);
11117 });
11118 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11119 }
11120
11121 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11122 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11123 let item = self.cut_common(window, cx);
11124 cx.write_to_clipboard(item);
11125 }
11126
11127 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11128 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11129 self.change_selections(None, window, cx, |s| {
11130 s.move_with(|snapshot, sel| {
11131 if sel.is_empty() {
11132 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11133 }
11134 });
11135 });
11136 let item = self.cut_common(window, cx);
11137 cx.set_global(KillRing(item))
11138 }
11139
11140 pub fn kill_ring_yank(
11141 &mut self,
11142 _: &KillRingYank,
11143 window: &mut Window,
11144 cx: &mut Context<Self>,
11145 ) {
11146 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11147 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11148 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11149 (kill_ring.text().to_string(), kill_ring.metadata_json())
11150 } else {
11151 return;
11152 }
11153 } else {
11154 return;
11155 };
11156 self.do_paste(&text, metadata, false, window, cx);
11157 }
11158
11159 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11160 self.do_copy(true, cx);
11161 }
11162
11163 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11164 self.do_copy(false, cx);
11165 }
11166
11167 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11168 let selections = self.selections.all::<Point>(cx);
11169 let buffer = self.buffer.read(cx).read(cx);
11170 let mut text = String::new();
11171
11172 let mut clipboard_selections = Vec::with_capacity(selections.len());
11173 {
11174 let max_point = buffer.max_point();
11175 let mut is_first = true;
11176 for selection in &selections {
11177 let mut start = selection.start;
11178 let mut end = selection.end;
11179 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11180 if is_entire_line {
11181 start = Point::new(start.row, 0);
11182 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11183 }
11184
11185 let mut trimmed_selections = Vec::new();
11186 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11187 let row = MultiBufferRow(start.row);
11188 let first_indent = buffer.indent_size_for_line(row);
11189 if first_indent.len == 0 || start.column > first_indent.len {
11190 trimmed_selections.push(start..end);
11191 } else {
11192 trimmed_selections.push(
11193 Point::new(row.0, first_indent.len)
11194 ..Point::new(row.0, buffer.line_len(row)),
11195 );
11196 for row in start.row + 1..=end.row {
11197 let mut line_len = buffer.line_len(MultiBufferRow(row));
11198 if row == end.row {
11199 line_len = end.column;
11200 }
11201 if line_len == 0 {
11202 trimmed_selections
11203 .push(Point::new(row, 0)..Point::new(row, line_len));
11204 continue;
11205 }
11206 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11207 if row_indent_size.len >= first_indent.len {
11208 trimmed_selections.push(
11209 Point::new(row, first_indent.len)..Point::new(row, line_len),
11210 );
11211 } else {
11212 trimmed_selections.clear();
11213 trimmed_selections.push(start..end);
11214 break;
11215 }
11216 }
11217 }
11218 } else {
11219 trimmed_selections.push(start..end);
11220 }
11221
11222 for trimmed_range in trimmed_selections {
11223 if is_first {
11224 is_first = false;
11225 } else {
11226 text += "\n";
11227 }
11228 let mut len = 0;
11229 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11230 text.push_str(chunk);
11231 len += chunk.len();
11232 }
11233 clipboard_selections.push(ClipboardSelection {
11234 len,
11235 is_entire_line,
11236 first_line_indent: buffer
11237 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11238 .len,
11239 });
11240 }
11241 }
11242 }
11243
11244 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11245 text,
11246 clipboard_selections,
11247 ));
11248 }
11249
11250 pub fn do_paste(
11251 &mut self,
11252 text: &String,
11253 clipboard_selections: Option<Vec<ClipboardSelection>>,
11254 handle_entire_lines: bool,
11255 window: &mut Window,
11256 cx: &mut Context<Self>,
11257 ) {
11258 if self.read_only(cx) {
11259 return;
11260 }
11261
11262 let clipboard_text = Cow::Borrowed(text);
11263
11264 self.transact(window, cx, |this, window, cx| {
11265 if let Some(mut clipboard_selections) = clipboard_selections {
11266 let old_selections = this.selections.all::<usize>(cx);
11267 let all_selections_were_entire_line =
11268 clipboard_selections.iter().all(|s| s.is_entire_line);
11269 let first_selection_indent_column =
11270 clipboard_selections.first().map(|s| s.first_line_indent);
11271 if clipboard_selections.len() != old_selections.len() {
11272 clipboard_selections.drain(..);
11273 }
11274 let cursor_offset = this.selections.last::<usize>(cx).head();
11275 let mut auto_indent_on_paste = true;
11276
11277 this.buffer.update(cx, |buffer, cx| {
11278 let snapshot = buffer.read(cx);
11279 auto_indent_on_paste = snapshot
11280 .language_settings_at(cursor_offset, cx)
11281 .auto_indent_on_paste;
11282
11283 let mut start_offset = 0;
11284 let mut edits = Vec::new();
11285 let mut original_indent_columns = Vec::new();
11286 for (ix, selection) in old_selections.iter().enumerate() {
11287 let to_insert;
11288 let entire_line;
11289 let original_indent_column;
11290 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11291 let end_offset = start_offset + clipboard_selection.len;
11292 to_insert = &clipboard_text[start_offset..end_offset];
11293 entire_line = clipboard_selection.is_entire_line;
11294 start_offset = end_offset + 1;
11295 original_indent_column = Some(clipboard_selection.first_line_indent);
11296 } else {
11297 to_insert = clipboard_text.as_str();
11298 entire_line = all_selections_were_entire_line;
11299 original_indent_column = first_selection_indent_column
11300 }
11301
11302 // If the corresponding selection was empty when this slice of the
11303 // clipboard text was written, then the entire line containing the
11304 // selection was copied. If this selection is also currently empty,
11305 // then paste the line before the current line of the buffer.
11306 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11307 let column = selection.start.to_point(&snapshot).column as usize;
11308 let line_start = selection.start - column;
11309 line_start..line_start
11310 } else {
11311 selection.range()
11312 };
11313
11314 edits.push((range, to_insert));
11315 original_indent_columns.push(original_indent_column);
11316 }
11317 drop(snapshot);
11318
11319 buffer.edit(
11320 edits,
11321 if auto_indent_on_paste {
11322 Some(AutoindentMode::Block {
11323 original_indent_columns,
11324 })
11325 } else {
11326 None
11327 },
11328 cx,
11329 );
11330 });
11331
11332 let selections = this.selections.all::<usize>(cx);
11333 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11334 s.select(selections)
11335 });
11336 } else {
11337 this.insert(&clipboard_text, window, cx);
11338 }
11339 });
11340 }
11341
11342 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11343 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11344 if let Some(item) = cx.read_from_clipboard() {
11345 let entries = item.entries();
11346
11347 match entries.first() {
11348 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11349 // of all the pasted entries.
11350 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11351 .do_paste(
11352 clipboard_string.text(),
11353 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11354 true,
11355 window,
11356 cx,
11357 ),
11358 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11359 }
11360 }
11361 }
11362
11363 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11364 if self.read_only(cx) {
11365 return;
11366 }
11367
11368 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11369
11370 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11371 if let Some((selections, _)) =
11372 self.selection_history.transaction(transaction_id).cloned()
11373 {
11374 self.change_selections(None, window, cx, |s| {
11375 s.select_anchors(selections.to_vec());
11376 });
11377 } else {
11378 log::error!(
11379 "No entry in selection_history found for undo. \
11380 This may correspond to a bug where undo does not update the selection. \
11381 If this is occurring, please add details to \
11382 https://github.com/zed-industries/zed/issues/22692"
11383 );
11384 }
11385 self.request_autoscroll(Autoscroll::fit(), cx);
11386 self.unmark_text(window, cx);
11387 self.refresh_inline_completion(true, false, window, cx);
11388 cx.emit(EditorEvent::Edited { transaction_id });
11389 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11390 }
11391 }
11392
11393 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11394 if self.read_only(cx) {
11395 return;
11396 }
11397
11398 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11399
11400 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11401 if let Some((_, Some(selections))) =
11402 self.selection_history.transaction(transaction_id).cloned()
11403 {
11404 self.change_selections(None, window, cx, |s| {
11405 s.select_anchors(selections.to_vec());
11406 });
11407 } else {
11408 log::error!(
11409 "No entry in selection_history found for redo. \
11410 This may correspond to a bug where undo does not update the selection. \
11411 If this is occurring, please add details to \
11412 https://github.com/zed-industries/zed/issues/22692"
11413 );
11414 }
11415 self.request_autoscroll(Autoscroll::fit(), cx);
11416 self.unmark_text(window, cx);
11417 self.refresh_inline_completion(true, false, window, cx);
11418 cx.emit(EditorEvent::Edited { transaction_id });
11419 }
11420 }
11421
11422 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11423 self.buffer
11424 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11425 }
11426
11427 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11428 self.buffer
11429 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11430 }
11431
11432 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11433 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11434 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11435 s.move_with(|map, selection| {
11436 let cursor = if selection.is_empty() {
11437 movement::left(map, selection.start)
11438 } else {
11439 selection.start
11440 };
11441 selection.collapse_to(cursor, SelectionGoal::None);
11442 });
11443 })
11444 }
11445
11446 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11447 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11448 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11449 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11450 })
11451 }
11452
11453 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11454 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11455 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11456 s.move_with(|map, selection| {
11457 let cursor = if selection.is_empty() {
11458 movement::right(map, selection.end)
11459 } else {
11460 selection.end
11461 };
11462 selection.collapse_to(cursor, SelectionGoal::None)
11463 });
11464 })
11465 }
11466
11467 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11468 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11469 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11470 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11471 })
11472 }
11473
11474 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11475 if self.take_rename(true, window, cx).is_some() {
11476 return;
11477 }
11478
11479 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11480 cx.propagate();
11481 return;
11482 }
11483
11484 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11485
11486 let text_layout_details = &self.text_layout_details(window);
11487 let selection_count = self.selections.count();
11488 let first_selection = self.selections.first_anchor();
11489
11490 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11491 s.move_with(|map, selection| {
11492 if !selection.is_empty() {
11493 selection.goal = SelectionGoal::None;
11494 }
11495 let (cursor, goal) = movement::up(
11496 map,
11497 selection.start,
11498 selection.goal,
11499 false,
11500 text_layout_details,
11501 );
11502 selection.collapse_to(cursor, goal);
11503 });
11504 });
11505
11506 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11507 {
11508 cx.propagate();
11509 }
11510 }
11511
11512 pub fn move_up_by_lines(
11513 &mut self,
11514 action: &MoveUpByLines,
11515 window: &mut Window,
11516 cx: &mut Context<Self>,
11517 ) {
11518 if self.take_rename(true, window, cx).is_some() {
11519 return;
11520 }
11521
11522 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11523 cx.propagate();
11524 return;
11525 }
11526
11527 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11528
11529 let text_layout_details = &self.text_layout_details(window);
11530
11531 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11532 s.move_with(|map, selection| {
11533 if !selection.is_empty() {
11534 selection.goal = SelectionGoal::None;
11535 }
11536 let (cursor, goal) = movement::up_by_rows(
11537 map,
11538 selection.start,
11539 action.lines,
11540 selection.goal,
11541 false,
11542 text_layout_details,
11543 );
11544 selection.collapse_to(cursor, goal);
11545 });
11546 })
11547 }
11548
11549 pub fn move_down_by_lines(
11550 &mut self,
11551 action: &MoveDownByLines,
11552 window: &mut Window,
11553 cx: &mut Context<Self>,
11554 ) {
11555 if self.take_rename(true, window, cx).is_some() {
11556 return;
11557 }
11558
11559 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11560 cx.propagate();
11561 return;
11562 }
11563
11564 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11565
11566 let text_layout_details = &self.text_layout_details(window);
11567
11568 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11569 s.move_with(|map, selection| {
11570 if !selection.is_empty() {
11571 selection.goal = SelectionGoal::None;
11572 }
11573 let (cursor, goal) = movement::down_by_rows(
11574 map,
11575 selection.start,
11576 action.lines,
11577 selection.goal,
11578 false,
11579 text_layout_details,
11580 );
11581 selection.collapse_to(cursor, goal);
11582 });
11583 })
11584 }
11585
11586 pub fn select_down_by_lines(
11587 &mut self,
11588 action: &SelectDownByLines,
11589 window: &mut Window,
11590 cx: &mut Context<Self>,
11591 ) {
11592 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11593 let text_layout_details = &self.text_layout_details(window);
11594 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11595 s.move_heads_with(|map, head, goal| {
11596 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11597 })
11598 })
11599 }
11600
11601 pub fn select_up_by_lines(
11602 &mut self,
11603 action: &SelectUpByLines,
11604 window: &mut Window,
11605 cx: &mut Context<Self>,
11606 ) {
11607 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11608 let text_layout_details = &self.text_layout_details(window);
11609 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11610 s.move_heads_with(|map, head, goal| {
11611 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11612 })
11613 })
11614 }
11615
11616 pub fn select_page_up(
11617 &mut self,
11618 _: &SelectPageUp,
11619 window: &mut Window,
11620 cx: &mut Context<Self>,
11621 ) {
11622 let Some(row_count) = self.visible_row_count() else {
11623 return;
11624 };
11625
11626 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11627
11628 let text_layout_details = &self.text_layout_details(window);
11629
11630 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11631 s.move_heads_with(|map, head, goal| {
11632 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11633 })
11634 })
11635 }
11636
11637 pub fn move_page_up(
11638 &mut self,
11639 action: &MovePageUp,
11640 window: &mut Window,
11641 cx: &mut Context<Self>,
11642 ) {
11643 if self.take_rename(true, window, cx).is_some() {
11644 return;
11645 }
11646
11647 if self
11648 .context_menu
11649 .borrow_mut()
11650 .as_mut()
11651 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11652 .unwrap_or(false)
11653 {
11654 return;
11655 }
11656
11657 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11658 cx.propagate();
11659 return;
11660 }
11661
11662 let Some(row_count) = self.visible_row_count() else {
11663 return;
11664 };
11665
11666 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11667
11668 let autoscroll = if action.center_cursor {
11669 Autoscroll::center()
11670 } else {
11671 Autoscroll::fit()
11672 };
11673
11674 let text_layout_details = &self.text_layout_details(window);
11675
11676 self.change_selections(Some(autoscroll), window, cx, |s| {
11677 s.move_with(|map, selection| {
11678 if !selection.is_empty() {
11679 selection.goal = SelectionGoal::None;
11680 }
11681 let (cursor, goal) = movement::up_by_rows(
11682 map,
11683 selection.end,
11684 row_count,
11685 selection.goal,
11686 false,
11687 text_layout_details,
11688 );
11689 selection.collapse_to(cursor, goal);
11690 });
11691 });
11692 }
11693
11694 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11695 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11696 let text_layout_details = &self.text_layout_details(window);
11697 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11698 s.move_heads_with(|map, head, goal| {
11699 movement::up(map, head, goal, false, text_layout_details)
11700 })
11701 })
11702 }
11703
11704 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11705 self.take_rename(true, window, cx);
11706
11707 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11708 cx.propagate();
11709 return;
11710 }
11711
11712 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11713
11714 let text_layout_details = &self.text_layout_details(window);
11715 let selection_count = self.selections.count();
11716 let first_selection = self.selections.first_anchor();
11717
11718 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11719 s.move_with(|map, selection| {
11720 if !selection.is_empty() {
11721 selection.goal = SelectionGoal::None;
11722 }
11723 let (cursor, goal) = movement::down(
11724 map,
11725 selection.end,
11726 selection.goal,
11727 false,
11728 text_layout_details,
11729 );
11730 selection.collapse_to(cursor, goal);
11731 });
11732 });
11733
11734 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11735 {
11736 cx.propagate();
11737 }
11738 }
11739
11740 pub fn select_page_down(
11741 &mut self,
11742 _: &SelectPageDown,
11743 window: &mut Window,
11744 cx: &mut Context<Self>,
11745 ) {
11746 let Some(row_count) = self.visible_row_count() else {
11747 return;
11748 };
11749
11750 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11751
11752 let text_layout_details = &self.text_layout_details(window);
11753
11754 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11755 s.move_heads_with(|map, head, goal| {
11756 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11757 })
11758 })
11759 }
11760
11761 pub fn move_page_down(
11762 &mut self,
11763 action: &MovePageDown,
11764 window: &mut Window,
11765 cx: &mut Context<Self>,
11766 ) {
11767 if self.take_rename(true, window, cx).is_some() {
11768 return;
11769 }
11770
11771 if self
11772 .context_menu
11773 .borrow_mut()
11774 .as_mut()
11775 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11776 .unwrap_or(false)
11777 {
11778 return;
11779 }
11780
11781 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11782 cx.propagate();
11783 return;
11784 }
11785
11786 let Some(row_count) = self.visible_row_count() else {
11787 return;
11788 };
11789
11790 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11791
11792 let autoscroll = if action.center_cursor {
11793 Autoscroll::center()
11794 } else {
11795 Autoscroll::fit()
11796 };
11797
11798 let text_layout_details = &self.text_layout_details(window);
11799 self.change_selections(Some(autoscroll), window, cx, |s| {
11800 s.move_with(|map, selection| {
11801 if !selection.is_empty() {
11802 selection.goal = SelectionGoal::None;
11803 }
11804 let (cursor, goal) = movement::down_by_rows(
11805 map,
11806 selection.end,
11807 row_count,
11808 selection.goal,
11809 false,
11810 text_layout_details,
11811 );
11812 selection.collapse_to(cursor, goal);
11813 });
11814 });
11815 }
11816
11817 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11818 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11819 let text_layout_details = &self.text_layout_details(window);
11820 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11821 s.move_heads_with(|map, head, goal| {
11822 movement::down(map, head, goal, false, text_layout_details)
11823 })
11824 });
11825 }
11826
11827 pub fn context_menu_first(
11828 &mut self,
11829 _: &ContextMenuFirst,
11830 window: &mut Window,
11831 cx: &mut Context<Self>,
11832 ) {
11833 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11834 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11835 }
11836 }
11837
11838 pub fn context_menu_prev(
11839 &mut self,
11840 _: &ContextMenuPrevious,
11841 window: &mut Window,
11842 cx: &mut Context<Self>,
11843 ) {
11844 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11845 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11846 }
11847 }
11848
11849 pub fn context_menu_next(
11850 &mut self,
11851 _: &ContextMenuNext,
11852 window: &mut Window,
11853 cx: &mut Context<Self>,
11854 ) {
11855 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11856 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11857 }
11858 }
11859
11860 pub fn context_menu_last(
11861 &mut self,
11862 _: &ContextMenuLast,
11863 window: &mut Window,
11864 cx: &mut Context<Self>,
11865 ) {
11866 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11867 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11868 }
11869 }
11870
11871 pub fn move_to_previous_word_start(
11872 &mut self,
11873 _: &MoveToPreviousWordStart,
11874 window: &mut Window,
11875 cx: &mut Context<Self>,
11876 ) {
11877 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11878 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11879 s.move_cursors_with(|map, head, _| {
11880 (
11881 movement::previous_word_start(map, head),
11882 SelectionGoal::None,
11883 )
11884 });
11885 })
11886 }
11887
11888 pub fn move_to_previous_subword_start(
11889 &mut self,
11890 _: &MoveToPreviousSubwordStart,
11891 window: &mut Window,
11892 cx: &mut Context<Self>,
11893 ) {
11894 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11895 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11896 s.move_cursors_with(|map, head, _| {
11897 (
11898 movement::previous_subword_start(map, head),
11899 SelectionGoal::None,
11900 )
11901 });
11902 })
11903 }
11904
11905 pub fn select_to_previous_word_start(
11906 &mut self,
11907 _: &SelectToPreviousWordStart,
11908 window: &mut Window,
11909 cx: &mut Context<Self>,
11910 ) {
11911 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11912 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11913 s.move_heads_with(|map, head, _| {
11914 (
11915 movement::previous_word_start(map, head),
11916 SelectionGoal::None,
11917 )
11918 });
11919 })
11920 }
11921
11922 pub fn select_to_previous_subword_start(
11923 &mut self,
11924 _: &SelectToPreviousSubwordStart,
11925 window: &mut Window,
11926 cx: &mut Context<Self>,
11927 ) {
11928 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11929 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11930 s.move_heads_with(|map, head, _| {
11931 (
11932 movement::previous_subword_start(map, head),
11933 SelectionGoal::None,
11934 )
11935 });
11936 })
11937 }
11938
11939 pub fn delete_to_previous_word_start(
11940 &mut self,
11941 action: &DeleteToPreviousWordStart,
11942 window: &mut Window,
11943 cx: &mut Context<Self>,
11944 ) {
11945 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11946 self.transact(window, cx, |this, window, cx| {
11947 this.select_autoclose_pair(window, cx);
11948 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11949 s.move_with(|map, selection| {
11950 if selection.is_empty() {
11951 let cursor = if action.ignore_newlines {
11952 movement::previous_word_start(map, selection.head())
11953 } else {
11954 movement::previous_word_start_or_newline(map, selection.head())
11955 };
11956 selection.set_head(cursor, SelectionGoal::None);
11957 }
11958 });
11959 });
11960 this.insert("", window, cx);
11961 });
11962 }
11963
11964 pub fn delete_to_previous_subword_start(
11965 &mut self,
11966 _: &DeleteToPreviousSubwordStart,
11967 window: &mut Window,
11968 cx: &mut Context<Self>,
11969 ) {
11970 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11971 self.transact(window, cx, |this, window, cx| {
11972 this.select_autoclose_pair(window, cx);
11973 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11974 s.move_with(|map, selection| {
11975 if selection.is_empty() {
11976 let cursor = movement::previous_subword_start(map, selection.head());
11977 selection.set_head(cursor, SelectionGoal::None);
11978 }
11979 });
11980 });
11981 this.insert("", window, cx);
11982 });
11983 }
11984
11985 pub fn move_to_next_word_end(
11986 &mut self,
11987 _: &MoveToNextWordEnd,
11988 window: &mut Window,
11989 cx: &mut Context<Self>,
11990 ) {
11991 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11992 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11993 s.move_cursors_with(|map, head, _| {
11994 (movement::next_word_end(map, head), SelectionGoal::None)
11995 });
11996 })
11997 }
11998
11999 pub fn move_to_next_subword_end(
12000 &mut self,
12001 _: &MoveToNextSubwordEnd,
12002 window: &mut Window,
12003 cx: &mut Context<Self>,
12004 ) {
12005 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12006 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12007 s.move_cursors_with(|map, head, _| {
12008 (movement::next_subword_end(map, head), SelectionGoal::None)
12009 });
12010 })
12011 }
12012
12013 pub fn select_to_next_word_end(
12014 &mut self,
12015 _: &SelectToNextWordEnd,
12016 window: &mut Window,
12017 cx: &mut Context<Self>,
12018 ) {
12019 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12020 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12021 s.move_heads_with(|map, head, _| {
12022 (movement::next_word_end(map, head), SelectionGoal::None)
12023 });
12024 })
12025 }
12026
12027 pub fn select_to_next_subword_end(
12028 &mut self,
12029 _: &SelectToNextSubwordEnd,
12030 window: &mut Window,
12031 cx: &mut Context<Self>,
12032 ) {
12033 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12034 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12035 s.move_heads_with(|map, head, _| {
12036 (movement::next_subword_end(map, head), SelectionGoal::None)
12037 });
12038 })
12039 }
12040
12041 pub fn delete_to_next_word_end(
12042 &mut self,
12043 action: &DeleteToNextWordEnd,
12044 window: &mut Window,
12045 cx: &mut Context<Self>,
12046 ) {
12047 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12048 self.transact(window, cx, |this, window, cx| {
12049 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12050 s.move_with(|map, selection| {
12051 if selection.is_empty() {
12052 let cursor = if action.ignore_newlines {
12053 movement::next_word_end(map, selection.head())
12054 } else {
12055 movement::next_word_end_or_newline(map, selection.head())
12056 };
12057 selection.set_head(cursor, SelectionGoal::None);
12058 }
12059 });
12060 });
12061 this.insert("", window, cx);
12062 });
12063 }
12064
12065 pub fn delete_to_next_subword_end(
12066 &mut self,
12067 _: &DeleteToNextSubwordEnd,
12068 window: &mut Window,
12069 cx: &mut Context<Self>,
12070 ) {
12071 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12072 self.transact(window, cx, |this, window, cx| {
12073 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12074 s.move_with(|map, selection| {
12075 if selection.is_empty() {
12076 let cursor = movement::next_subword_end(map, selection.head());
12077 selection.set_head(cursor, SelectionGoal::None);
12078 }
12079 });
12080 });
12081 this.insert("", window, cx);
12082 });
12083 }
12084
12085 pub fn move_to_beginning_of_line(
12086 &mut self,
12087 action: &MoveToBeginningOfLine,
12088 window: &mut Window,
12089 cx: &mut Context<Self>,
12090 ) {
12091 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12092 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12093 s.move_cursors_with(|map, head, _| {
12094 (
12095 movement::indented_line_beginning(
12096 map,
12097 head,
12098 action.stop_at_soft_wraps,
12099 action.stop_at_indent,
12100 ),
12101 SelectionGoal::None,
12102 )
12103 });
12104 })
12105 }
12106
12107 pub fn select_to_beginning_of_line(
12108 &mut self,
12109 action: &SelectToBeginningOfLine,
12110 window: &mut Window,
12111 cx: &mut Context<Self>,
12112 ) {
12113 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12114 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12115 s.move_heads_with(|map, head, _| {
12116 (
12117 movement::indented_line_beginning(
12118 map,
12119 head,
12120 action.stop_at_soft_wraps,
12121 action.stop_at_indent,
12122 ),
12123 SelectionGoal::None,
12124 )
12125 });
12126 });
12127 }
12128
12129 pub fn delete_to_beginning_of_line(
12130 &mut self,
12131 action: &DeleteToBeginningOfLine,
12132 window: &mut Window,
12133 cx: &mut Context<Self>,
12134 ) {
12135 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12136 self.transact(window, cx, |this, window, cx| {
12137 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12138 s.move_with(|_, selection| {
12139 selection.reversed = true;
12140 });
12141 });
12142
12143 this.select_to_beginning_of_line(
12144 &SelectToBeginningOfLine {
12145 stop_at_soft_wraps: false,
12146 stop_at_indent: action.stop_at_indent,
12147 },
12148 window,
12149 cx,
12150 );
12151 this.backspace(&Backspace, window, cx);
12152 });
12153 }
12154
12155 pub fn move_to_end_of_line(
12156 &mut self,
12157 action: &MoveToEndOfLine,
12158 window: &mut Window,
12159 cx: &mut Context<Self>,
12160 ) {
12161 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12162 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12163 s.move_cursors_with(|map, head, _| {
12164 (
12165 movement::line_end(map, head, action.stop_at_soft_wraps),
12166 SelectionGoal::None,
12167 )
12168 });
12169 })
12170 }
12171
12172 pub fn select_to_end_of_line(
12173 &mut self,
12174 action: &SelectToEndOfLine,
12175 window: &mut Window,
12176 cx: &mut Context<Self>,
12177 ) {
12178 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12179 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12180 s.move_heads_with(|map, head, _| {
12181 (
12182 movement::line_end(map, head, action.stop_at_soft_wraps),
12183 SelectionGoal::None,
12184 )
12185 });
12186 })
12187 }
12188
12189 pub fn delete_to_end_of_line(
12190 &mut self,
12191 _: &DeleteToEndOfLine,
12192 window: &mut Window,
12193 cx: &mut Context<Self>,
12194 ) {
12195 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12196 self.transact(window, cx, |this, window, cx| {
12197 this.select_to_end_of_line(
12198 &SelectToEndOfLine {
12199 stop_at_soft_wraps: false,
12200 },
12201 window,
12202 cx,
12203 );
12204 this.delete(&Delete, window, cx);
12205 });
12206 }
12207
12208 pub fn cut_to_end_of_line(
12209 &mut self,
12210 _: &CutToEndOfLine,
12211 window: &mut Window,
12212 cx: &mut Context<Self>,
12213 ) {
12214 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12215 self.transact(window, cx, |this, window, cx| {
12216 this.select_to_end_of_line(
12217 &SelectToEndOfLine {
12218 stop_at_soft_wraps: false,
12219 },
12220 window,
12221 cx,
12222 );
12223 this.cut(&Cut, window, cx);
12224 });
12225 }
12226
12227 pub fn move_to_start_of_paragraph(
12228 &mut self,
12229 _: &MoveToStartOfParagraph,
12230 window: &mut Window,
12231 cx: &mut Context<Self>,
12232 ) {
12233 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12234 cx.propagate();
12235 return;
12236 }
12237 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12238 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12239 s.move_with(|map, selection| {
12240 selection.collapse_to(
12241 movement::start_of_paragraph(map, selection.head(), 1),
12242 SelectionGoal::None,
12243 )
12244 });
12245 })
12246 }
12247
12248 pub fn move_to_end_of_paragraph(
12249 &mut self,
12250 _: &MoveToEndOfParagraph,
12251 window: &mut Window,
12252 cx: &mut Context<Self>,
12253 ) {
12254 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12255 cx.propagate();
12256 return;
12257 }
12258 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12259 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12260 s.move_with(|map, selection| {
12261 selection.collapse_to(
12262 movement::end_of_paragraph(map, selection.head(), 1),
12263 SelectionGoal::None,
12264 )
12265 });
12266 })
12267 }
12268
12269 pub fn select_to_start_of_paragraph(
12270 &mut self,
12271 _: &SelectToStartOfParagraph,
12272 window: &mut Window,
12273 cx: &mut Context<Self>,
12274 ) {
12275 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12276 cx.propagate();
12277 return;
12278 }
12279 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12280 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12281 s.move_heads_with(|map, head, _| {
12282 (
12283 movement::start_of_paragraph(map, head, 1),
12284 SelectionGoal::None,
12285 )
12286 });
12287 })
12288 }
12289
12290 pub fn select_to_end_of_paragraph(
12291 &mut self,
12292 _: &SelectToEndOfParagraph,
12293 window: &mut Window,
12294 cx: &mut Context<Self>,
12295 ) {
12296 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12297 cx.propagate();
12298 return;
12299 }
12300 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12301 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12302 s.move_heads_with(|map, head, _| {
12303 (
12304 movement::end_of_paragraph(map, head, 1),
12305 SelectionGoal::None,
12306 )
12307 });
12308 })
12309 }
12310
12311 pub fn move_to_start_of_excerpt(
12312 &mut self,
12313 _: &MoveToStartOfExcerpt,
12314 window: &mut Window,
12315 cx: &mut Context<Self>,
12316 ) {
12317 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12318 cx.propagate();
12319 return;
12320 }
12321 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12322 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12323 s.move_with(|map, selection| {
12324 selection.collapse_to(
12325 movement::start_of_excerpt(
12326 map,
12327 selection.head(),
12328 workspace::searchable::Direction::Prev,
12329 ),
12330 SelectionGoal::None,
12331 )
12332 });
12333 })
12334 }
12335
12336 pub fn move_to_start_of_next_excerpt(
12337 &mut self,
12338 _: &MoveToStartOfNextExcerpt,
12339 window: &mut Window,
12340 cx: &mut Context<Self>,
12341 ) {
12342 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12343 cx.propagate();
12344 return;
12345 }
12346
12347 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12348 s.move_with(|map, selection| {
12349 selection.collapse_to(
12350 movement::start_of_excerpt(
12351 map,
12352 selection.head(),
12353 workspace::searchable::Direction::Next,
12354 ),
12355 SelectionGoal::None,
12356 )
12357 });
12358 })
12359 }
12360
12361 pub fn move_to_end_of_excerpt(
12362 &mut self,
12363 _: &MoveToEndOfExcerpt,
12364 window: &mut Window,
12365 cx: &mut Context<Self>,
12366 ) {
12367 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12368 cx.propagate();
12369 return;
12370 }
12371 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12372 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12373 s.move_with(|map, selection| {
12374 selection.collapse_to(
12375 movement::end_of_excerpt(
12376 map,
12377 selection.head(),
12378 workspace::searchable::Direction::Next,
12379 ),
12380 SelectionGoal::None,
12381 )
12382 });
12383 })
12384 }
12385
12386 pub fn move_to_end_of_previous_excerpt(
12387 &mut self,
12388 _: &MoveToEndOfPreviousExcerpt,
12389 window: &mut Window,
12390 cx: &mut Context<Self>,
12391 ) {
12392 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12393 cx.propagate();
12394 return;
12395 }
12396 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12397 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12398 s.move_with(|map, selection| {
12399 selection.collapse_to(
12400 movement::end_of_excerpt(
12401 map,
12402 selection.head(),
12403 workspace::searchable::Direction::Prev,
12404 ),
12405 SelectionGoal::None,
12406 )
12407 });
12408 })
12409 }
12410
12411 pub fn select_to_start_of_excerpt(
12412 &mut self,
12413 _: &SelectToStartOfExcerpt,
12414 window: &mut Window,
12415 cx: &mut Context<Self>,
12416 ) {
12417 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12418 cx.propagate();
12419 return;
12420 }
12421 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12422 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12423 s.move_heads_with(|map, head, _| {
12424 (
12425 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12426 SelectionGoal::None,
12427 )
12428 });
12429 })
12430 }
12431
12432 pub fn select_to_start_of_next_excerpt(
12433 &mut self,
12434 _: &SelectToStartOfNextExcerpt,
12435 window: &mut Window,
12436 cx: &mut Context<Self>,
12437 ) {
12438 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12439 cx.propagate();
12440 return;
12441 }
12442 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12443 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12444 s.move_heads_with(|map, head, _| {
12445 (
12446 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12447 SelectionGoal::None,
12448 )
12449 });
12450 })
12451 }
12452
12453 pub fn select_to_end_of_excerpt(
12454 &mut self,
12455 _: &SelectToEndOfExcerpt,
12456 window: &mut Window,
12457 cx: &mut Context<Self>,
12458 ) {
12459 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12460 cx.propagate();
12461 return;
12462 }
12463 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12464 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12465 s.move_heads_with(|map, head, _| {
12466 (
12467 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12468 SelectionGoal::None,
12469 )
12470 });
12471 })
12472 }
12473
12474 pub fn select_to_end_of_previous_excerpt(
12475 &mut self,
12476 _: &SelectToEndOfPreviousExcerpt,
12477 window: &mut Window,
12478 cx: &mut Context<Self>,
12479 ) {
12480 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12481 cx.propagate();
12482 return;
12483 }
12484 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12485 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12486 s.move_heads_with(|map, head, _| {
12487 (
12488 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12489 SelectionGoal::None,
12490 )
12491 });
12492 })
12493 }
12494
12495 pub fn move_to_beginning(
12496 &mut self,
12497 _: &MoveToBeginning,
12498 window: &mut Window,
12499 cx: &mut Context<Self>,
12500 ) {
12501 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12502 cx.propagate();
12503 return;
12504 }
12505 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12506 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12507 s.select_ranges(vec![0..0]);
12508 });
12509 }
12510
12511 pub fn select_to_beginning(
12512 &mut self,
12513 _: &SelectToBeginning,
12514 window: &mut Window,
12515 cx: &mut Context<Self>,
12516 ) {
12517 let mut selection = self.selections.last::<Point>(cx);
12518 selection.set_head(Point::zero(), SelectionGoal::None);
12519 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12520 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12521 s.select(vec![selection]);
12522 });
12523 }
12524
12525 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12526 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12527 cx.propagate();
12528 return;
12529 }
12530 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12531 let cursor = self.buffer.read(cx).read(cx).len();
12532 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12533 s.select_ranges(vec![cursor..cursor])
12534 });
12535 }
12536
12537 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12538 self.nav_history = nav_history;
12539 }
12540
12541 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12542 self.nav_history.as_ref()
12543 }
12544
12545 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12546 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12547 }
12548
12549 fn push_to_nav_history(
12550 &mut self,
12551 cursor_anchor: Anchor,
12552 new_position: Option<Point>,
12553 is_deactivate: bool,
12554 cx: &mut Context<Self>,
12555 ) {
12556 if let Some(nav_history) = self.nav_history.as_mut() {
12557 let buffer = self.buffer.read(cx).read(cx);
12558 let cursor_position = cursor_anchor.to_point(&buffer);
12559 let scroll_state = self.scroll_manager.anchor();
12560 let scroll_top_row = scroll_state.top_row(&buffer);
12561 drop(buffer);
12562
12563 if let Some(new_position) = new_position {
12564 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12565 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12566 return;
12567 }
12568 }
12569
12570 nav_history.push(
12571 Some(NavigationData {
12572 cursor_anchor,
12573 cursor_position,
12574 scroll_anchor: scroll_state,
12575 scroll_top_row,
12576 }),
12577 cx,
12578 );
12579 cx.emit(EditorEvent::PushedToNavHistory {
12580 anchor: cursor_anchor,
12581 is_deactivate,
12582 })
12583 }
12584 }
12585
12586 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12587 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12588 let buffer = self.buffer.read(cx).snapshot(cx);
12589 let mut selection = self.selections.first::<usize>(cx);
12590 selection.set_head(buffer.len(), SelectionGoal::None);
12591 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12592 s.select(vec![selection]);
12593 });
12594 }
12595
12596 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12597 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12598 let end = self.buffer.read(cx).read(cx).len();
12599 self.change_selections(None, window, cx, |s| {
12600 s.select_ranges(vec![0..end]);
12601 });
12602 }
12603
12604 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12605 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12606 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12607 let mut selections = self.selections.all::<Point>(cx);
12608 let max_point = display_map.buffer_snapshot.max_point();
12609 for selection in &mut selections {
12610 let rows = selection.spanned_rows(true, &display_map);
12611 selection.start = Point::new(rows.start.0, 0);
12612 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12613 selection.reversed = false;
12614 }
12615 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12616 s.select(selections);
12617 });
12618 }
12619
12620 pub fn split_selection_into_lines(
12621 &mut self,
12622 _: &SplitSelectionIntoLines,
12623 window: &mut Window,
12624 cx: &mut Context<Self>,
12625 ) {
12626 let selections = self
12627 .selections
12628 .all::<Point>(cx)
12629 .into_iter()
12630 .map(|selection| selection.start..selection.end)
12631 .collect::<Vec<_>>();
12632 self.unfold_ranges(&selections, true, true, cx);
12633
12634 let mut new_selection_ranges = Vec::new();
12635 {
12636 let buffer = self.buffer.read(cx).read(cx);
12637 for selection in selections {
12638 for row in selection.start.row..selection.end.row {
12639 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12640 new_selection_ranges.push(cursor..cursor);
12641 }
12642
12643 let is_multiline_selection = selection.start.row != selection.end.row;
12644 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12645 // so this action feels more ergonomic when paired with other selection operations
12646 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12647 if !should_skip_last {
12648 new_selection_ranges.push(selection.end..selection.end);
12649 }
12650 }
12651 }
12652 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12653 s.select_ranges(new_selection_ranges);
12654 });
12655 }
12656
12657 pub fn add_selection_above(
12658 &mut self,
12659 _: &AddSelectionAbove,
12660 window: &mut Window,
12661 cx: &mut Context<Self>,
12662 ) {
12663 self.add_selection(true, window, cx);
12664 }
12665
12666 pub fn add_selection_below(
12667 &mut self,
12668 _: &AddSelectionBelow,
12669 window: &mut Window,
12670 cx: &mut Context<Self>,
12671 ) {
12672 self.add_selection(false, window, cx);
12673 }
12674
12675 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12676 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12677
12678 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12679 let mut selections = self.selections.all::<Point>(cx);
12680 let text_layout_details = self.text_layout_details(window);
12681 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12682 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12683 let range = oldest_selection.display_range(&display_map).sorted();
12684
12685 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12686 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12687 let positions = start_x.min(end_x)..start_x.max(end_x);
12688
12689 selections.clear();
12690 let mut stack = Vec::new();
12691 for row in range.start.row().0..=range.end.row().0 {
12692 if let Some(selection) = self.selections.build_columnar_selection(
12693 &display_map,
12694 DisplayRow(row),
12695 &positions,
12696 oldest_selection.reversed,
12697 &text_layout_details,
12698 ) {
12699 stack.push(selection.id);
12700 selections.push(selection);
12701 }
12702 }
12703
12704 if above {
12705 stack.reverse();
12706 }
12707
12708 AddSelectionsState { above, stack }
12709 });
12710
12711 let last_added_selection = *state.stack.last().unwrap();
12712 let mut new_selections = Vec::new();
12713 if above == state.above {
12714 let end_row = if above {
12715 DisplayRow(0)
12716 } else {
12717 display_map.max_point().row()
12718 };
12719
12720 'outer: for selection in selections {
12721 if selection.id == last_added_selection {
12722 let range = selection.display_range(&display_map).sorted();
12723 debug_assert_eq!(range.start.row(), range.end.row());
12724 let mut row = range.start.row();
12725 let positions =
12726 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12727 px(start)..px(end)
12728 } else {
12729 let start_x =
12730 display_map.x_for_display_point(range.start, &text_layout_details);
12731 let end_x =
12732 display_map.x_for_display_point(range.end, &text_layout_details);
12733 start_x.min(end_x)..start_x.max(end_x)
12734 };
12735
12736 while row != end_row {
12737 if above {
12738 row.0 -= 1;
12739 } else {
12740 row.0 += 1;
12741 }
12742
12743 if let Some(new_selection) = self.selections.build_columnar_selection(
12744 &display_map,
12745 row,
12746 &positions,
12747 selection.reversed,
12748 &text_layout_details,
12749 ) {
12750 state.stack.push(new_selection.id);
12751 if above {
12752 new_selections.push(new_selection);
12753 new_selections.push(selection);
12754 } else {
12755 new_selections.push(selection);
12756 new_selections.push(new_selection);
12757 }
12758
12759 continue 'outer;
12760 }
12761 }
12762 }
12763
12764 new_selections.push(selection);
12765 }
12766 } else {
12767 new_selections = selections;
12768 new_selections.retain(|s| s.id != last_added_selection);
12769 state.stack.pop();
12770 }
12771
12772 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12773 s.select(new_selections);
12774 });
12775 if state.stack.len() > 1 {
12776 self.add_selections_state = Some(state);
12777 }
12778 }
12779
12780 fn select_match_ranges(
12781 &mut self,
12782 range: Range<usize>,
12783 reversed: bool,
12784 replace_newest: bool,
12785 auto_scroll: Option<Autoscroll>,
12786 window: &mut Window,
12787 cx: &mut Context<Editor>,
12788 ) {
12789 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12790 self.change_selections(auto_scroll, window, cx, |s| {
12791 if replace_newest {
12792 s.delete(s.newest_anchor().id);
12793 }
12794 if reversed {
12795 s.insert_range(range.end..range.start);
12796 } else {
12797 s.insert_range(range);
12798 }
12799 });
12800 }
12801
12802 pub fn select_next_match_internal(
12803 &mut self,
12804 display_map: &DisplaySnapshot,
12805 replace_newest: bool,
12806 autoscroll: Option<Autoscroll>,
12807 window: &mut Window,
12808 cx: &mut Context<Self>,
12809 ) -> Result<()> {
12810 let buffer = &display_map.buffer_snapshot;
12811 let mut selections = self.selections.all::<usize>(cx);
12812 if let Some(mut select_next_state) = self.select_next_state.take() {
12813 let query = &select_next_state.query;
12814 if !select_next_state.done {
12815 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12816 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12817 let mut next_selected_range = None;
12818
12819 let bytes_after_last_selection =
12820 buffer.bytes_in_range(last_selection.end..buffer.len());
12821 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12822 let query_matches = query
12823 .stream_find_iter(bytes_after_last_selection)
12824 .map(|result| (last_selection.end, result))
12825 .chain(
12826 query
12827 .stream_find_iter(bytes_before_first_selection)
12828 .map(|result| (0, result)),
12829 );
12830
12831 for (start_offset, query_match) in query_matches {
12832 let query_match = query_match.unwrap(); // can only fail due to I/O
12833 let offset_range =
12834 start_offset + query_match.start()..start_offset + query_match.end();
12835 let display_range = offset_range.start.to_display_point(display_map)
12836 ..offset_range.end.to_display_point(display_map);
12837
12838 if !select_next_state.wordwise
12839 || (!movement::is_inside_word(display_map, display_range.start)
12840 && !movement::is_inside_word(display_map, display_range.end))
12841 {
12842 // TODO: This is n^2, because we might check all the selections
12843 if !selections
12844 .iter()
12845 .any(|selection| selection.range().overlaps(&offset_range))
12846 {
12847 next_selected_range = Some(offset_range);
12848 break;
12849 }
12850 }
12851 }
12852
12853 if let Some(next_selected_range) = next_selected_range {
12854 self.select_match_ranges(
12855 next_selected_range,
12856 last_selection.reversed,
12857 replace_newest,
12858 autoscroll,
12859 window,
12860 cx,
12861 );
12862 } else {
12863 select_next_state.done = true;
12864 }
12865 }
12866
12867 self.select_next_state = Some(select_next_state);
12868 } else {
12869 let mut only_carets = true;
12870 let mut same_text_selected = true;
12871 let mut selected_text = None;
12872
12873 let mut selections_iter = selections.iter().peekable();
12874 while let Some(selection) = selections_iter.next() {
12875 if selection.start != selection.end {
12876 only_carets = false;
12877 }
12878
12879 if same_text_selected {
12880 if selected_text.is_none() {
12881 selected_text =
12882 Some(buffer.text_for_range(selection.range()).collect::<String>());
12883 }
12884
12885 if let Some(next_selection) = selections_iter.peek() {
12886 if next_selection.range().len() == selection.range().len() {
12887 let next_selected_text = buffer
12888 .text_for_range(next_selection.range())
12889 .collect::<String>();
12890 if Some(next_selected_text) != selected_text {
12891 same_text_selected = false;
12892 selected_text = None;
12893 }
12894 } else {
12895 same_text_selected = false;
12896 selected_text = None;
12897 }
12898 }
12899 }
12900 }
12901
12902 if only_carets {
12903 for selection in &mut selections {
12904 let word_range = movement::surrounding_word(
12905 display_map,
12906 selection.start.to_display_point(display_map),
12907 );
12908 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12909 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12910 selection.goal = SelectionGoal::None;
12911 selection.reversed = false;
12912 self.select_match_ranges(
12913 selection.start..selection.end,
12914 selection.reversed,
12915 replace_newest,
12916 autoscroll,
12917 window,
12918 cx,
12919 );
12920 }
12921
12922 if selections.len() == 1 {
12923 let selection = selections
12924 .last()
12925 .expect("ensured that there's only one selection");
12926 let query = buffer
12927 .text_for_range(selection.start..selection.end)
12928 .collect::<String>();
12929 let is_empty = query.is_empty();
12930 let select_state = SelectNextState {
12931 query: AhoCorasick::new(&[query])?,
12932 wordwise: true,
12933 done: is_empty,
12934 };
12935 self.select_next_state = Some(select_state);
12936 } else {
12937 self.select_next_state = None;
12938 }
12939 } else if let Some(selected_text) = selected_text {
12940 self.select_next_state = Some(SelectNextState {
12941 query: AhoCorasick::new(&[selected_text])?,
12942 wordwise: false,
12943 done: false,
12944 });
12945 self.select_next_match_internal(
12946 display_map,
12947 replace_newest,
12948 autoscroll,
12949 window,
12950 cx,
12951 )?;
12952 }
12953 }
12954 Ok(())
12955 }
12956
12957 pub fn select_all_matches(
12958 &mut self,
12959 _action: &SelectAllMatches,
12960 window: &mut Window,
12961 cx: &mut Context<Self>,
12962 ) -> Result<()> {
12963 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12964
12965 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12966
12967 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12968 let Some(select_next_state) = self.select_next_state.as_mut() else {
12969 return Ok(());
12970 };
12971 if select_next_state.done {
12972 return Ok(());
12973 }
12974
12975 let mut new_selections = Vec::new();
12976
12977 let reversed = self.selections.oldest::<usize>(cx).reversed;
12978 let buffer = &display_map.buffer_snapshot;
12979 let query_matches = select_next_state
12980 .query
12981 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12982
12983 for query_match in query_matches.into_iter() {
12984 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12985 let offset_range = if reversed {
12986 query_match.end()..query_match.start()
12987 } else {
12988 query_match.start()..query_match.end()
12989 };
12990 let display_range = offset_range.start.to_display_point(&display_map)
12991 ..offset_range.end.to_display_point(&display_map);
12992
12993 if !select_next_state.wordwise
12994 || (!movement::is_inside_word(&display_map, display_range.start)
12995 && !movement::is_inside_word(&display_map, display_range.end))
12996 {
12997 new_selections.push(offset_range.start..offset_range.end);
12998 }
12999 }
13000
13001 select_next_state.done = true;
13002 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13003 self.change_selections(None, window, cx, |selections| {
13004 selections.select_ranges(new_selections)
13005 });
13006
13007 Ok(())
13008 }
13009
13010 pub fn select_next(
13011 &mut self,
13012 action: &SelectNext,
13013 window: &mut Window,
13014 cx: &mut Context<Self>,
13015 ) -> Result<()> {
13016 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13017 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13018 self.select_next_match_internal(
13019 &display_map,
13020 action.replace_newest,
13021 Some(Autoscroll::newest()),
13022 window,
13023 cx,
13024 )?;
13025 Ok(())
13026 }
13027
13028 pub fn select_previous(
13029 &mut self,
13030 action: &SelectPrevious,
13031 window: &mut Window,
13032 cx: &mut Context<Self>,
13033 ) -> Result<()> {
13034 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13035 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13036 let buffer = &display_map.buffer_snapshot;
13037 let mut selections = self.selections.all::<usize>(cx);
13038 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13039 let query = &select_prev_state.query;
13040 if !select_prev_state.done {
13041 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13042 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13043 let mut next_selected_range = None;
13044 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13045 let bytes_before_last_selection =
13046 buffer.reversed_bytes_in_range(0..last_selection.start);
13047 let bytes_after_first_selection =
13048 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13049 let query_matches = query
13050 .stream_find_iter(bytes_before_last_selection)
13051 .map(|result| (last_selection.start, result))
13052 .chain(
13053 query
13054 .stream_find_iter(bytes_after_first_selection)
13055 .map(|result| (buffer.len(), result)),
13056 );
13057 for (end_offset, query_match) in query_matches {
13058 let query_match = query_match.unwrap(); // can only fail due to I/O
13059 let offset_range =
13060 end_offset - query_match.end()..end_offset - query_match.start();
13061 let display_range = offset_range.start.to_display_point(&display_map)
13062 ..offset_range.end.to_display_point(&display_map);
13063
13064 if !select_prev_state.wordwise
13065 || (!movement::is_inside_word(&display_map, display_range.start)
13066 && !movement::is_inside_word(&display_map, display_range.end))
13067 {
13068 next_selected_range = Some(offset_range);
13069 break;
13070 }
13071 }
13072
13073 if let Some(next_selected_range) = next_selected_range {
13074 self.select_match_ranges(
13075 next_selected_range,
13076 last_selection.reversed,
13077 action.replace_newest,
13078 Some(Autoscroll::newest()),
13079 window,
13080 cx,
13081 );
13082 } else {
13083 select_prev_state.done = true;
13084 }
13085 }
13086
13087 self.select_prev_state = Some(select_prev_state);
13088 } else {
13089 let mut only_carets = true;
13090 let mut same_text_selected = true;
13091 let mut selected_text = None;
13092
13093 let mut selections_iter = selections.iter().peekable();
13094 while let Some(selection) = selections_iter.next() {
13095 if selection.start != selection.end {
13096 only_carets = false;
13097 }
13098
13099 if same_text_selected {
13100 if selected_text.is_none() {
13101 selected_text =
13102 Some(buffer.text_for_range(selection.range()).collect::<String>());
13103 }
13104
13105 if let Some(next_selection) = selections_iter.peek() {
13106 if next_selection.range().len() == selection.range().len() {
13107 let next_selected_text = buffer
13108 .text_for_range(next_selection.range())
13109 .collect::<String>();
13110 if Some(next_selected_text) != selected_text {
13111 same_text_selected = false;
13112 selected_text = None;
13113 }
13114 } else {
13115 same_text_selected = false;
13116 selected_text = None;
13117 }
13118 }
13119 }
13120 }
13121
13122 if only_carets {
13123 for selection in &mut selections {
13124 let word_range = movement::surrounding_word(
13125 &display_map,
13126 selection.start.to_display_point(&display_map),
13127 );
13128 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13129 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13130 selection.goal = SelectionGoal::None;
13131 selection.reversed = false;
13132 self.select_match_ranges(
13133 selection.start..selection.end,
13134 selection.reversed,
13135 action.replace_newest,
13136 Some(Autoscroll::newest()),
13137 window,
13138 cx,
13139 );
13140 }
13141 if selections.len() == 1 {
13142 let selection = selections
13143 .last()
13144 .expect("ensured that there's only one selection");
13145 let query = buffer
13146 .text_for_range(selection.start..selection.end)
13147 .collect::<String>();
13148 let is_empty = query.is_empty();
13149 let select_state = SelectNextState {
13150 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13151 wordwise: true,
13152 done: is_empty,
13153 };
13154 self.select_prev_state = Some(select_state);
13155 } else {
13156 self.select_prev_state = None;
13157 }
13158 } else if let Some(selected_text) = selected_text {
13159 self.select_prev_state = Some(SelectNextState {
13160 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13161 wordwise: false,
13162 done: false,
13163 });
13164 self.select_previous(action, window, cx)?;
13165 }
13166 }
13167 Ok(())
13168 }
13169
13170 pub fn find_next_match(
13171 &mut self,
13172 _: &FindNextMatch,
13173 window: &mut Window,
13174 cx: &mut Context<Self>,
13175 ) -> Result<()> {
13176 let selections = self.selections.disjoint_anchors();
13177 match selections.first() {
13178 Some(first) if selections.len() >= 2 => {
13179 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13180 s.select_ranges([first.range()]);
13181 });
13182 }
13183 _ => self.select_next(
13184 &SelectNext {
13185 replace_newest: true,
13186 },
13187 window,
13188 cx,
13189 )?,
13190 }
13191 Ok(())
13192 }
13193
13194 pub fn find_previous_match(
13195 &mut self,
13196 _: &FindPreviousMatch,
13197 window: &mut Window,
13198 cx: &mut Context<Self>,
13199 ) -> Result<()> {
13200 let selections = self.selections.disjoint_anchors();
13201 match selections.last() {
13202 Some(last) if selections.len() >= 2 => {
13203 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13204 s.select_ranges([last.range()]);
13205 });
13206 }
13207 _ => self.select_previous(
13208 &SelectPrevious {
13209 replace_newest: true,
13210 },
13211 window,
13212 cx,
13213 )?,
13214 }
13215 Ok(())
13216 }
13217
13218 pub fn toggle_comments(
13219 &mut self,
13220 action: &ToggleComments,
13221 window: &mut Window,
13222 cx: &mut Context<Self>,
13223 ) {
13224 if self.read_only(cx) {
13225 return;
13226 }
13227 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13228 let text_layout_details = &self.text_layout_details(window);
13229 self.transact(window, cx, |this, window, cx| {
13230 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13231 let mut edits = Vec::new();
13232 let mut selection_edit_ranges = Vec::new();
13233 let mut last_toggled_row = None;
13234 let snapshot = this.buffer.read(cx).read(cx);
13235 let empty_str: Arc<str> = Arc::default();
13236 let mut suffixes_inserted = Vec::new();
13237 let ignore_indent = action.ignore_indent;
13238
13239 fn comment_prefix_range(
13240 snapshot: &MultiBufferSnapshot,
13241 row: MultiBufferRow,
13242 comment_prefix: &str,
13243 comment_prefix_whitespace: &str,
13244 ignore_indent: bool,
13245 ) -> Range<Point> {
13246 let indent_size = if ignore_indent {
13247 0
13248 } else {
13249 snapshot.indent_size_for_line(row).len
13250 };
13251
13252 let start = Point::new(row.0, indent_size);
13253
13254 let mut line_bytes = snapshot
13255 .bytes_in_range(start..snapshot.max_point())
13256 .flatten()
13257 .copied();
13258
13259 // If this line currently begins with the line comment prefix, then record
13260 // the range containing the prefix.
13261 if line_bytes
13262 .by_ref()
13263 .take(comment_prefix.len())
13264 .eq(comment_prefix.bytes())
13265 {
13266 // Include any whitespace that matches the comment prefix.
13267 let matching_whitespace_len = line_bytes
13268 .zip(comment_prefix_whitespace.bytes())
13269 .take_while(|(a, b)| a == b)
13270 .count() as u32;
13271 let end = Point::new(
13272 start.row,
13273 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13274 );
13275 start..end
13276 } else {
13277 start..start
13278 }
13279 }
13280
13281 fn comment_suffix_range(
13282 snapshot: &MultiBufferSnapshot,
13283 row: MultiBufferRow,
13284 comment_suffix: &str,
13285 comment_suffix_has_leading_space: bool,
13286 ) -> Range<Point> {
13287 let end = Point::new(row.0, snapshot.line_len(row));
13288 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13289
13290 let mut line_end_bytes = snapshot
13291 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13292 .flatten()
13293 .copied();
13294
13295 let leading_space_len = if suffix_start_column > 0
13296 && line_end_bytes.next() == Some(b' ')
13297 && comment_suffix_has_leading_space
13298 {
13299 1
13300 } else {
13301 0
13302 };
13303
13304 // If this line currently begins with the line comment prefix, then record
13305 // the range containing the prefix.
13306 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13307 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13308 start..end
13309 } else {
13310 end..end
13311 }
13312 }
13313
13314 // TODO: Handle selections that cross excerpts
13315 for selection in &mut selections {
13316 let start_column = snapshot
13317 .indent_size_for_line(MultiBufferRow(selection.start.row))
13318 .len;
13319 let language = if let Some(language) =
13320 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13321 {
13322 language
13323 } else {
13324 continue;
13325 };
13326
13327 selection_edit_ranges.clear();
13328
13329 // If multiple selections contain a given row, avoid processing that
13330 // row more than once.
13331 let mut start_row = MultiBufferRow(selection.start.row);
13332 if last_toggled_row == Some(start_row) {
13333 start_row = start_row.next_row();
13334 }
13335 let end_row =
13336 if selection.end.row > selection.start.row && selection.end.column == 0 {
13337 MultiBufferRow(selection.end.row - 1)
13338 } else {
13339 MultiBufferRow(selection.end.row)
13340 };
13341 last_toggled_row = Some(end_row);
13342
13343 if start_row > end_row {
13344 continue;
13345 }
13346
13347 // If the language has line comments, toggle those.
13348 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13349
13350 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13351 if ignore_indent {
13352 full_comment_prefixes = full_comment_prefixes
13353 .into_iter()
13354 .map(|s| Arc::from(s.trim_end()))
13355 .collect();
13356 }
13357
13358 if !full_comment_prefixes.is_empty() {
13359 let first_prefix = full_comment_prefixes
13360 .first()
13361 .expect("prefixes is non-empty");
13362 let prefix_trimmed_lengths = full_comment_prefixes
13363 .iter()
13364 .map(|p| p.trim_end_matches(' ').len())
13365 .collect::<SmallVec<[usize; 4]>>();
13366
13367 let mut all_selection_lines_are_comments = true;
13368
13369 for row in start_row.0..=end_row.0 {
13370 let row = MultiBufferRow(row);
13371 if start_row < end_row && snapshot.is_line_blank(row) {
13372 continue;
13373 }
13374
13375 let prefix_range = full_comment_prefixes
13376 .iter()
13377 .zip(prefix_trimmed_lengths.iter().copied())
13378 .map(|(prefix, trimmed_prefix_len)| {
13379 comment_prefix_range(
13380 snapshot.deref(),
13381 row,
13382 &prefix[..trimmed_prefix_len],
13383 &prefix[trimmed_prefix_len..],
13384 ignore_indent,
13385 )
13386 })
13387 .max_by_key(|range| range.end.column - range.start.column)
13388 .expect("prefixes is non-empty");
13389
13390 if prefix_range.is_empty() {
13391 all_selection_lines_are_comments = false;
13392 }
13393
13394 selection_edit_ranges.push(prefix_range);
13395 }
13396
13397 if all_selection_lines_are_comments {
13398 edits.extend(
13399 selection_edit_ranges
13400 .iter()
13401 .cloned()
13402 .map(|range| (range, empty_str.clone())),
13403 );
13404 } else {
13405 let min_column = selection_edit_ranges
13406 .iter()
13407 .map(|range| range.start.column)
13408 .min()
13409 .unwrap_or(0);
13410 edits.extend(selection_edit_ranges.iter().map(|range| {
13411 let position = Point::new(range.start.row, min_column);
13412 (position..position, first_prefix.clone())
13413 }));
13414 }
13415 } else if let Some((full_comment_prefix, comment_suffix)) =
13416 language.block_comment_delimiters()
13417 {
13418 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13419 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13420 let prefix_range = comment_prefix_range(
13421 snapshot.deref(),
13422 start_row,
13423 comment_prefix,
13424 comment_prefix_whitespace,
13425 ignore_indent,
13426 );
13427 let suffix_range = comment_suffix_range(
13428 snapshot.deref(),
13429 end_row,
13430 comment_suffix.trim_start_matches(' '),
13431 comment_suffix.starts_with(' '),
13432 );
13433
13434 if prefix_range.is_empty() || suffix_range.is_empty() {
13435 edits.push((
13436 prefix_range.start..prefix_range.start,
13437 full_comment_prefix.clone(),
13438 ));
13439 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13440 suffixes_inserted.push((end_row, comment_suffix.len()));
13441 } else {
13442 edits.push((prefix_range, empty_str.clone()));
13443 edits.push((suffix_range, empty_str.clone()));
13444 }
13445 } else {
13446 continue;
13447 }
13448 }
13449
13450 drop(snapshot);
13451 this.buffer.update(cx, |buffer, cx| {
13452 buffer.edit(edits, None, cx);
13453 });
13454
13455 // Adjust selections so that they end before any comment suffixes that
13456 // were inserted.
13457 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13458 let mut selections = this.selections.all::<Point>(cx);
13459 let snapshot = this.buffer.read(cx).read(cx);
13460 for selection in &mut selections {
13461 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13462 match row.cmp(&MultiBufferRow(selection.end.row)) {
13463 Ordering::Less => {
13464 suffixes_inserted.next();
13465 continue;
13466 }
13467 Ordering::Greater => break,
13468 Ordering::Equal => {
13469 if selection.end.column == snapshot.line_len(row) {
13470 if selection.is_empty() {
13471 selection.start.column -= suffix_len as u32;
13472 }
13473 selection.end.column -= suffix_len as u32;
13474 }
13475 break;
13476 }
13477 }
13478 }
13479 }
13480
13481 drop(snapshot);
13482 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13483 s.select(selections)
13484 });
13485
13486 let selections = this.selections.all::<Point>(cx);
13487 let selections_on_single_row = selections.windows(2).all(|selections| {
13488 selections[0].start.row == selections[1].start.row
13489 && selections[0].end.row == selections[1].end.row
13490 && selections[0].start.row == selections[0].end.row
13491 });
13492 let selections_selecting = selections
13493 .iter()
13494 .any(|selection| selection.start != selection.end);
13495 let advance_downwards = action.advance_downwards
13496 && selections_on_single_row
13497 && !selections_selecting
13498 && !matches!(this.mode, EditorMode::SingleLine { .. });
13499
13500 if advance_downwards {
13501 let snapshot = this.buffer.read(cx).snapshot(cx);
13502
13503 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13504 s.move_cursors_with(|display_snapshot, display_point, _| {
13505 let mut point = display_point.to_point(display_snapshot);
13506 point.row += 1;
13507 point = snapshot.clip_point(point, Bias::Left);
13508 let display_point = point.to_display_point(display_snapshot);
13509 let goal = SelectionGoal::HorizontalPosition(
13510 display_snapshot
13511 .x_for_display_point(display_point, text_layout_details)
13512 .into(),
13513 );
13514 (display_point, goal)
13515 })
13516 });
13517 }
13518 });
13519 }
13520
13521 pub fn select_enclosing_symbol(
13522 &mut self,
13523 _: &SelectEnclosingSymbol,
13524 window: &mut Window,
13525 cx: &mut Context<Self>,
13526 ) {
13527 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13528
13529 let buffer = self.buffer.read(cx).snapshot(cx);
13530 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13531
13532 fn update_selection(
13533 selection: &Selection<usize>,
13534 buffer_snap: &MultiBufferSnapshot,
13535 ) -> Option<Selection<usize>> {
13536 let cursor = selection.head();
13537 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13538 for symbol in symbols.iter().rev() {
13539 let start = symbol.range.start.to_offset(buffer_snap);
13540 let end = symbol.range.end.to_offset(buffer_snap);
13541 let new_range = start..end;
13542 if start < selection.start || end > selection.end {
13543 return Some(Selection {
13544 id: selection.id,
13545 start: new_range.start,
13546 end: new_range.end,
13547 goal: SelectionGoal::None,
13548 reversed: selection.reversed,
13549 });
13550 }
13551 }
13552 None
13553 }
13554
13555 let mut selected_larger_symbol = false;
13556 let new_selections = old_selections
13557 .iter()
13558 .map(|selection| match update_selection(selection, &buffer) {
13559 Some(new_selection) => {
13560 if new_selection.range() != selection.range() {
13561 selected_larger_symbol = true;
13562 }
13563 new_selection
13564 }
13565 None => selection.clone(),
13566 })
13567 .collect::<Vec<_>>();
13568
13569 if selected_larger_symbol {
13570 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13571 s.select(new_selections);
13572 });
13573 }
13574 }
13575
13576 pub fn select_larger_syntax_node(
13577 &mut self,
13578 _: &SelectLargerSyntaxNode,
13579 window: &mut Window,
13580 cx: &mut Context<Self>,
13581 ) {
13582 let Some(visible_row_count) = self.visible_row_count() else {
13583 return;
13584 };
13585 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13586 if old_selections.is_empty() {
13587 return;
13588 }
13589
13590 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13591
13592 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13593 let buffer = self.buffer.read(cx).snapshot(cx);
13594
13595 let mut selected_larger_node = false;
13596 let mut new_selections = old_selections
13597 .iter()
13598 .map(|selection| {
13599 let old_range = selection.start..selection.end;
13600
13601 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13602 // manually select word at selection
13603 if ["string_content", "inline"].contains(&node.kind()) {
13604 let word_range = {
13605 let display_point = buffer
13606 .offset_to_point(old_range.start)
13607 .to_display_point(&display_map);
13608 let Range { start, end } =
13609 movement::surrounding_word(&display_map, display_point);
13610 start.to_point(&display_map).to_offset(&buffer)
13611 ..end.to_point(&display_map).to_offset(&buffer)
13612 };
13613 // ignore if word is already selected
13614 if !word_range.is_empty() && old_range != word_range {
13615 let last_word_range = {
13616 let display_point = buffer
13617 .offset_to_point(old_range.end)
13618 .to_display_point(&display_map);
13619 let Range { start, end } =
13620 movement::surrounding_word(&display_map, display_point);
13621 start.to_point(&display_map).to_offset(&buffer)
13622 ..end.to_point(&display_map).to_offset(&buffer)
13623 };
13624 // only select word if start and end point belongs to same word
13625 if word_range == last_word_range {
13626 selected_larger_node = true;
13627 return Selection {
13628 id: selection.id,
13629 start: word_range.start,
13630 end: word_range.end,
13631 goal: SelectionGoal::None,
13632 reversed: selection.reversed,
13633 };
13634 }
13635 }
13636 }
13637 }
13638
13639 let mut new_range = old_range.clone();
13640 while let Some((_node, containing_range)) =
13641 buffer.syntax_ancestor(new_range.clone())
13642 {
13643 new_range = match containing_range {
13644 MultiOrSingleBufferOffsetRange::Single(_) => break,
13645 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13646 };
13647 if !display_map.intersects_fold(new_range.start)
13648 && !display_map.intersects_fold(new_range.end)
13649 {
13650 break;
13651 }
13652 }
13653
13654 selected_larger_node |= new_range != old_range;
13655 Selection {
13656 id: selection.id,
13657 start: new_range.start,
13658 end: new_range.end,
13659 goal: SelectionGoal::None,
13660 reversed: selection.reversed,
13661 }
13662 })
13663 .collect::<Vec<_>>();
13664
13665 if !selected_larger_node {
13666 return; // don't put this call in the history
13667 }
13668
13669 // scroll based on transformation done to the last selection created by the user
13670 let (last_old, last_new) = old_selections
13671 .last()
13672 .zip(new_selections.last().cloned())
13673 .expect("old_selections isn't empty");
13674
13675 // revert selection
13676 let is_selection_reversed = {
13677 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13678 new_selections.last_mut().expect("checked above").reversed =
13679 should_newest_selection_be_reversed;
13680 should_newest_selection_be_reversed
13681 };
13682
13683 if selected_larger_node {
13684 self.select_syntax_node_history.disable_clearing = true;
13685 self.change_selections(None, window, cx, |s| {
13686 s.select(new_selections.clone());
13687 });
13688 self.select_syntax_node_history.disable_clearing = false;
13689 }
13690
13691 let start_row = last_new.start.to_display_point(&display_map).row().0;
13692 let end_row = last_new.end.to_display_point(&display_map).row().0;
13693 let selection_height = end_row - start_row + 1;
13694 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13695
13696 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13697 let scroll_behavior = if fits_on_the_screen {
13698 self.request_autoscroll(Autoscroll::fit(), cx);
13699 SelectSyntaxNodeScrollBehavior::FitSelection
13700 } else if is_selection_reversed {
13701 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13702 SelectSyntaxNodeScrollBehavior::CursorTop
13703 } else {
13704 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13705 SelectSyntaxNodeScrollBehavior::CursorBottom
13706 };
13707
13708 self.select_syntax_node_history.push((
13709 old_selections,
13710 scroll_behavior,
13711 is_selection_reversed,
13712 ));
13713 }
13714
13715 pub fn select_smaller_syntax_node(
13716 &mut self,
13717 _: &SelectSmallerSyntaxNode,
13718 window: &mut Window,
13719 cx: &mut Context<Self>,
13720 ) {
13721 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13722
13723 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13724 self.select_syntax_node_history.pop()
13725 {
13726 if let Some(selection) = selections.last_mut() {
13727 selection.reversed = is_selection_reversed;
13728 }
13729
13730 self.select_syntax_node_history.disable_clearing = true;
13731 self.change_selections(None, window, cx, |s| {
13732 s.select(selections.to_vec());
13733 });
13734 self.select_syntax_node_history.disable_clearing = false;
13735
13736 match scroll_behavior {
13737 SelectSyntaxNodeScrollBehavior::CursorTop => {
13738 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13739 }
13740 SelectSyntaxNodeScrollBehavior::FitSelection => {
13741 self.request_autoscroll(Autoscroll::fit(), cx);
13742 }
13743 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13744 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13745 }
13746 }
13747 }
13748 }
13749
13750 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13751 if !EditorSettings::get_global(cx).gutter.runnables {
13752 self.clear_tasks();
13753 return Task::ready(());
13754 }
13755 let project = self.project.as_ref().map(Entity::downgrade);
13756 let task_sources = self.lsp_task_sources(cx);
13757 let multi_buffer = self.buffer.downgrade();
13758 cx.spawn_in(window, async move |editor, cx| {
13759 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13760 let Some(project) = project.and_then(|p| p.upgrade()) else {
13761 return;
13762 };
13763 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13764 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13765 }) else {
13766 return;
13767 };
13768
13769 let hide_runnables = project
13770 .update(cx, |project, cx| {
13771 // Do not display any test indicators in non-dev server remote projects.
13772 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13773 })
13774 .unwrap_or(true);
13775 if hide_runnables {
13776 return;
13777 }
13778 let new_rows =
13779 cx.background_spawn({
13780 let snapshot = display_snapshot.clone();
13781 async move {
13782 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13783 }
13784 })
13785 .await;
13786 let Ok(lsp_tasks) =
13787 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13788 else {
13789 return;
13790 };
13791 let lsp_tasks = lsp_tasks.await;
13792
13793 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13794 lsp_tasks
13795 .into_iter()
13796 .flat_map(|(kind, tasks)| {
13797 tasks.into_iter().filter_map(move |(location, task)| {
13798 Some((kind.clone(), location?, task))
13799 })
13800 })
13801 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13802 let buffer = location.target.buffer;
13803 let buffer_snapshot = buffer.read(cx).snapshot();
13804 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13805 |(excerpt_id, snapshot, _)| {
13806 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13807 display_snapshot
13808 .buffer_snapshot
13809 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13810 } else {
13811 None
13812 }
13813 },
13814 );
13815 if let Some(offset) = offset {
13816 let task_buffer_range =
13817 location.target.range.to_point(&buffer_snapshot);
13818 let context_buffer_range =
13819 task_buffer_range.to_offset(&buffer_snapshot);
13820 let context_range = BufferOffset(context_buffer_range.start)
13821 ..BufferOffset(context_buffer_range.end);
13822
13823 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13824 .or_insert_with(|| RunnableTasks {
13825 templates: Vec::new(),
13826 offset,
13827 column: task_buffer_range.start.column,
13828 extra_variables: HashMap::default(),
13829 context_range,
13830 })
13831 .templates
13832 .push((kind, task.original_task().clone()));
13833 }
13834
13835 acc
13836 })
13837 }) else {
13838 return;
13839 };
13840
13841 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
13842 buffer.language_settings(cx).tasks.prefer_lsp
13843 }) else {
13844 return;
13845 };
13846
13847 let rows = Self::runnable_rows(
13848 project,
13849 display_snapshot,
13850 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
13851 new_rows,
13852 cx.clone(),
13853 );
13854 editor
13855 .update(cx, |editor, _| {
13856 editor.clear_tasks();
13857 for (key, mut value) in rows {
13858 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13859 value.templates.extend(lsp_tasks.templates);
13860 }
13861
13862 editor.insert_tasks(key, value);
13863 }
13864 for (key, value) in lsp_tasks_by_rows {
13865 editor.insert_tasks(key, value);
13866 }
13867 })
13868 .ok();
13869 })
13870 }
13871 fn fetch_runnable_ranges(
13872 snapshot: &DisplaySnapshot,
13873 range: Range<Anchor>,
13874 ) -> Vec<language::RunnableRange> {
13875 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13876 }
13877
13878 fn runnable_rows(
13879 project: Entity<Project>,
13880 snapshot: DisplaySnapshot,
13881 prefer_lsp: bool,
13882 runnable_ranges: Vec<RunnableRange>,
13883 mut cx: AsyncWindowContext,
13884 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13885 runnable_ranges
13886 .into_iter()
13887 .filter_map(|mut runnable| {
13888 let mut tasks = cx
13889 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13890 .ok()?;
13891 if prefer_lsp {
13892 tasks.retain(|(task_kind, _)| {
13893 !matches!(task_kind, TaskSourceKind::Language { .. })
13894 });
13895 }
13896 if tasks.is_empty() {
13897 return None;
13898 }
13899
13900 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13901
13902 let row = snapshot
13903 .buffer_snapshot
13904 .buffer_line_for_row(MultiBufferRow(point.row))?
13905 .1
13906 .start
13907 .row;
13908
13909 let context_range =
13910 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13911 Some((
13912 (runnable.buffer_id, row),
13913 RunnableTasks {
13914 templates: tasks,
13915 offset: snapshot
13916 .buffer_snapshot
13917 .anchor_before(runnable.run_range.start),
13918 context_range,
13919 column: point.column,
13920 extra_variables: runnable.extra_captures,
13921 },
13922 ))
13923 })
13924 .collect()
13925 }
13926
13927 fn templates_with_tags(
13928 project: &Entity<Project>,
13929 runnable: &mut Runnable,
13930 cx: &mut App,
13931 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13932 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13933 let (worktree_id, file) = project
13934 .buffer_for_id(runnable.buffer, cx)
13935 .and_then(|buffer| buffer.read(cx).file())
13936 .map(|file| (file.worktree_id(cx), file.clone()))
13937 .unzip();
13938
13939 (
13940 project.task_store().read(cx).task_inventory().cloned(),
13941 worktree_id,
13942 file,
13943 )
13944 });
13945
13946 let mut templates_with_tags = mem::take(&mut runnable.tags)
13947 .into_iter()
13948 .flat_map(|RunnableTag(tag)| {
13949 inventory
13950 .as_ref()
13951 .into_iter()
13952 .flat_map(|inventory| {
13953 inventory.read(cx).list_tasks(
13954 file.clone(),
13955 Some(runnable.language.clone()),
13956 worktree_id,
13957 cx,
13958 )
13959 })
13960 .filter(move |(_, template)| {
13961 template.tags.iter().any(|source_tag| source_tag == &tag)
13962 })
13963 })
13964 .sorted_by_key(|(kind, _)| kind.to_owned())
13965 .collect::<Vec<_>>();
13966 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13967 // Strongest source wins; if we have worktree tag binding, prefer that to
13968 // global and language bindings;
13969 // if we have a global binding, prefer that to language binding.
13970 let first_mismatch = templates_with_tags
13971 .iter()
13972 .position(|(tag_source, _)| tag_source != leading_tag_source);
13973 if let Some(index) = first_mismatch {
13974 templates_with_tags.truncate(index);
13975 }
13976 }
13977
13978 templates_with_tags
13979 }
13980
13981 pub fn move_to_enclosing_bracket(
13982 &mut self,
13983 _: &MoveToEnclosingBracket,
13984 window: &mut Window,
13985 cx: &mut Context<Self>,
13986 ) {
13987 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13988 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13989 s.move_offsets_with(|snapshot, selection| {
13990 let Some(enclosing_bracket_ranges) =
13991 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13992 else {
13993 return;
13994 };
13995
13996 let mut best_length = usize::MAX;
13997 let mut best_inside = false;
13998 let mut best_in_bracket_range = false;
13999 let mut best_destination = None;
14000 for (open, close) in enclosing_bracket_ranges {
14001 let close = close.to_inclusive();
14002 let length = close.end() - open.start;
14003 let inside = selection.start >= open.end && selection.end <= *close.start();
14004 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14005 || close.contains(&selection.head());
14006
14007 // If best is next to a bracket and current isn't, skip
14008 if !in_bracket_range && best_in_bracket_range {
14009 continue;
14010 }
14011
14012 // Prefer smaller lengths unless best is inside and current isn't
14013 if length > best_length && (best_inside || !inside) {
14014 continue;
14015 }
14016
14017 best_length = length;
14018 best_inside = inside;
14019 best_in_bracket_range = in_bracket_range;
14020 best_destination = Some(
14021 if close.contains(&selection.start) && close.contains(&selection.end) {
14022 if inside { open.end } else { open.start }
14023 } else if inside {
14024 *close.start()
14025 } else {
14026 *close.end()
14027 },
14028 );
14029 }
14030
14031 if let Some(destination) = best_destination {
14032 selection.collapse_to(destination, SelectionGoal::None);
14033 }
14034 })
14035 });
14036 }
14037
14038 pub fn undo_selection(
14039 &mut self,
14040 _: &UndoSelection,
14041 window: &mut Window,
14042 cx: &mut Context<Self>,
14043 ) {
14044 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14045 self.end_selection(window, cx);
14046 self.selection_history.mode = SelectionHistoryMode::Undoing;
14047 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14048 self.change_selections(None, window, cx, |s| {
14049 s.select_anchors(entry.selections.to_vec())
14050 });
14051 self.select_next_state = entry.select_next_state;
14052 self.select_prev_state = entry.select_prev_state;
14053 self.add_selections_state = entry.add_selections_state;
14054 self.request_autoscroll(Autoscroll::newest(), cx);
14055 }
14056 self.selection_history.mode = SelectionHistoryMode::Normal;
14057 }
14058
14059 pub fn redo_selection(
14060 &mut self,
14061 _: &RedoSelection,
14062 window: &mut Window,
14063 cx: &mut Context<Self>,
14064 ) {
14065 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14066 self.end_selection(window, cx);
14067 self.selection_history.mode = SelectionHistoryMode::Redoing;
14068 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14069 self.change_selections(None, window, cx, |s| {
14070 s.select_anchors(entry.selections.to_vec())
14071 });
14072 self.select_next_state = entry.select_next_state;
14073 self.select_prev_state = entry.select_prev_state;
14074 self.add_selections_state = entry.add_selections_state;
14075 self.request_autoscroll(Autoscroll::newest(), cx);
14076 }
14077 self.selection_history.mode = SelectionHistoryMode::Normal;
14078 }
14079
14080 pub fn expand_excerpts(
14081 &mut self,
14082 action: &ExpandExcerpts,
14083 _: &mut Window,
14084 cx: &mut Context<Self>,
14085 ) {
14086 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14087 }
14088
14089 pub fn expand_excerpts_down(
14090 &mut self,
14091 action: &ExpandExcerptsDown,
14092 _: &mut Window,
14093 cx: &mut Context<Self>,
14094 ) {
14095 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14096 }
14097
14098 pub fn expand_excerpts_up(
14099 &mut self,
14100 action: &ExpandExcerptsUp,
14101 _: &mut Window,
14102 cx: &mut Context<Self>,
14103 ) {
14104 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14105 }
14106
14107 pub fn expand_excerpts_for_direction(
14108 &mut self,
14109 lines: u32,
14110 direction: ExpandExcerptDirection,
14111
14112 cx: &mut Context<Self>,
14113 ) {
14114 let selections = self.selections.disjoint_anchors();
14115
14116 let lines = if lines == 0 {
14117 EditorSettings::get_global(cx).expand_excerpt_lines
14118 } else {
14119 lines
14120 };
14121
14122 self.buffer.update(cx, |buffer, cx| {
14123 let snapshot = buffer.snapshot(cx);
14124 let mut excerpt_ids = selections
14125 .iter()
14126 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14127 .collect::<Vec<_>>();
14128 excerpt_ids.sort();
14129 excerpt_ids.dedup();
14130 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14131 })
14132 }
14133
14134 pub fn expand_excerpt(
14135 &mut self,
14136 excerpt: ExcerptId,
14137 direction: ExpandExcerptDirection,
14138 window: &mut Window,
14139 cx: &mut Context<Self>,
14140 ) {
14141 let current_scroll_position = self.scroll_position(cx);
14142 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14143 let mut should_scroll_up = false;
14144
14145 if direction == ExpandExcerptDirection::Down {
14146 let multi_buffer = self.buffer.read(cx);
14147 let snapshot = multi_buffer.snapshot(cx);
14148 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14149 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14150 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14151 let buffer_snapshot = buffer.read(cx).snapshot();
14152 let excerpt_end_row =
14153 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14154 let last_row = buffer_snapshot.max_point().row;
14155 let lines_below = last_row.saturating_sub(excerpt_end_row);
14156 should_scroll_up = lines_below >= lines_to_expand;
14157 }
14158 }
14159 }
14160 }
14161
14162 self.buffer.update(cx, |buffer, cx| {
14163 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14164 });
14165
14166 if should_scroll_up {
14167 let new_scroll_position =
14168 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14169 self.set_scroll_position(new_scroll_position, window, cx);
14170 }
14171 }
14172
14173 pub fn go_to_singleton_buffer_point(
14174 &mut self,
14175 point: Point,
14176 window: &mut Window,
14177 cx: &mut Context<Self>,
14178 ) {
14179 self.go_to_singleton_buffer_range(point..point, window, cx);
14180 }
14181
14182 pub fn go_to_singleton_buffer_range(
14183 &mut self,
14184 range: Range<Point>,
14185 window: &mut Window,
14186 cx: &mut Context<Self>,
14187 ) {
14188 let multibuffer = self.buffer().read(cx);
14189 let Some(buffer) = multibuffer.as_singleton() else {
14190 return;
14191 };
14192 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14193 return;
14194 };
14195 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14196 return;
14197 };
14198 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14199 s.select_anchor_ranges([start..end])
14200 });
14201 }
14202
14203 pub fn go_to_diagnostic(
14204 &mut self,
14205 _: &GoToDiagnostic,
14206 window: &mut Window,
14207 cx: &mut Context<Self>,
14208 ) {
14209 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14210 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14211 }
14212
14213 pub fn go_to_prev_diagnostic(
14214 &mut self,
14215 _: &GoToPreviousDiagnostic,
14216 window: &mut Window,
14217 cx: &mut Context<Self>,
14218 ) {
14219 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14220 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14221 }
14222
14223 pub fn go_to_diagnostic_impl(
14224 &mut self,
14225 direction: Direction,
14226 window: &mut Window,
14227 cx: &mut Context<Self>,
14228 ) {
14229 let buffer = self.buffer.read(cx).snapshot(cx);
14230 let selection = self.selections.newest::<usize>(cx);
14231
14232 let mut active_group_id = None;
14233 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14234 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14235 active_group_id = Some(active_group.group_id);
14236 }
14237 }
14238
14239 fn filtered(
14240 snapshot: EditorSnapshot,
14241 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14242 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14243 diagnostics
14244 .filter(|entry| entry.range.start != entry.range.end)
14245 .filter(|entry| !entry.diagnostic.is_unnecessary)
14246 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14247 }
14248
14249 let snapshot = self.snapshot(window, cx);
14250 let before = filtered(
14251 snapshot.clone(),
14252 buffer
14253 .diagnostics_in_range(0..selection.start)
14254 .filter(|entry| entry.range.start <= selection.start),
14255 );
14256 let after = filtered(
14257 snapshot,
14258 buffer
14259 .diagnostics_in_range(selection.start..buffer.len())
14260 .filter(|entry| entry.range.start >= selection.start),
14261 );
14262
14263 let mut found: Option<DiagnosticEntry<usize>> = None;
14264 if direction == Direction::Prev {
14265 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14266 {
14267 for diagnostic in prev_diagnostics.into_iter().rev() {
14268 if diagnostic.range.start != selection.start
14269 || active_group_id
14270 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14271 {
14272 found = Some(diagnostic);
14273 break 'outer;
14274 }
14275 }
14276 }
14277 } else {
14278 for diagnostic in after.chain(before) {
14279 if diagnostic.range.start != selection.start
14280 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14281 {
14282 found = Some(diagnostic);
14283 break;
14284 }
14285 }
14286 }
14287 let Some(next_diagnostic) = found else {
14288 return;
14289 };
14290
14291 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14292 return;
14293 };
14294 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14295 s.select_ranges(vec![
14296 next_diagnostic.range.start..next_diagnostic.range.start,
14297 ])
14298 });
14299 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14300 self.refresh_inline_completion(false, true, window, cx);
14301 }
14302
14303 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14304 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14305 let snapshot = self.snapshot(window, cx);
14306 let selection = self.selections.newest::<Point>(cx);
14307 self.go_to_hunk_before_or_after_position(
14308 &snapshot,
14309 selection.head(),
14310 Direction::Next,
14311 window,
14312 cx,
14313 );
14314 }
14315
14316 pub fn go_to_hunk_before_or_after_position(
14317 &mut self,
14318 snapshot: &EditorSnapshot,
14319 position: Point,
14320 direction: Direction,
14321 window: &mut Window,
14322 cx: &mut Context<Editor>,
14323 ) {
14324 let row = if direction == Direction::Next {
14325 self.hunk_after_position(snapshot, position)
14326 .map(|hunk| hunk.row_range.start)
14327 } else {
14328 self.hunk_before_position(snapshot, position)
14329 };
14330
14331 if let Some(row) = row {
14332 let destination = Point::new(row.0, 0);
14333 let autoscroll = Autoscroll::center();
14334
14335 self.unfold_ranges(&[destination..destination], false, false, cx);
14336 self.change_selections(Some(autoscroll), window, cx, |s| {
14337 s.select_ranges([destination..destination]);
14338 });
14339 }
14340 }
14341
14342 fn hunk_after_position(
14343 &mut self,
14344 snapshot: &EditorSnapshot,
14345 position: Point,
14346 ) -> Option<MultiBufferDiffHunk> {
14347 snapshot
14348 .buffer_snapshot
14349 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14350 .find(|hunk| hunk.row_range.start.0 > position.row)
14351 .or_else(|| {
14352 snapshot
14353 .buffer_snapshot
14354 .diff_hunks_in_range(Point::zero()..position)
14355 .find(|hunk| hunk.row_range.end.0 < position.row)
14356 })
14357 }
14358
14359 fn go_to_prev_hunk(
14360 &mut self,
14361 _: &GoToPreviousHunk,
14362 window: &mut Window,
14363 cx: &mut Context<Self>,
14364 ) {
14365 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14366 let snapshot = self.snapshot(window, cx);
14367 let selection = self.selections.newest::<Point>(cx);
14368 self.go_to_hunk_before_or_after_position(
14369 &snapshot,
14370 selection.head(),
14371 Direction::Prev,
14372 window,
14373 cx,
14374 );
14375 }
14376
14377 fn hunk_before_position(
14378 &mut self,
14379 snapshot: &EditorSnapshot,
14380 position: Point,
14381 ) -> Option<MultiBufferRow> {
14382 snapshot
14383 .buffer_snapshot
14384 .diff_hunk_before(position)
14385 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14386 }
14387
14388 fn go_to_next_change(
14389 &mut self,
14390 _: &GoToNextChange,
14391 window: &mut Window,
14392 cx: &mut Context<Self>,
14393 ) {
14394 if let Some(selections) = self
14395 .change_list
14396 .next_change(1, Direction::Next)
14397 .map(|s| s.to_vec())
14398 {
14399 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14400 let map = s.display_map();
14401 s.select_display_ranges(selections.iter().map(|a| {
14402 let point = a.to_display_point(&map);
14403 point..point
14404 }))
14405 })
14406 }
14407 }
14408
14409 fn go_to_previous_change(
14410 &mut self,
14411 _: &GoToPreviousChange,
14412 window: &mut Window,
14413 cx: &mut Context<Self>,
14414 ) {
14415 if let Some(selections) = self
14416 .change_list
14417 .next_change(1, Direction::Prev)
14418 .map(|s| s.to_vec())
14419 {
14420 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14421 let map = s.display_map();
14422 s.select_display_ranges(selections.iter().map(|a| {
14423 let point = a.to_display_point(&map);
14424 point..point
14425 }))
14426 })
14427 }
14428 }
14429
14430 fn go_to_line<T: 'static>(
14431 &mut self,
14432 position: Anchor,
14433 highlight_color: Option<Hsla>,
14434 window: &mut Window,
14435 cx: &mut Context<Self>,
14436 ) {
14437 let snapshot = self.snapshot(window, cx).display_snapshot;
14438 let position = position.to_point(&snapshot.buffer_snapshot);
14439 let start = snapshot
14440 .buffer_snapshot
14441 .clip_point(Point::new(position.row, 0), Bias::Left);
14442 let end = start + Point::new(1, 0);
14443 let start = snapshot.buffer_snapshot.anchor_before(start);
14444 let end = snapshot.buffer_snapshot.anchor_before(end);
14445
14446 self.highlight_rows::<T>(
14447 start..end,
14448 highlight_color
14449 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14450 Default::default(),
14451 cx,
14452 );
14453
14454 if self.buffer.read(cx).is_singleton() {
14455 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14456 }
14457 }
14458
14459 pub fn go_to_definition(
14460 &mut self,
14461 _: &GoToDefinition,
14462 window: &mut Window,
14463 cx: &mut Context<Self>,
14464 ) -> Task<Result<Navigated>> {
14465 let definition =
14466 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14467 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14468 cx.spawn_in(window, async move |editor, cx| {
14469 if definition.await? == Navigated::Yes {
14470 return Ok(Navigated::Yes);
14471 }
14472 match fallback_strategy {
14473 GoToDefinitionFallback::None => Ok(Navigated::No),
14474 GoToDefinitionFallback::FindAllReferences => {
14475 match editor.update_in(cx, |editor, window, cx| {
14476 editor.find_all_references(&FindAllReferences, window, cx)
14477 })? {
14478 Some(references) => references.await,
14479 None => Ok(Navigated::No),
14480 }
14481 }
14482 }
14483 })
14484 }
14485
14486 pub fn go_to_declaration(
14487 &mut self,
14488 _: &GoToDeclaration,
14489 window: &mut Window,
14490 cx: &mut Context<Self>,
14491 ) -> Task<Result<Navigated>> {
14492 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14493 }
14494
14495 pub fn go_to_declaration_split(
14496 &mut self,
14497 _: &GoToDeclaration,
14498 window: &mut Window,
14499 cx: &mut Context<Self>,
14500 ) -> Task<Result<Navigated>> {
14501 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14502 }
14503
14504 pub fn go_to_implementation(
14505 &mut self,
14506 _: &GoToImplementation,
14507 window: &mut Window,
14508 cx: &mut Context<Self>,
14509 ) -> Task<Result<Navigated>> {
14510 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14511 }
14512
14513 pub fn go_to_implementation_split(
14514 &mut self,
14515 _: &GoToImplementationSplit,
14516 window: &mut Window,
14517 cx: &mut Context<Self>,
14518 ) -> Task<Result<Navigated>> {
14519 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14520 }
14521
14522 pub fn go_to_type_definition(
14523 &mut self,
14524 _: &GoToTypeDefinition,
14525 window: &mut Window,
14526 cx: &mut Context<Self>,
14527 ) -> Task<Result<Navigated>> {
14528 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14529 }
14530
14531 pub fn go_to_definition_split(
14532 &mut self,
14533 _: &GoToDefinitionSplit,
14534 window: &mut Window,
14535 cx: &mut Context<Self>,
14536 ) -> Task<Result<Navigated>> {
14537 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14538 }
14539
14540 pub fn go_to_type_definition_split(
14541 &mut self,
14542 _: &GoToTypeDefinitionSplit,
14543 window: &mut Window,
14544 cx: &mut Context<Self>,
14545 ) -> Task<Result<Navigated>> {
14546 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14547 }
14548
14549 fn go_to_definition_of_kind(
14550 &mut self,
14551 kind: GotoDefinitionKind,
14552 split: bool,
14553 window: &mut Window,
14554 cx: &mut Context<Self>,
14555 ) -> Task<Result<Navigated>> {
14556 let Some(provider) = self.semantics_provider.clone() else {
14557 return Task::ready(Ok(Navigated::No));
14558 };
14559 let head = self.selections.newest::<usize>(cx).head();
14560 let buffer = self.buffer.read(cx);
14561 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14562 text_anchor
14563 } else {
14564 return Task::ready(Ok(Navigated::No));
14565 };
14566
14567 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14568 return Task::ready(Ok(Navigated::No));
14569 };
14570
14571 cx.spawn_in(window, async move |editor, cx| {
14572 let definitions = definitions.await?;
14573 let navigated = editor
14574 .update_in(cx, |editor, window, cx| {
14575 editor.navigate_to_hover_links(
14576 Some(kind),
14577 definitions
14578 .into_iter()
14579 .filter(|location| {
14580 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14581 })
14582 .map(HoverLink::Text)
14583 .collect::<Vec<_>>(),
14584 split,
14585 window,
14586 cx,
14587 )
14588 })?
14589 .await?;
14590 anyhow::Ok(navigated)
14591 })
14592 }
14593
14594 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14595 let selection = self.selections.newest_anchor();
14596 let head = selection.head();
14597 let tail = selection.tail();
14598
14599 let Some((buffer, start_position)) =
14600 self.buffer.read(cx).text_anchor_for_position(head, cx)
14601 else {
14602 return;
14603 };
14604
14605 let end_position = if head != tail {
14606 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14607 return;
14608 };
14609 Some(pos)
14610 } else {
14611 None
14612 };
14613
14614 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14615 let url = if let Some(end_pos) = end_position {
14616 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14617 } else {
14618 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14619 };
14620
14621 if let Some(url) = url {
14622 editor.update(cx, |_, cx| {
14623 cx.open_url(&url);
14624 })
14625 } else {
14626 Ok(())
14627 }
14628 });
14629
14630 url_finder.detach();
14631 }
14632
14633 pub fn open_selected_filename(
14634 &mut self,
14635 _: &OpenSelectedFilename,
14636 window: &mut Window,
14637 cx: &mut Context<Self>,
14638 ) {
14639 let Some(workspace) = self.workspace() else {
14640 return;
14641 };
14642
14643 let position = self.selections.newest_anchor().head();
14644
14645 let Some((buffer, buffer_position)) =
14646 self.buffer.read(cx).text_anchor_for_position(position, cx)
14647 else {
14648 return;
14649 };
14650
14651 let project = self.project.clone();
14652
14653 cx.spawn_in(window, async move |_, cx| {
14654 let result = find_file(&buffer, project, buffer_position, cx).await;
14655
14656 if let Some((_, path)) = result {
14657 workspace
14658 .update_in(cx, |workspace, window, cx| {
14659 workspace.open_resolved_path(path, window, cx)
14660 })?
14661 .await?;
14662 }
14663 anyhow::Ok(())
14664 })
14665 .detach();
14666 }
14667
14668 pub(crate) fn navigate_to_hover_links(
14669 &mut self,
14670 kind: Option<GotoDefinitionKind>,
14671 mut definitions: Vec<HoverLink>,
14672 split: bool,
14673 window: &mut Window,
14674 cx: &mut Context<Editor>,
14675 ) -> Task<Result<Navigated>> {
14676 // If there is one definition, just open it directly
14677 if definitions.len() == 1 {
14678 let definition = definitions.pop().unwrap();
14679
14680 enum TargetTaskResult {
14681 Location(Option<Location>),
14682 AlreadyNavigated,
14683 }
14684
14685 let target_task = match definition {
14686 HoverLink::Text(link) => {
14687 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14688 }
14689 HoverLink::InlayHint(lsp_location, server_id) => {
14690 let computation =
14691 self.compute_target_location(lsp_location, server_id, window, cx);
14692 cx.background_spawn(async move {
14693 let location = computation.await?;
14694 Ok(TargetTaskResult::Location(location))
14695 })
14696 }
14697 HoverLink::Url(url) => {
14698 cx.open_url(&url);
14699 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14700 }
14701 HoverLink::File(path) => {
14702 if let Some(workspace) = self.workspace() {
14703 cx.spawn_in(window, async move |_, cx| {
14704 workspace
14705 .update_in(cx, |workspace, window, cx| {
14706 workspace.open_resolved_path(path, window, cx)
14707 })?
14708 .await
14709 .map(|_| TargetTaskResult::AlreadyNavigated)
14710 })
14711 } else {
14712 Task::ready(Ok(TargetTaskResult::Location(None)))
14713 }
14714 }
14715 };
14716 cx.spawn_in(window, async move |editor, cx| {
14717 let target = match target_task.await.context("target resolution task")? {
14718 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14719 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14720 TargetTaskResult::Location(Some(target)) => target,
14721 };
14722
14723 editor.update_in(cx, |editor, window, cx| {
14724 let Some(workspace) = editor.workspace() else {
14725 return Navigated::No;
14726 };
14727 let pane = workspace.read(cx).active_pane().clone();
14728
14729 let range = target.range.to_point(target.buffer.read(cx));
14730 let range = editor.range_for_match(&range);
14731 let range = collapse_multiline_range(range);
14732
14733 if !split
14734 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14735 {
14736 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14737 } else {
14738 window.defer(cx, move |window, cx| {
14739 let target_editor: Entity<Self> =
14740 workspace.update(cx, |workspace, cx| {
14741 let pane = if split {
14742 workspace.adjacent_pane(window, cx)
14743 } else {
14744 workspace.active_pane().clone()
14745 };
14746
14747 workspace.open_project_item(
14748 pane,
14749 target.buffer.clone(),
14750 true,
14751 true,
14752 window,
14753 cx,
14754 )
14755 });
14756 target_editor.update(cx, |target_editor, cx| {
14757 // When selecting a definition in a different buffer, disable the nav history
14758 // to avoid creating a history entry at the previous cursor location.
14759 pane.update(cx, |pane, _| pane.disable_history());
14760 target_editor.go_to_singleton_buffer_range(range, window, cx);
14761 pane.update(cx, |pane, _| pane.enable_history());
14762 });
14763 });
14764 }
14765 Navigated::Yes
14766 })
14767 })
14768 } else if !definitions.is_empty() {
14769 cx.spawn_in(window, async move |editor, cx| {
14770 let (title, location_tasks, workspace) = editor
14771 .update_in(cx, |editor, window, cx| {
14772 let tab_kind = match kind {
14773 Some(GotoDefinitionKind::Implementation) => "Implementations",
14774 _ => "Definitions",
14775 };
14776 let title = definitions
14777 .iter()
14778 .find_map(|definition| match definition {
14779 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14780 let buffer = origin.buffer.read(cx);
14781 format!(
14782 "{} for {}",
14783 tab_kind,
14784 buffer
14785 .text_for_range(origin.range.clone())
14786 .collect::<String>()
14787 )
14788 }),
14789 HoverLink::InlayHint(_, _) => None,
14790 HoverLink::Url(_) => None,
14791 HoverLink::File(_) => None,
14792 })
14793 .unwrap_or(tab_kind.to_string());
14794 let location_tasks = definitions
14795 .into_iter()
14796 .map(|definition| match definition {
14797 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14798 HoverLink::InlayHint(lsp_location, server_id) => editor
14799 .compute_target_location(lsp_location, server_id, window, cx),
14800 HoverLink::Url(_) => Task::ready(Ok(None)),
14801 HoverLink::File(_) => Task::ready(Ok(None)),
14802 })
14803 .collect::<Vec<_>>();
14804 (title, location_tasks, editor.workspace().clone())
14805 })
14806 .context("location tasks preparation")?;
14807
14808 let locations = future::join_all(location_tasks)
14809 .await
14810 .into_iter()
14811 .filter_map(|location| location.transpose())
14812 .collect::<Result<_>>()
14813 .context("location tasks")?;
14814
14815 let Some(workspace) = workspace else {
14816 return Ok(Navigated::No);
14817 };
14818 let opened = workspace
14819 .update_in(cx, |workspace, window, cx| {
14820 Self::open_locations_in_multibuffer(
14821 workspace,
14822 locations,
14823 title,
14824 split,
14825 MultibufferSelectionMode::First,
14826 window,
14827 cx,
14828 )
14829 })
14830 .ok();
14831
14832 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14833 })
14834 } else {
14835 Task::ready(Ok(Navigated::No))
14836 }
14837 }
14838
14839 fn compute_target_location(
14840 &self,
14841 lsp_location: lsp::Location,
14842 server_id: LanguageServerId,
14843 window: &mut Window,
14844 cx: &mut Context<Self>,
14845 ) -> Task<anyhow::Result<Option<Location>>> {
14846 let Some(project) = self.project.clone() else {
14847 return Task::ready(Ok(None));
14848 };
14849
14850 cx.spawn_in(window, async move |editor, cx| {
14851 let location_task = editor.update(cx, |_, cx| {
14852 project.update(cx, |project, cx| {
14853 let language_server_name = project
14854 .language_server_statuses(cx)
14855 .find(|(id, _)| server_id == *id)
14856 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14857 language_server_name.map(|language_server_name| {
14858 project.open_local_buffer_via_lsp(
14859 lsp_location.uri.clone(),
14860 server_id,
14861 language_server_name,
14862 cx,
14863 )
14864 })
14865 })
14866 })?;
14867 let location = match location_task {
14868 Some(task) => Some({
14869 let target_buffer_handle = task.await.context("open local buffer")?;
14870 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
14871 let target_start = target_buffer
14872 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14873 let target_end = target_buffer
14874 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14875 target_buffer.anchor_after(target_start)
14876 ..target_buffer.anchor_before(target_end)
14877 })?;
14878 Location {
14879 buffer: target_buffer_handle,
14880 range,
14881 }
14882 }),
14883 None => None,
14884 };
14885 Ok(location)
14886 })
14887 }
14888
14889 pub fn find_all_references(
14890 &mut self,
14891 _: &FindAllReferences,
14892 window: &mut Window,
14893 cx: &mut Context<Self>,
14894 ) -> Option<Task<Result<Navigated>>> {
14895 let selection = self.selections.newest::<usize>(cx);
14896 let multi_buffer = self.buffer.read(cx);
14897 let head = selection.head();
14898
14899 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14900 let head_anchor = multi_buffer_snapshot.anchor_at(
14901 head,
14902 if head < selection.tail() {
14903 Bias::Right
14904 } else {
14905 Bias::Left
14906 },
14907 );
14908
14909 match self
14910 .find_all_references_task_sources
14911 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14912 {
14913 Ok(_) => {
14914 log::info!(
14915 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14916 );
14917 return None;
14918 }
14919 Err(i) => {
14920 self.find_all_references_task_sources.insert(i, head_anchor);
14921 }
14922 }
14923
14924 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14925 let workspace = self.workspace()?;
14926 let project = workspace.read(cx).project().clone();
14927 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14928 Some(cx.spawn_in(window, async move |editor, cx| {
14929 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14930 if let Ok(i) = editor
14931 .find_all_references_task_sources
14932 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14933 {
14934 editor.find_all_references_task_sources.remove(i);
14935 }
14936 });
14937
14938 let locations = references.await?;
14939 if locations.is_empty() {
14940 return anyhow::Ok(Navigated::No);
14941 }
14942
14943 workspace.update_in(cx, |workspace, window, cx| {
14944 let title = locations
14945 .first()
14946 .as_ref()
14947 .map(|location| {
14948 let buffer = location.buffer.read(cx);
14949 format!(
14950 "References to `{}`",
14951 buffer
14952 .text_for_range(location.range.clone())
14953 .collect::<String>()
14954 )
14955 })
14956 .unwrap();
14957 Self::open_locations_in_multibuffer(
14958 workspace,
14959 locations,
14960 title,
14961 false,
14962 MultibufferSelectionMode::First,
14963 window,
14964 cx,
14965 );
14966 Navigated::Yes
14967 })
14968 }))
14969 }
14970
14971 /// Opens a multibuffer with the given project locations in it
14972 pub fn open_locations_in_multibuffer(
14973 workspace: &mut Workspace,
14974 mut locations: Vec<Location>,
14975 title: String,
14976 split: bool,
14977 multibuffer_selection_mode: MultibufferSelectionMode,
14978 window: &mut Window,
14979 cx: &mut Context<Workspace>,
14980 ) {
14981 // If there are multiple definitions, open them in a multibuffer
14982 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14983 let mut locations = locations.into_iter().peekable();
14984 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14985 let capability = workspace.project().read(cx).capability();
14986
14987 let excerpt_buffer = cx.new(|cx| {
14988 let mut multibuffer = MultiBuffer::new(capability);
14989 while let Some(location) = locations.next() {
14990 let buffer = location.buffer.read(cx);
14991 let mut ranges_for_buffer = Vec::new();
14992 let range = location.range.to_point(buffer);
14993 ranges_for_buffer.push(range.clone());
14994
14995 while let Some(next_location) = locations.peek() {
14996 if next_location.buffer == location.buffer {
14997 ranges_for_buffer.push(next_location.range.to_point(buffer));
14998 locations.next();
14999 } else {
15000 break;
15001 }
15002 }
15003
15004 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15005 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15006 PathKey::for_buffer(&location.buffer, cx),
15007 location.buffer.clone(),
15008 ranges_for_buffer,
15009 DEFAULT_MULTIBUFFER_CONTEXT,
15010 cx,
15011 );
15012 ranges.extend(new_ranges)
15013 }
15014
15015 multibuffer.with_title(title)
15016 });
15017
15018 let editor = cx.new(|cx| {
15019 Editor::for_multibuffer(
15020 excerpt_buffer,
15021 Some(workspace.project().clone()),
15022 window,
15023 cx,
15024 )
15025 });
15026 editor.update(cx, |editor, cx| {
15027 match multibuffer_selection_mode {
15028 MultibufferSelectionMode::First => {
15029 if let Some(first_range) = ranges.first() {
15030 editor.change_selections(None, window, cx, |selections| {
15031 selections.clear_disjoint();
15032 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15033 });
15034 }
15035 editor.highlight_background::<Self>(
15036 &ranges,
15037 |theme| theme.editor_highlighted_line_background,
15038 cx,
15039 );
15040 }
15041 MultibufferSelectionMode::All => {
15042 editor.change_selections(None, window, cx, |selections| {
15043 selections.clear_disjoint();
15044 selections.select_anchor_ranges(ranges);
15045 });
15046 }
15047 }
15048 editor.register_buffers_with_language_servers(cx);
15049 });
15050
15051 let item = Box::new(editor);
15052 let item_id = item.item_id();
15053
15054 if split {
15055 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15056 } else {
15057 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15058 let (preview_item_id, preview_item_idx) =
15059 workspace.active_pane().read_with(cx, |pane, _| {
15060 (pane.preview_item_id(), pane.preview_item_idx())
15061 });
15062
15063 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15064
15065 if let Some(preview_item_id) = preview_item_id {
15066 workspace.active_pane().update(cx, |pane, cx| {
15067 pane.remove_item(preview_item_id, false, false, window, cx);
15068 });
15069 }
15070 } else {
15071 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15072 }
15073 }
15074 workspace.active_pane().update(cx, |pane, cx| {
15075 pane.set_preview_item_id(Some(item_id), cx);
15076 });
15077 }
15078
15079 pub fn rename(
15080 &mut self,
15081 _: &Rename,
15082 window: &mut Window,
15083 cx: &mut Context<Self>,
15084 ) -> Option<Task<Result<()>>> {
15085 use language::ToOffset as _;
15086
15087 let provider = self.semantics_provider.clone()?;
15088 let selection = self.selections.newest_anchor().clone();
15089 let (cursor_buffer, cursor_buffer_position) = self
15090 .buffer
15091 .read(cx)
15092 .text_anchor_for_position(selection.head(), cx)?;
15093 let (tail_buffer, cursor_buffer_position_end) = self
15094 .buffer
15095 .read(cx)
15096 .text_anchor_for_position(selection.tail(), cx)?;
15097 if tail_buffer != cursor_buffer {
15098 return None;
15099 }
15100
15101 let snapshot = cursor_buffer.read(cx).snapshot();
15102 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15103 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15104 let prepare_rename = provider
15105 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15106 .unwrap_or_else(|| Task::ready(Ok(None)));
15107 drop(snapshot);
15108
15109 Some(cx.spawn_in(window, async move |this, cx| {
15110 let rename_range = if let Some(range) = prepare_rename.await? {
15111 Some(range)
15112 } else {
15113 this.update(cx, |this, cx| {
15114 let buffer = this.buffer.read(cx).snapshot(cx);
15115 let mut buffer_highlights = this
15116 .document_highlights_for_position(selection.head(), &buffer)
15117 .filter(|highlight| {
15118 highlight.start.excerpt_id == selection.head().excerpt_id
15119 && highlight.end.excerpt_id == selection.head().excerpt_id
15120 });
15121 buffer_highlights
15122 .next()
15123 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15124 })?
15125 };
15126 if let Some(rename_range) = rename_range {
15127 this.update_in(cx, |this, window, cx| {
15128 let snapshot = cursor_buffer.read(cx).snapshot();
15129 let rename_buffer_range = rename_range.to_offset(&snapshot);
15130 let cursor_offset_in_rename_range =
15131 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15132 let cursor_offset_in_rename_range_end =
15133 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15134
15135 this.take_rename(false, window, cx);
15136 let buffer = this.buffer.read(cx).read(cx);
15137 let cursor_offset = selection.head().to_offset(&buffer);
15138 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15139 let rename_end = rename_start + rename_buffer_range.len();
15140 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15141 let mut old_highlight_id = None;
15142 let old_name: Arc<str> = buffer
15143 .chunks(rename_start..rename_end, true)
15144 .map(|chunk| {
15145 if old_highlight_id.is_none() {
15146 old_highlight_id = chunk.syntax_highlight_id;
15147 }
15148 chunk.text
15149 })
15150 .collect::<String>()
15151 .into();
15152
15153 drop(buffer);
15154
15155 // Position the selection in the rename editor so that it matches the current selection.
15156 this.show_local_selections = false;
15157 let rename_editor = cx.new(|cx| {
15158 let mut editor = Editor::single_line(window, cx);
15159 editor.buffer.update(cx, |buffer, cx| {
15160 buffer.edit([(0..0, old_name.clone())], None, cx)
15161 });
15162 let rename_selection_range = match cursor_offset_in_rename_range
15163 .cmp(&cursor_offset_in_rename_range_end)
15164 {
15165 Ordering::Equal => {
15166 editor.select_all(&SelectAll, window, cx);
15167 return editor;
15168 }
15169 Ordering::Less => {
15170 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15171 }
15172 Ordering::Greater => {
15173 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15174 }
15175 };
15176 if rename_selection_range.end > old_name.len() {
15177 editor.select_all(&SelectAll, window, cx);
15178 } else {
15179 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15180 s.select_ranges([rename_selection_range]);
15181 });
15182 }
15183 editor
15184 });
15185 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15186 if e == &EditorEvent::Focused {
15187 cx.emit(EditorEvent::FocusedIn)
15188 }
15189 })
15190 .detach();
15191
15192 let write_highlights =
15193 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15194 let read_highlights =
15195 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15196 let ranges = write_highlights
15197 .iter()
15198 .flat_map(|(_, ranges)| ranges.iter())
15199 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15200 .cloned()
15201 .collect();
15202
15203 this.highlight_text::<Rename>(
15204 ranges,
15205 HighlightStyle {
15206 fade_out: Some(0.6),
15207 ..Default::default()
15208 },
15209 cx,
15210 );
15211 let rename_focus_handle = rename_editor.focus_handle(cx);
15212 window.focus(&rename_focus_handle);
15213 let block_id = this.insert_blocks(
15214 [BlockProperties {
15215 style: BlockStyle::Flex,
15216 placement: BlockPlacement::Below(range.start),
15217 height: Some(1),
15218 render: Arc::new({
15219 let rename_editor = rename_editor.clone();
15220 move |cx: &mut BlockContext| {
15221 let mut text_style = cx.editor_style.text.clone();
15222 if let Some(highlight_style) = old_highlight_id
15223 .and_then(|h| h.style(&cx.editor_style.syntax))
15224 {
15225 text_style = text_style.highlight(highlight_style);
15226 }
15227 div()
15228 .block_mouse_except_scroll()
15229 .pl(cx.anchor_x)
15230 .child(EditorElement::new(
15231 &rename_editor,
15232 EditorStyle {
15233 background: cx.theme().system().transparent,
15234 local_player: cx.editor_style.local_player,
15235 text: text_style,
15236 scrollbar_width: cx.editor_style.scrollbar_width,
15237 syntax: cx.editor_style.syntax.clone(),
15238 status: cx.editor_style.status.clone(),
15239 inlay_hints_style: HighlightStyle {
15240 font_weight: Some(FontWeight::BOLD),
15241 ..make_inlay_hints_style(cx.app)
15242 },
15243 inline_completion_styles: make_suggestion_styles(
15244 cx.app,
15245 ),
15246 ..EditorStyle::default()
15247 },
15248 ))
15249 .into_any_element()
15250 }
15251 }),
15252 priority: 0,
15253 render_in_minimap: true,
15254 }],
15255 Some(Autoscroll::fit()),
15256 cx,
15257 )[0];
15258 this.pending_rename = Some(RenameState {
15259 range,
15260 old_name,
15261 editor: rename_editor,
15262 block_id,
15263 });
15264 })?;
15265 }
15266
15267 Ok(())
15268 }))
15269 }
15270
15271 pub fn confirm_rename(
15272 &mut self,
15273 _: &ConfirmRename,
15274 window: &mut Window,
15275 cx: &mut Context<Self>,
15276 ) -> Option<Task<Result<()>>> {
15277 let rename = self.take_rename(false, window, cx)?;
15278 let workspace = self.workspace()?.downgrade();
15279 let (buffer, start) = self
15280 .buffer
15281 .read(cx)
15282 .text_anchor_for_position(rename.range.start, cx)?;
15283 let (end_buffer, _) = self
15284 .buffer
15285 .read(cx)
15286 .text_anchor_for_position(rename.range.end, cx)?;
15287 if buffer != end_buffer {
15288 return None;
15289 }
15290
15291 let old_name = rename.old_name;
15292 let new_name = rename.editor.read(cx).text(cx);
15293
15294 let rename = self.semantics_provider.as_ref()?.perform_rename(
15295 &buffer,
15296 start,
15297 new_name.clone(),
15298 cx,
15299 )?;
15300
15301 Some(cx.spawn_in(window, async move |editor, cx| {
15302 let project_transaction = rename.await?;
15303 Self::open_project_transaction(
15304 &editor,
15305 workspace,
15306 project_transaction,
15307 format!("Rename: {} → {}", old_name, new_name),
15308 cx,
15309 )
15310 .await?;
15311
15312 editor.update(cx, |editor, cx| {
15313 editor.refresh_document_highlights(cx);
15314 })?;
15315 Ok(())
15316 }))
15317 }
15318
15319 fn take_rename(
15320 &mut self,
15321 moving_cursor: bool,
15322 window: &mut Window,
15323 cx: &mut Context<Self>,
15324 ) -> Option<RenameState> {
15325 let rename = self.pending_rename.take()?;
15326 if rename.editor.focus_handle(cx).is_focused(window) {
15327 window.focus(&self.focus_handle);
15328 }
15329
15330 self.remove_blocks(
15331 [rename.block_id].into_iter().collect(),
15332 Some(Autoscroll::fit()),
15333 cx,
15334 );
15335 self.clear_highlights::<Rename>(cx);
15336 self.show_local_selections = true;
15337
15338 if moving_cursor {
15339 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15340 editor.selections.newest::<usize>(cx).head()
15341 });
15342
15343 // Update the selection to match the position of the selection inside
15344 // the rename editor.
15345 let snapshot = self.buffer.read(cx).read(cx);
15346 let rename_range = rename.range.to_offset(&snapshot);
15347 let cursor_in_editor = snapshot
15348 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15349 .min(rename_range.end);
15350 drop(snapshot);
15351
15352 self.change_selections(None, window, cx, |s| {
15353 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15354 });
15355 } else {
15356 self.refresh_document_highlights(cx);
15357 }
15358
15359 Some(rename)
15360 }
15361
15362 pub fn pending_rename(&self) -> Option<&RenameState> {
15363 self.pending_rename.as_ref()
15364 }
15365
15366 fn format(
15367 &mut self,
15368 _: &Format,
15369 window: &mut Window,
15370 cx: &mut Context<Self>,
15371 ) -> Option<Task<Result<()>>> {
15372 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15373
15374 let project = match &self.project {
15375 Some(project) => project.clone(),
15376 None => return None,
15377 };
15378
15379 Some(self.perform_format(
15380 project,
15381 FormatTrigger::Manual,
15382 FormatTarget::Buffers,
15383 window,
15384 cx,
15385 ))
15386 }
15387
15388 fn format_selections(
15389 &mut self,
15390 _: &FormatSelections,
15391 window: &mut Window,
15392 cx: &mut Context<Self>,
15393 ) -> Option<Task<Result<()>>> {
15394 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15395
15396 let project = match &self.project {
15397 Some(project) => project.clone(),
15398 None => return None,
15399 };
15400
15401 let ranges = self
15402 .selections
15403 .all_adjusted(cx)
15404 .into_iter()
15405 .map(|selection| selection.range())
15406 .collect_vec();
15407
15408 Some(self.perform_format(
15409 project,
15410 FormatTrigger::Manual,
15411 FormatTarget::Ranges(ranges),
15412 window,
15413 cx,
15414 ))
15415 }
15416
15417 fn perform_format(
15418 &mut self,
15419 project: Entity<Project>,
15420 trigger: FormatTrigger,
15421 target: FormatTarget,
15422 window: &mut Window,
15423 cx: &mut Context<Self>,
15424 ) -> Task<Result<()>> {
15425 let buffer = self.buffer.clone();
15426 let (buffers, target) = match target {
15427 FormatTarget::Buffers => {
15428 let mut buffers = buffer.read(cx).all_buffers();
15429 if trigger == FormatTrigger::Save {
15430 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15431 }
15432 (buffers, LspFormatTarget::Buffers)
15433 }
15434 FormatTarget::Ranges(selection_ranges) => {
15435 let multi_buffer = buffer.read(cx);
15436 let snapshot = multi_buffer.read(cx);
15437 let mut buffers = HashSet::default();
15438 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15439 BTreeMap::new();
15440 for selection_range in selection_ranges {
15441 for (buffer, buffer_range, _) in
15442 snapshot.range_to_buffer_ranges(selection_range)
15443 {
15444 let buffer_id = buffer.remote_id();
15445 let start = buffer.anchor_before(buffer_range.start);
15446 let end = buffer.anchor_after(buffer_range.end);
15447 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15448 buffer_id_to_ranges
15449 .entry(buffer_id)
15450 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15451 .or_insert_with(|| vec![start..end]);
15452 }
15453 }
15454 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15455 }
15456 };
15457
15458 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15459 let selections_prev = transaction_id_prev
15460 .and_then(|transaction_id_prev| {
15461 // default to selections as they were after the last edit, if we have them,
15462 // instead of how they are now.
15463 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15464 // will take you back to where you made the last edit, instead of staying where you scrolled
15465 self.selection_history
15466 .transaction(transaction_id_prev)
15467 .map(|t| t.0.clone())
15468 })
15469 .unwrap_or_else(|| {
15470 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15471 self.selections.disjoint_anchors()
15472 });
15473
15474 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15475 let format = project.update(cx, |project, cx| {
15476 project.format(buffers, target, true, trigger, cx)
15477 });
15478
15479 cx.spawn_in(window, async move |editor, cx| {
15480 let transaction = futures::select_biased! {
15481 transaction = format.log_err().fuse() => transaction,
15482 () = timeout => {
15483 log::warn!("timed out waiting for formatting");
15484 None
15485 }
15486 };
15487
15488 buffer
15489 .update(cx, |buffer, cx| {
15490 if let Some(transaction) = transaction {
15491 if !buffer.is_singleton() {
15492 buffer.push_transaction(&transaction.0, cx);
15493 }
15494 }
15495 cx.notify();
15496 })
15497 .ok();
15498
15499 if let Some(transaction_id_now) =
15500 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15501 {
15502 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15503 if has_new_transaction {
15504 _ = editor.update(cx, |editor, _| {
15505 editor
15506 .selection_history
15507 .insert_transaction(transaction_id_now, selections_prev);
15508 });
15509 }
15510 }
15511
15512 Ok(())
15513 })
15514 }
15515
15516 fn organize_imports(
15517 &mut self,
15518 _: &OrganizeImports,
15519 window: &mut Window,
15520 cx: &mut Context<Self>,
15521 ) -> Option<Task<Result<()>>> {
15522 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15523 let project = match &self.project {
15524 Some(project) => project.clone(),
15525 None => return None,
15526 };
15527 Some(self.perform_code_action_kind(
15528 project,
15529 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15530 window,
15531 cx,
15532 ))
15533 }
15534
15535 fn perform_code_action_kind(
15536 &mut self,
15537 project: Entity<Project>,
15538 kind: CodeActionKind,
15539 window: &mut Window,
15540 cx: &mut Context<Self>,
15541 ) -> Task<Result<()>> {
15542 let buffer = self.buffer.clone();
15543 let buffers = buffer.read(cx).all_buffers();
15544 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15545 let apply_action = project.update(cx, |project, cx| {
15546 project.apply_code_action_kind(buffers, kind, true, cx)
15547 });
15548 cx.spawn_in(window, async move |_, cx| {
15549 let transaction = futures::select_biased! {
15550 () = timeout => {
15551 log::warn!("timed out waiting for executing code action");
15552 None
15553 }
15554 transaction = apply_action.log_err().fuse() => transaction,
15555 };
15556 buffer
15557 .update(cx, |buffer, cx| {
15558 // check if we need this
15559 if let Some(transaction) = transaction {
15560 if !buffer.is_singleton() {
15561 buffer.push_transaction(&transaction.0, cx);
15562 }
15563 }
15564 cx.notify();
15565 })
15566 .ok();
15567 Ok(())
15568 })
15569 }
15570
15571 fn restart_language_server(
15572 &mut self,
15573 _: &RestartLanguageServer,
15574 _: &mut Window,
15575 cx: &mut Context<Self>,
15576 ) {
15577 if let Some(project) = self.project.clone() {
15578 self.buffer.update(cx, |multi_buffer, cx| {
15579 project.update(cx, |project, cx| {
15580 project.restart_language_servers_for_buffers(
15581 multi_buffer.all_buffers().into_iter().collect(),
15582 cx,
15583 );
15584 });
15585 })
15586 }
15587 }
15588
15589 fn stop_language_server(
15590 &mut self,
15591 _: &StopLanguageServer,
15592 _: &mut Window,
15593 cx: &mut Context<Self>,
15594 ) {
15595 if let Some(project) = self.project.clone() {
15596 self.buffer.update(cx, |multi_buffer, cx| {
15597 project.update(cx, |project, cx| {
15598 project.stop_language_servers_for_buffers(
15599 multi_buffer.all_buffers().into_iter().collect(),
15600 cx,
15601 );
15602 cx.emit(project::Event::RefreshInlayHints);
15603 });
15604 });
15605 }
15606 }
15607
15608 fn cancel_language_server_work(
15609 workspace: &mut Workspace,
15610 _: &actions::CancelLanguageServerWork,
15611 _: &mut Window,
15612 cx: &mut Context<Workspace>,
15613 ) {
15614 let project = workspace.project();
15615 let buffers = workspace
15616 .active_item(cx)
15617 .and_then(|item| item.act_as::<Editor>(cx))
15618 .map_or(HashSet::default(), |editor| {
15619 editor.read(cx).buffer.read(cx).all_buffers()
15620 });
15621 project.update(cx, |project, cx| {
15622 project.cancel_language_server_work_for_buffers(buffers, cx);
15623 });
15624 }
15625
15626 fn show_character_palette(
15627 &mut self,
15628 _: &ShowCharacterPalette,
15629 window: &mut Window,
15630 _: &mut Context<Self>,
15631 ) {
15632 window.show_character_palette();
15633 }
15634
15635 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15636 if self.mode.is_minimap() {
15637 return;
15638 }
15639
15640 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15641 let buffer = self.buffer.read(cx).snapshot(cx);
15642 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15643 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15644 let is_valid = buffer
15645 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15646 .any(|entry| {
15647 entry.diagnostic.is_primary
15648 && !entry.range.is_empty()
15649 && entry.range.start == primary_range_start
15650 && entry.diagnostic.message == active_diagnostics.active_message
15651 });
15652
15653 if !is_valid {
15654 self.dismiss_diagnostics(cx);
15655 }
15656 }
15657 }
15658
15659 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15660 match &self.active_diagnostics {
15661 ActiveDiagnostic::Group(group) => Some(group),
15662 _ => None,
15663 }
15664 }
15665
15666 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15667 self.dismiss_diagnostics(cx);
15668 self.active_diagnostics = ActiveDiagnostic::All;
15669 }
15670
15671 fn activate_diagnostics(
15672 &mut self,
15673 buffer_id: BufferId,
15674 diagnostic: DiagnosticEntry<usize>,
15675 window: &mut Window,
15676 cx: &mut Context<Self>,
15677 ) {
15678 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15679 return;
15680 }
15681 self.dismiss_diagnostics(cx);
15682 let snapshot = self.snapshot(window, cx);
15683 let buffer = self.buffer.read(cx).snapshot(cx);
15684 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15685 return;
15686 };
15687
15688 let diagnostic_group = buffer
15689 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15690 .collect::<Vec<_>>();
15691
15692 let blocks =
15693 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15694
15695 let blocks = self.display_map.update(cx, |display_map, cx| {
15696 display_map.insert_blocks(blocks, cx).into_iter().collect()
15697 });
15698 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15699 active_range: buffer.anchor_before(diagnostic.range.start)
15700 ..buffer.anchor_after(diagnostic.range.end),
15701 active_message: diagnostic.diagnostic.message.clone(),
15702 group_id: diagnostic.diagnostic.group_id,
15703 blocks,
15704 });
15705 cx.notify();
15706 }
15707
15708 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15709 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15710 return;
15711 };
15712
15713 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15714 if let ActiveDiagnostic::Group(group) = prev {
15715 self.display_map.update(cx, |display_map, cx| {
15716 display_map.remove_blocks(group.blocks, cx);
15717 });
15718 cx.notify();
15719 }
15720 }
15721
15722 /// Disable inline diagnostics rendering for this editor.
15723 pub fn disable_inline_diagnostics(&mut self) {
15724 self.inline_diagnostics_enabled = false;
15725 self.inline_diagnostics_update = Task::ready(());
15726 self.inline_diagnostics.clear();
15727 }
15728
15729 pub fn diagnostics_enabled(&self) -> bool {
15730 self.mode.is_full()
15731 }
15732
15733 pub fn inline_diagnostics_enabled(&self) -> bool {
15734 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15735 }
15736
15737 pub fn show_inline_diagnostics(&self) -> bool {
15738 self.show_inline_diagnostics
15739 }
15740
15741 pub fn toggle_inline_diagnostics(
15742 &mut self,
15743 _: &ToggleInlineDiagnostics,
15744 window: &mut Window,
15745 cx: &mut Context<Editor>,
15746 ) {
15747 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15748 self.refresh_inline_diagnostics(false, window, cx);
15749 }
15750
15751 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15752 self.diagnostics_max_severity = severity;
15753 self.display_map.update(cx, |display_map, _| {
15754 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15755 });
15756 }
15757
15758 pub fn toggle_diagnostics(
15759 &mut self,
15760 _: &ToggleDiagnostics,
15761 window: &mut Window,
15762 cx: &mut Context<Editor>,
15763 ) {
15764 if !self.diagnostics_enabled() {
15765 return;
15766 }
15767
15768 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15769 EditorSettings::get_global(cx)
15770 .diagnostics_max_severity
15771 .filter(|severity| severity != &DiagnosticSeverity::Off)
15772 .unwrap_or(DiagnosticSeverity::Hint)
15773 } else {
15774 DiagnosticSeverity::Off
15775 };
15776 self.set_max_diagnostics_severity(new_severity, cx);
15777 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15778 self.active_diagnostics = ActiveDiagnostic::None;
15779 self.inline_diagnostics_update = Task::ready(());
15780 self.inline_diagnostics.clear();
15781 } else {
15782 self.refresh_inline_diagnostics(false, window, cx);
15783 }
15784
15785 cx.notify();
15786 }
15787
15788 pub fn toggle_minimap(
15789 &mut self,
15790 _: &ToggleMinimap,
15791 window: &mut Window,
15792 cx: &mut Context<Editor>,
15793 ) {
15794 if self.supports_minimap(cx) {
15795 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15796 }
15797 }
15798
15799 fn refresh_inline_diagnostics(
15800 &mut self,
15801 debounce: bool,
15802 window: &mut Window,
15803 cx: &mut Context<Self>,
15804 ) {
15805 let max_severity = ProjectSettings::get_global(cx)
15806 .diagnostics
15807 .inline
15808 .max_severity
15809 .unwrap_or(self.diagnostics_max_severity);
15810
15811 if !self.inline_diagnostics_enabled()
15812 || !self.show_inline_diagnostics
15813 || max_severity == DiagnosticSeverity::Off
15814 {
15815 self.inline_diagnostics_update = Task::ready(());
15816 self.inline_diagnostics.clear();
15817 return;
15818 }
15819
15820 let debounce_ms = ProjectSettings::get_global(cx)
15821 .diagnostics
15822 .inline
15823 .update_debounce_ms;
15824 let debounce = if debounce && debounce_ms > 0 {
15825 Some(Duration::from_millis(debounce_ms))
15826 } else {
15827 None
15828 };
15829 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15830 if let Some(debounce) = debounce {
15831 cx.background_executor().timer(debounce).await;
15832 }
15833 let Some(snapshot) = editor.upgrade().and_then(|editor| {
15834 editor
15835 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15836 .ok()
15837 }) else {
15838 return;
15839 };
15840
15841 let new_inline_diagnostics = cx
15842 .background_spawn(async move {
15843 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15844 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15845 let message = diagnostic_entry
15846 .diagnostic
15847 .message
15848 .split_once('\n')
15849 .map(|(line, _)| line)
15850 .map(SharedString::new)
15851 .unwrap_or_else(|| {
15852 SharedString::from(diagnostic_entry.diagnostic.message)
15853 });
15854 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15855 let (Ok(i) | Err(i)) = inline_diagnostics
15856 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15857 inline_diagnostics.insert(
15858 i,
15859 (
15860 start_anchor,
15861 InlineDiagnostic {
15862 message,
15863 group_id: diagnostic_entry.diagnostic.group_id,
15864 start: diagnostic_entry.range.start.to_point(&snapshot),
15865 is_primary: diagnostic_entry.diagnostic.is_primary,
15866 severity: diagnostic_entry.diagnostic.severity,
15867 },
15868 ),
15869 );
15870 }
15871 inline_diagnostics
15872 })
15873 .await;
15874
15875 editor
15876 .update(cx, |editor, cx| {
15877 editor.inline_diagnostics = new_inline_diagnostics;
15878 cx.notify();
15879 })
15880 .ok();
15881 });
15882 }
15883
15884 pub fn set_selections_from_remote(
15885 &mut self,
15886 selections: Vec<Selection<Anchor>>,
15887 pending_selection: Option<Selection<Anchor>>,
15888 window: &mut Window,
15889 cx: &mut Context<Self>,
15890 ) {
15891 let old_cursor_position = self.selections.newest_anchor().head();
15892 self.selections.change_with(cx, |s| {
15893 s.select_anchors(selections);
15894 if let Some(pending_selection) = pending_selection {
15895 s.set_pending(pending_selection, SelectMode::Character);
15896 } else {
15897 s.clear_pending();
15898 }
15899 });
15900 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15901 }
15902
15903 pub fn transact(
15904 &mut self,
15905 window: &mut Window,
15906 cx: &mut Context<Self>,
15907 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15908 ) -> Option<TransactionId> {
15909 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15910 this.start_transaction_at(Instant::now(), window, cx);
15911 update(this, window, cx);
15912 this.end_transaction_at(Instant::now(), cx)
15913 })
15914 }
15915
15916 pub fn start_transaction_at(
15917 &mut self,
15918 now: Instant,
15919 window: &mut Window,
15920 cx: &mut Context<Self>,
15921 ) {
15922 self.end_selection(window, cx);
15923 if let Some(tx_id) = self
15924 .buffer
15925 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15926 {
15927 self.selection_history
15928 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15929 cx.emit(EditorEvent::TransactionBegun {
15930 transaction_id: tx_id,
15931 })
15932 }
15933 }
15934
15935 pub fn end_transaction_at(
15936 &mut self,
15937 now: Instant,
15938 cx: &mut Context<Self>,
15939 ) -> Option<TransactionId> {
15940 if let Some(transaction_id) = self
15941 .buffer
15942 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15943 {
15944 if let Some((_, end_selections)) =
15945 self.selection_history.transaction_mut(transaction_id)
15946 {
15947 *end_selections = Some(self.selections.disjoint_anchors());
15948 } else {
15949 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15950 }
15951
15952 cx.emit(EditorEvent::Edited { transaction_id });
15953 Some(transaction_id)
15954 } else {
15955 None
15956 }
15957 }
15958
15959 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15960 if self.selection_mark_mode {
15961 self.change_selections(None, window, cx, |s| {
15962 s.move_with(|_, sel| {
15963 sel.collapse_to(sel.head(), SelectionGoal::None);
15964 });
15965 })
15966 }
15967 self.selection_mark_mode = true;
15968 cx.notify();
15969 }
15970
15971 pub fn swap_selection_ends(
15972 &mut self,
15973 _: &actions::SwapSelectionEnds,
15974 window: &mut Window,
15975 cx: &mut Context<Self>,
15976 ) {
15977 self.change_selections(None, window, cx, |s| {
15978 s.move_with(|_, sel| {
15979 if sel.start != sel.end {
15980 sel.reversed = !sel.reversed
15981 }
15982 });
15983 });
15984 self.request_autoscroll(Autoscroll::newest(), cx);
15985 cx.notify();
15986 }
15987
15988 pub fn toggle_fold(
15989 &mut self,
15990 _: &actions::ToggleFold,
15991 window: &mut Window,
15992 cx: &mut Context<Self>,
15993 ) {
15994 if self.is_singleton(cx) {
15995 let selection = self.selections.newest::<Point>(cx);
15996
15997 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15998 let range = if selection.is_empty() {
15999 let point = selection.head().to_display_point(&display_map);
16000 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16001 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16002 .to_point(&display_map);
16003 start..end
16004 } else {
16005 selection.range()
16006 };
16007 if display_map.folds_in_range(range).next().is_some() {
16008 self.unfold_lines(&Default::default(), window, cx)
16009 } else {
16010 self.fold(&Default::default(), window, cx)
16011 }
16012 } else {
16013 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16014 let buffer_ids: HashSet<_> = self
16015 .selections
16016 .disjoint_anchor_ranges()
16017 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16018 .collect();
16019
16020 let should_unfold = buffer_ids
16021 .iter()
16022 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16023
16024 for buffer_id in buffer_ids {
16025 if should_unfold {
16026 self.unfold_buffer(buffer_id, cx);
16027 } else {
16028 self.fold_buffer(buffer_id, cx);
16029 }
16030 }
16031 }
16032 }
16033
16034 pub fn toggle_fold_recursive(
16035 &mut self,
16036 _: &actions::ToggleFoldRecursive,
16037 window: &mut Window,
16038 cx: &mut Context<Self>,
16039 ) {
16040 let selection = self.selections.newest::<Point>(cx);
16041
16042 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16043 let range = if selection.is_empty() {
16044 let point = selection.head().to_display_point(&display_map);
16045 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16046 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16047 .to_point(&display_map);
16048 start..end
16049 } else {
16050 selection.range()
16051 };
16052 if display_map.folds_in_range(range).next().is_some() {
16053 self.unfold_recursive(&Default::default(), window, cx)
16054 } else {
16055 self.fold_recursive(&Default::default(), window, cx)
16056 }
16057 }
16058
16059 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16060 if self.is_singleton(cx) {
16061 let mut to_fold = Vec::new();
16062 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16063 let selections = self.selections.all_adjusted(cx);
16064
16065 for selection in selections {
16066 let range = selection.range().sorted();
16067 let buffer_start_row = range.start.row;
16068
16069 if range.start.row != range.end.row {
16070 let mut found = false;
16071 let mut row = range.start.row;
16072 while row <= range.end.row {
16073 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16074 {
16075 found = true;
16076 row = crease.range().end.row + 1;
16077 to_fold.push(crease);
16078 } else {
16079 row += 1
16080 }
16081 }
16082 if found {
16083 continue;
16084 }
16085 }
16086
16087 for row in (0..=range.start.row).rev() {
16088 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16089 if crease.range().end.row >= buffer_start_row {
16090 to_fold.push(crease);
16091 if row <= range.start.row {
16092 break;
16093 }
16094 }
16095 }
16096 }
16097 }
16098
16099 self.fold_creases(to_fold, true, window, cx);
16100 } else {
16101 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16102 let buffer_ids = self
16103 .selections
16104 .disjoint_anchor_ranges()
16105 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16106 .collect::<HashSet<_>>();
16107 for buffer_id in buffer_ids {
16108 self.fold_buffer(buffer_id, cx);
16109 }
16110 }
16111 }
16112
16113 fn fold_at_level(
16114 &mut self,
16115 fold_at: &FoldAtLevel,
16116 window: &mut Window,
16117 cx: &mut Context<Self>,
16118 ) {
16119 if !self.buffer.read(cx).is_singleton() {
16120 return;
16121 }
16122
16123 let fold_at_level = fold_at.0;
16124 let snapshot = self.buffer.read(cx).snapshot(cx);
16125 let mut to_fold = Vec::new();
16126 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16127
16128 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16129 while start_row < end_row {
16130 match self
16131 .snapshot(window, cx)
16132 .crease_for_buffer_row(MultiBufferRow(start_row))
16133 {
16134 Some(crease) => {
16135 let nested_start_row = crease.range().start.row + 1;
16136 let nested_end_row = crease.range().end.row;
16137
16138 if current_level < fold_at_level {
16139 stack.push((nested_start_row, nested_end_row, current_level + 1));
16140 } else if current_level == fold_at_level {
16141 to_fold.push(crease);
16142 }
16143
16144 start_row = nested_end_row + 1;
16145 }
16146 None => start_row += 1,
16147 }
16148 }
16149 }
16150
16151 self.fold_creases(to_fold, true, window, cx);
16152 }
16153
16154 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16155 if self.buffer.read(cx).is_singleton() {
16156 let mut fold_ranges = Vec::new();
16157 let snapshot = self.buffer.read(cx).snapshot(cx);
16158
16159 for row in 0..snapshot.max_row().0 {
16160 if let Some(foldable_range) = self
16161 .snapshot(window, cx)
16162 .crease_for_buffer_row(MultiBufferRow(row))
16163 {
16164 fold_ranges.push(foldable_range);
16165 }
16166 }
16167
16168 self.fold_creases(fold_ranges, true, window, cx);
16169 } else {
16170 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16171 editor
16172 .update_in(cx, |editor, _, cx| {
16173 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16174 editor.fold_buffer(buffer_id, cx);
16175 }
16176 })
16177 .ok();
16178 });
16179 }
16180 }
16181
16182 pub fn fold_function_bodies(
16183 &mut self,
16184 _: &actions::FoldFunctionBodies,
16185 window: &mut Window,
16186 cx: &mut Context<Self>,
16187 ) {
16188 let snapshot = self.buffer.read(cx).snapshot(cx);
16189
16190 let ranges = snapshot
16191 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16192 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16193 .collect::<Vec<_>>();
16194
16195 let creases = ranges
16196 .into_iter()
16197 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16198 .collect();
16199
16200 self.fold_creases(creases, true, window, cx);
16201 }
16202
16203 pub fn fold_recursive(
16204 &mut self,
16205 _: &actions::FoldRecursive,
16206 window: &mut Window,
16207 cx: &mut Context<Self>,
16208 ) {
16209 let mut to_fold = Vec::new();
16210 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16211 let selections = self.selections.all_adjusted(cx);
16212
16213 for selection in selections {
16214 let range = selection.range().sorted();
16215 let buffer_start_row = range.start.row;
16216
16217 if range.start.row != range.end.row {
16218 let mut found = false;
16219 for row in range.start.row..=range.end.row {
16220 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16221 found = true;
16222 to_fold.push(crease);
16223 }
16224 }
16225 if found {
16226 continue;
16227 }
16228 }
16229
16230 for row in (0..=range.start.row).rev() {
16231 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16232 if crease.range().end.row >= buffer_start_row {
16233 to_fold.push(crease);
16234 } else {
16235 break;
16236 }
16237 }
16238 }
16239 }
16240
16241 self.fold_creases(to_fold, true, window, cx);
16242 }
16243
16244 pub fn fold_at(
16245 &mut self,
16246 buffer_row: MultiBufferRow,
16247 window: &mut Window,
16248 cx: &mut Context<Self>,
16249 ) {
16250 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16251
16252 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16253 let autoscroll = self
16254 .selections
16255 .all::<Point>(cx)
16256 .iter()
16257 .any(|selection| crease.range().overlaps(&selection.range()));
16258
16259 self.fold_creases(vec![crease], autoscroll, window, cx);
16260 }
16261 }
16262
16263 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16264 if self.is_singleton(cx) {
16265 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16266 let buffer = &display_map.buffer_snapshot;
16267 let selections = self.selections.all::<Point>(cx);
16268 let ranges = selections
16269 .iter()
16270 .map(|s| {
16271 let range = s.display_range(&display_map).sorted();
16272 let mut start = range.start.to_point(&display_map);
16273 let mut end = range.end.to_point(&display_map);
16274 start.column = 0;
16275 end.column = buffer.line_len(MultiBufferRow(end.row));
16276 start..end
16277 })
16278 .collect::<Vec<_>>();
16279
16280 self.unfold_ranges(&ranges, true, true, cx);
16281 } else {
16282 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16283 let buffer_ids = self
16284 .selections
16285 .disjoint_anchor_ranges()
16286 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16287 .collect::<HashSet<_>>();
16288 for buffer_id in buffer_ids {
16289 self.unfold_buffer(buffer_id, cx);
16290 }
16291 }
16292 }
16293
16294 pub fn unfold_recursive(
16295 &mut self,
16296 _: &UnfoldRecursive,
16297 _window: &mut Window,
16298 cx: &mut Context<Self>,
16299 ) {
16300 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16301 let selections = self.selections.all::<Point>(cx);
16302 let ranges = selections
16303 .iter()
16304 .map(|s| {
16305 let mut range = s.display_range(&display_map).sorted();
16306 *range.start.column_mut() = 0;
16307 *range.end.column_mut() = display_map.line_len(range.end.row());
16308 let start = range.start.to_point(&display_map);
16309 let end = range.end.to_point(&display_map);
16310 start..end
16311 })
16312 .collect::<Vec<_>>();
16313
16314 self.unfold_ranges(&ranges, true, true, cx);
16315 }
16316
16317 pub fn unfold_at(
16318 &mut self,
16319 buffer_row: MultiBufferRow,
16320 _window: &mut Window,
16321 cx: &mut Context<Self>,
16322 ) {
16323 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16324
16325 let intersection_range = Point::new(buffer_row.0, 0)
16326 ..Point::new(
16327 buffer_row.0,
16328 display_map.buffer_snapshot.line_len(buffer_row),
16329 );
16330
16331 let autoscroll = self
16332 .selections
16333 .all::<Point>(cx)
16334 .iter()
16335 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16336
16337 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16338 }
16339
16340 pub fn unfold_all(
16341 &mut self,
16342 _: &actions::UnfoldAll,
16343 _window: &mut Window,
16344 cx: &mut Context<Self>,
16345 ) {
16346 if self.buffer.read(cx).is_singleton() {
16347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16348 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16349 } else {
16350 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16351 editor
16352 .update(cx, |editor, cx| {
16353 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16354 editor.unfold_buffer(buffer_id, cx);
16355 }
16356 })
16357 .ok();
16358 });
16359 }
16360 }
16361
16362 pub fn fold_selected_ranges(
16363 &mut self,
16364 _: &FoldSelectedRanges,
16365 window: &mut Window,
16366 cx: &mut Context<Self>,
16367 ) {
16368 let selections = self.selections.all_adjusted(cx);
16369 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16370 let ranges = selections
16371 .into_iter()
16372 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16373 .collect::<Vec<_>>();
16374 self.fold_creases(ranges, true, window, cx);
16375 }
16376
16377 pub fn fold_ranges<T: ToOffset + Clone>(
16378 &mut self,
16379 ranges: Vec<Range<T>>,
16380 auto_scroll: bool,
16381 window: &mut Window,
16382 cx: &mut Context<Self>,
16383 ) {
16384 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16385 let ranges = ranges
16386 .into_iter()
16387 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16388 .collect::<Vec<_>>();
16389 self.fold_creases(ranges, auto_scroll, window, cx);
16390 }
16391
16392 pub fn fold_creases<T: ToOffset + Clone>(
16393 &mut self,
16394 creases: Vec<Crease<T>>,
16395 auto_scroll: bool,
16396 _window: &mut Window,
16397 cx: &mut Context<Self>,
16398 ) {
16399 if creases.is_empty() {
16400 return;
16401 }
16402
16403 let mut buffers_affected = HashSet::default();
16404 let multi_buffer = self.buffer().read(cx);
16405 for crease in &creases {
16406 if let Some((_, buffer, _)) =
16407 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16408 {
16409 buffers_affected.insert(buffer.read(cx).remote_id());
16410 };
16411 }
16412
16413 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16414
16415 if auto_scroll {
16416 self.request_autoscroll(Autoscroll::fit(), cx);
16417 }
16418
16419 cx.notify();
16420
16421 self.scrollbar_marker_state.dirty = true;
16422 self.folds_did_change(cx);
16423 }
16424
16425 /// Removes any folds whose ranges intersect any of the given ranges.
16426 pub fn unfold_ranges<T: ToOffset + Clone>(
16427 &mut self,
16428 ranges: &[Range<T>],
16429 inclusive: bool,
16430 auto_scroll: bool,
16431 cx: &mut Context<Self>,
16432 ) {
16433 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16434 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16435 });
16436 self.folds_did_change(cx);
16437 }
16438
16439 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16440 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16441 return;
16442 }
16443 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16444 self.display_map.update(cx, |display_map, cx| {
16445 display_map.fold_buffers([buffer_id], cx)
16446 });
16447 cx.emit(EditorEvent::BufferFoldToggled {
16448 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16449 folded: true,
16450 });
16451 cx.notify();
16452 }
16453
16454 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16455 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16456 return;
16457 }
16458 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16459 self.display_map.update(cx, |display_map, cx| {
16460 display_map.unfold_buffers([buffer_id], cx);
16461 });
16462 cx.emit(EditorEvent::BufferFoldToggled {
16463 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16464 folded: false,
16465 });
16466 cx.notify();
16467 }
16468
16469 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16470 self.display_map.read(cx).is_buffer_folded(buffer)
16471 }
16472
16473 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16474 self.display_map.read(cx).folded_buffers()
16475 }
16476
16477 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16478 self.display_map.update(cx, |display_map, cx| {
16479 display_map.disable_header_for_buffer(buffer_id, cx);
16480 });
16481 cx.notify();
16482 }
16483
16484 /// Removes any folds with the given ranges.
16485 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16486 &mut self,
16487 ranges: &[Range<T>],
16488 type_id: TypeId,
16489 auto_scroll: bool,
16490 cx: &mut Context<Self>,
16491 ) {
16492 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16493 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16494 });
16495 self.folds_did_change(cx);
16496 }
16497
16498 fn remove_folds_with<T: ToOffset + Clone>(
16499 &mut self,
16500 ranges: &[Range<T>],
16501 auto_scroll: bool,
16502 cx: &mut Context<Self>,
16503 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16504 ) {
16505 if ranges.is_empty() {
16506 return;
16507 }
16508
16509 let mut buffers_affected = HashSet::default();
16510 let multi_buffer = self.buffer().read(cx);
16511 for range in ranges {
16512 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16513 buffers_affected.insert(buffer.read(cx).remote_id());
16514 };
16515 }
16516
16517 self.display_map.update(cx, update);
16518
16519 if auto_scroll {
16520 self.request_autoscroll(Autoscroll::fit(), cx);
16521 }
16522
16523 cx.notify();
16524 self.scrollbar_marker_state.dirty = true;
16525 self.active_indent_guides_state.dirty = true;
16526 }
16527
16528 pub fn update_fold_widths(
16529 &mut self,
16530 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16531 cx: &mut Context<Self>,
16532 ) -> bool {
16533 self.display_map
16534 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16535 }
16536
16537 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16538 self.display_map.read(cx).fold_placeholder.clone()
16539 }
16540
16541 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16542 self.buffer.update(cx, |buffer, cx| {
16543 buffer.set_all_diff_hunks_expanded(cx);
16544 });
16545 }
16546
16547 pub fn expand_all_diff_hunks(
16548 &mut self,
16549 _: &ExpandAllDiffHunks,
16550 _window: &mut Window,
16551 cx: &mut Context<Self>,
16552 ) {
16553 self.buffer.update(cx, |buffer, cx| {
16554 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16555 });
16556 }
16557
16558 pub fn toggle_selected_diff_hunks(
16559 &mut self,
16560 _: &ToggleSelectedDiffHunks,
16561 _window: &mut Window,
16562 cx: &mut Context<Self>,
16563 ) {
16564 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16565 self.toggle_diff_hunks_in_ranges(ranges, cx);
16566 }
16567
16568 pub fn diff_hunks_in_ranges<'a>(
16569 &'a self,
16570 ranges: &'a [Range<Anchor>],
16571 buffer: &'a MultiBufferSnapshot,
16572 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16573 ranges.iter().flat_map(move |range| {
16574 let end_excerpt_id = range.end.excerpt_id;
16575 let range = range.to_point(buffer);
16576 let mut peek_end = range.end;
16577 if range.end.row < buffer.max_row().0 {
16578 peek_end = Point::new(range.end.row + 1, 0);
16579 }
16580 buffer
16581 .diff_hunks_in_range(range.start..peek_end)
16582 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16583 })
16584 }
16585
16586 pub fn has_stageable_diff_hunks_in_ranges(
16587 &self,
16588 ranges: &[Range<Anchor>],
16589 snapshot: &MultiBufferSnapshot,
16590 ) -> bool {
16591 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16592 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16593 }
16594
16595 pub fn toggle_staged_selected_diff_hunks(
16596 &mut self,
16597 _: &::git::ToggleStaged,
16598 _: &mut Window,
16599 cx: &mut Context<Self>,
16600 ) {
16601 let snapshot = self.buffer.read(cx).snapshot(cx);
16602 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16603 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16604 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16605 }
16606
16607 pub fn set_render_diff_hunk_controls(
16608 &mut self,
16609 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16610 cx: &mut Context<Self>,
16611 ) {
16612 self.render_diff_hunk_controls = render_diff_hunk_controls;
16613 cx.notify();
16614 }
16615
16616 pub fn stage_and_next(
16617 &mut self,
16618 _: &::git::StageAndNext,
16619 window: &mut Window,
16620 cx: &mut Context<Self>,
16621 ) {
16622 self.do_stage_or_unstage_and_next(true, window, cx);
16623 }
16624
16625 pub fn unstage_and_next(
16626 &mut self,
16627 _: &::git::UnstageAndNext,
16628 window: &mut Window,
16629 cx: &mut Context<Self>,
16630 ) {
16631 self.do_stage_or_unstage_and_next(false, window, cx);
16632 }
16633
16634 pub fn stage_or_unstage_diff_hunks(
16635 &mut self,
16636 stage: bool,
16637 ranges: Vec<Range<Anchor>>,
16638 cx: &mut Context<Self>,
16639 ) {
16640 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16641 cx.spawn(async move |this, cx| {
16642 task.await?;
16643 this.update(cx, |this, cx| {
16644 let snapshot = this.buffer.read(cx).snapshot(cx);
16645 let chunk_by = this
16646 .diff_hunks_in_ranges(&ranges, &snapshot)
16647 .chunk_by(|hunk| hunk.buffer_id);
16648 for (buffer_id, hunks) in &chunk_by {
16649 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16650 }
16651 })
16652 })
16653 .detach_and_log_err(cx);
16654 }
16655
16656 fn save_buffers_for_ranges_if_needed(
16657 &mut self,
16658 ranges: &[Range<Anchor>],
16659 cx: &mut Context<Editor>,
16660 ) -> Task<Result<()>> {
16661 let multibuffer = self.buffer.read(cx);
16662 let snapshot = multibuffer.read(cx);
16663 let buffer_ids: HashSet<_> = ranges
16664 .iter()
16665 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16666 .collect();
16667 drop(snapshot);
16668
16669 let mut buffers = HashSet::default();
16670 for buffer_id in buffer_ids {
16671 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16672 let buffer = buffer_entity.read(cx);
16673 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16674 {
16675 buffers.insert(buffer_entity);
16676 }
16677 }
16678 }
16679
16680 if let Some(project) = &self.project {
16681 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16682 } else {
16683 Task::ready(Ok(()))
16684 }
16685 }
16686
16687 fn do_stage_or_unstage_and_next(
16688 &mut self,
16689 stage: bool,
16690 window: &mut Window,
16691 cx: &mut Context<Self>,
16692 ) {
16693 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16694
16695 if ranges.iter().any(|range| range.start != range.end) {
16696 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16697 return;
16698 }
16699
16700 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16701 let snapshot = self.snapshot(window, cx);
16702 let position = self.selections.newest::<Point>(cx).head();
16703 let mut row = snapshot
16704 .buffer_snapshot
16705 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16706 .find(|hunk| hunk.row_range.start.0 > position.row)
16707 .map(|hunk| hunk.row_range.start);
16708
16709 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16710 // Outside of the project diff editor, wrap around to the beginning.
16711 if !all_diff_hunks_expanded {
16712 row = row.or_else(|| {
16713 snapshot
16714 .buffer_snapshot
16715 .diff_hunks_in_range(Point::zero()..position)
16716 .find(|hunk| hunk.row_range.end.0 < position.row)
16717 .map(|hunk| hunk.row_range.start)
16718 });
16719 }
16720
16721 if let Some(row) = row {
16722 let destination = Point::new(row.0, 0);
16723 let autoscroll = Autoscroll::center();
16724
16725 self.unfold_ranges(&[destination..destination], false, false, cx);
16726 self.change_selections(Some(autoscroll), window, cx, |s| {
16727 s.select_ranges([destination..destination]);
16728 });
16729 }
16730 }
16731
16732 fn do_stage_or_unstage(
16733 &self,
16734 stage: bool,
16735 buffer_id: BufferId,
16736 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16737 cx: &mut App,
16738 ) -> Option<()> {
16739 let project = self.project.as_ref()?;
16740 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16741 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16742 let buffer_snapshot = buffer.read(cx).snapshot();
16743 let file_exists = buffer_snapshot
16744 .file()
16745 .is_some_and(|file| file.disk_state().exists());
16746 diff.update(cx, |diff, cx| {
16747 diff.stage_or_unstage_hunks(
16748 stage,
16749 &hunks
16750 .map(|hunk| buffer_diff::DiffHunk {
16751 buffer_range: hunk.buffer_range,
16752 diff_base_byte_range: hunk.diff_base_byte_range,
16753 secondary_status: hunk.secondary_status,
16754 range: Point::zero()..Point::zero(), // unused
16755 })
16756 .collect::<Vec<_>>(),
16757 &buffer_snapshot,
16758 file_exists,
16759 cx,
16760 )
16761 });
16762 None
16763 }
16764
16765 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16766 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16767 self.buffer
16768 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16769 }
16770
16771 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16772 self.buffer.update(cx, |buffer, cx| {
16773 let ranges = vec![Anchor::min()..Anchor::max()];
16774 if !buffer.all_diff_hunks_expanded()
16775 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16776 {
16777 buffer.collapse_diff_hunks(ranges, cx);
16778 true
16779 } else {
16780 false
16781 }
16782 })
16783 }
16784
16785 fn toggle_diff_hunks_in_ranges(
16786 &mut self,
16787 ranges: Vec<Range<Anchor>>,
16788 cx: &mut Context<Editor>,
16789 ) {
16790 self.buffer.update(cx, |buffer, cx| {
16791 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16792 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16793 })
16794 }
16795
16796 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16797 self.buffer.update(cx, |buffer, cx| {
16798 let snapshot = buffer.snapshot(cx);
16799 let excerpt_id = range.end.excerpt_id;
16800 let point_range = range.to_point(&snapshot);
16801 let expand = !buffer.single_hunk_is_expanded(range, cx);
16802 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16803 })
16804 }
16805
16806 pub(crate) fn apply_all_diff_hunks(
16807 &mut self,
16808 _: &ApplyAllDiffHunks,
16809 window: &mut Window,
16810 cx: &mut Context<Self>,
16811 ) {
16812 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16813
16814 let buffers = self.buffer.read(cx).all_buffers();
16815 for branch_buffer in buffers {
16816 branch_buffer.update(cx, |branch_buffer, cx| {
16817 branch_buffer.merge_into_base(Vec::new(), cx);
16818 });
16819 }
16820
16821 if let Some(project) = self.project.clone() {
16822 self.save(true, project, window, cx).detach_and_log_err(cx);
16823 }
16824 }
16825
16826 pub(crate) fn apply_selected_diff_hunks(
16827 &mut self,
16828 _: &ApplyDiffHunk,
16829 window: &mut Window,
16830 cx: &mut Context<Self>,
16831 ) {
16832 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16833 let snapshot = self.snapshot(window, cx);
16834 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16835 let mut ranges_by_buffer = HashMap::default();
16836 self.transact(window, cx, |editor, _window, cx| {
16837 for hunk in hunks {
16838 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16839 ranges_by_buffer
16840 .entry(buffer.clone())
16841 .or_insert_with(Vec::new)
16842 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16843 }
16844 }
16845
16846 for (buffer, ranges) in ranges_by_buffer {
16847 buffer.update(cx, |buffer, cx| {
16848 buffer.merge_into_base(ranges, cx);
16849 });
16850 }
16851 });
16852
16853 if let Some(project) = self.project.clone() {
16854 self.save(true, project, window, cx).detach_and_log_err(cx);
16855 }
16856 }
16857
16858 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16859 if hovered != self.gutter_hovered {
16860 self.gutter_hovered = hovered;
16861 cx.notify();
16862 }
16863 }
16864
16865 pub fn insert_blocks(
16866 &mut self,
16867 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16868 autoscroll: Option<Autoscroll>,
16869 cx: &mut Context<Self>,
16870 ) -> Vec<CustomBlockId> {
16871 let blocks = self
16872 .display_map
16873 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16874 if let Some(autoscroll) = autoscroll {
16875 self.request_autoscroll(autoscroll, cx);
16876 }
16877 cx.notify();
16878 blocks
16879 }
16880
16881 pub fn resize_blocks(
16882 &mut self,
16883 heights: HashMap<CustomBlockId, u32>,
16884 autoscroll: Option<Autoscroll>,
16885 cx: &mut Context<Self>,
16886 ) {
16887 self.display_map
16888 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16889 if let Some(autoscroll) = autoscroll {
16890 self.request_autoscroll(autoscroll, cx);
16891 }
16892 cx.notify();
16893 }
16894
16895 pub fn replace_blocks(
16896 &mut self,
16897 renderers: HashMap<CustomBlockId, RenderBlock>,
16898 autoscroll: Option<Autoscroll>,
16899 cx: &mut Context<Self>,
16900 ) {
16901 self.display_map
16902 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16903 if let Some(autoscroll) = autoscroll {
16904 self.request_autoscroll(autoscroll, cx);
16905 }
16906 cx.notify();
16907 }
16908
16909 pub fn remove_blocks(
16910 &mut self,
16911 block_ids: HashSet<CustomBlockId>,
16912 autoscroll: Option<Autoscroll>,
16913 cx: &mut Context<Self>,
16914 ) {
16915 self.display_map.update(cx, |display_map, cx| {
16916 display_map.remove_blocks(block_ids, cx)
16917 });
16918 if let Some(autoscroll) = autoscroll {
16919 self.request_autoscroll(autoscroll, cx);
16920 }
16921 cx.notify();
16922 }
16923
16924 pub fn row_for_block(
16925 &self,
16926 block_id: CustomBlockId,
16927 cx: &mut Context<Self>,
16928 ) -> Option<DisplayRow> {
16929 self.display_map
16930 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16931 }
16932
16933 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16934 self.focused_block = Some(focused_block);
16935 }
16936
16937 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16938 self.focused_block.take()
16939 }
16940
16941 pub fn insert_creases(
16942 &mut self,
16943 creases: impl IntoIterator<Item = Crease<Anchor>>,
16944 cx: &mut Context<Self>,
16945 ) -> Vec<CreaseId> {
16946 self.display_map
16947 .update(cx, |map, cx| map.insert_creases(creases, cx))
16948 }
16949
16950 pub fn remove_creases(
16951 &mut self,
16952 ids: impl IntoIterator<Item = CreaseId>,
16953 cx: &mut Context<Self>,
16954 ) -> Vec<(CreaseId, Range<Anchor>)> {
16955 self.display_map
16956 .update(cx, |map, cx| map.remove_creases(ids, cx))
16957 }
16958
16959 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16960 self.display_map
16961 .update(cx, |map, cx| map.snapshot(cx))
16962 .longest_row()
16963 }
16964
16965 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16966 self.display_map
16967 .update(cx, |map, cx| map.snapshot(cx))
16968 .max_point()
16969 }
16970
16971 pub fn text(&self, cx: &App) -> String {
16972 self.buffer.read(cx).read(cx).text()
16973 }
16974
16975 pub fn is_empty(&self, cx: &App) -> bool {
16976 self.buffer.read(cx).read(cx).is_empty()
16977 }
16978
16979 pub fn text_option(&self, cx: &App) -> Option<String> {
16980 let text = self.text(cx);
16981 let text = text.trim();
16982
16983 if text.is_empty() {
16984 return None;
16985 }
16986
16987 Some(text.to_string())
16988 }
16989
16990 pub fn set_text(
16991 &mut self,
16992 text: impl Into<Arc<str>>,
16993 window: &mut Window,
16994 cx: &mut Context<Self>,
16995 ) {
16996 self.transact(window, cx, |this, _, cx| {
16997 this.buffer
16998 .read(cx)
16999 .as_singleton()
17000 .expect("you can only call set_text on editors for singleton buffers")
17001 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17002 });
17003 }
17004
17005 pub fn display_text(&self, cx: &mut App) -> String {
17006 self.display_map
17007 .update(cx, |map, cx| map.snapshot(cx))
17008 .text()
17009 }
17010
17011 fn create_minimap(
17012 &self,
17013 minimap_settings: MinimapSettings,
17014 window: &mut Window,
17015 cx: &mut Context<Self>,
17016 ) -> Option<Entity<Self>> {
17017 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17018 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17019 }
17020
17021 fn initialize_new_minimap(
17022 &self,
17023 minimap_settings: MinimapSettings,
17024 window: &mut Window,
17025 cx: &mut Context<Self>,
17026 ) -> Entity<Self> {
17027 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17028
17029 let mut minimap = Editor::new_internal(
17030 EditorMode::Minimap {
17031 parent: cx.weak_entity(),
17032 },
17033 self.buffer.clone(),
17034 self.project.clone(),
17035 Some(self.display_map.clone()),
17036 window,
17037 cx,
17038 );
17039 minimap.scroll_manager.clone_state(&self.scroll_manager);
17040 minimap.set_text_style_refinement(TextStyleRefinement {
17041 font_size: Some(MINIMAP_FONT_SIZE),
17042 font_weight: Some(MINIMAP_FONT_WEIGHT),
17043 ..Default::default()
17044 });
17045 minimap.update_minimap_configuration(minimap_settings, cx);
17046 cx.new(|_| minimap)
17047 }
17048
17049 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17050 let current_line_highlight = minimap_settings
17051 .current_line_highlight
17052 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17053 self.set_current_line_highlight(Some(current_line_highlight));
17054 }
17055
17056 pub fn minimap(&self) -> Option<&Entity<Self>> {
17057 self.minimap
17058 .as_ref()
17059 .filter(|_| self.minimap_visibility.visible())
17060 }
17061
17062 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17063 let mut wrap_guides = smallvec![];
17064
17065 if self.show_wrap_guides == Some(false) {
17066 return wrap_guides;
17067 }
17068
17069 let settings = self.buffer.read(cx).language_settings(cx);
17070 if settings.show_wrap_guides {
17071 match self.soft_wrap_mode(cx) {
17072 SoftWrap::Column(soft_wrap) => {
17073 wrap_guides.push((soft_wrap as usize, true));
17074 }
17075 SoftWrap::Bounded(soft_wrap) => {
17076 wrap_guides.push((soft_wrap as usize, true));
17077 }
17078 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17079 }
17080 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17081 }
17082
17083 wrap_guides
17084 }
17085
17086 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17087 let settings = self.buffer.read(cx).language_settings(cx);
17088 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17089 match mode {
17090 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17091 SoftWrap::None
17092 }
17093 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17094 language_settings::SoftWrap::PreferredLineLength => {
17095 SoftWrap::Column(settings.preferred_line_length)
17096 }
17097 language_settings::SoftWrap::Bounded => {
17098 SoftWrap::Bounded(settings.preferred_line_length)
17099 }
17100 }
17101 }
17102
17103 pub fn set_soft_wrap_mode(
17104 &mut self,
17105 mode: language_settings::SoftWrap,
17106
17107 cx: &mut Context<Self>,
17108 ) {
17109 self.soft_wrap_mode_override = Some(mode);
17110 cx.notify();
17111 }
17112
17113 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17114 self.hard_wrap = hard_wrap;
17115 cx.notify();
17116 }
17117
17118 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17119 self.text_style_refinement = Some(style);
17120 }
17121
17122 /// called by the Element so we know what style we were most recently rendered with.
17123 pub(crate) fn set_style(
17124 &mut self,
17125 style: EditorStyle,
17126 window: &mut Window,
17127 cx: &mut Context<Self>,
17128 ) {
17129 // We intentionally do not inform the display map about the minimap style
17130 // so that wrapping is not recalculated and stays consistent for the editor
17131 // and its linked minimap.
17132 if !self.mode.is_minimap() {
17133 let rem_size = window.rem_size();
17134 self.display_map.update(cx, |map, cx| {
17135 map.set_font(
17136 style.text.font(),
17137 style.text.font_size.to_pixels(rem_size),
17138 cx,
17139 )
17140 });
17141 }
17142 self.style = Some(style);
17143 }
17144
17145 pub fn style(&self) -> Option<&EditorStyle> {
17146 self.style.as_ref()
17147 }
17148
17149 // Called by the element. This method is not designed to be called outside of the editor
17150 // element's layout code because it does not notify when rewrapping is computed synchronously.
17151 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17152 self.display_map
17153 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17154 }
17155
17156 pub fn set_soft_wrap(&mut self) {
17157 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17158 }
17159
17160 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17161 if self.soft_wrap_mode_override.is_some() {
17162 self.soft_wrap_mode_override.take();
17163 } else {
17164 let soft_wrap = match self.soft_wrap_mode(cx) {
17165 SoftWrap::GitDiff => return,
17166 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17167 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17168 language_settings::SoftWrap::None
17169 }
17170 };
17171 self.soft_wrap_mode_override = Some(soft_wrap);
17172 }
17173 cx.notify();
17174 }
17175
17176 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17177 let Some(workspace) = self.workspace() else {
17178 return;
17179 };
17180 let fs = workspace.read(cx).app_state().fs.clone();
17181 let current_show = TabBarSettings::get_global(cx).show;
17182 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17183 setting.show = Some(!current_show);
17184 });
17185 }
17186
17187 pub fn toggle_indent_guides(
17188 &mut self,
17189 _: &ToggleIndentGuides,
17190 _: &mut Window,
17191 cx: &mut Context<Self>,
17192 ) {
17193 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17194 self.buffer
17195 .read(cx)
17196 .language_settings(cx)
17197 .indent_guides
17198 .enabled
17199 });
17200 self.show_indent_guides = Some(!currently_enabled);
17201 cx.notify();
17202 }
17203
17204 fn should_show_indent_guides(&self) -> Option<bool> {
17205 self.show_indent_guides
17206 }
17207
17208 pub fn toggle_line_numbers(
17209 &mut self,
17210 _: &ToggleLineNumbers,
17211 _: &mut Window,
17212 cx: &mut Context<Self>,
17213 ) {
17214 let mut editor_settings = EditorSettings::get_global(cx).clone();
17215 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17216 EditorSettings::override_global(editor_settings, cx);
17217 }
17218
17219 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17220 if let Some(show_line_numbers) = self.show_line_numbers {
17221 return show_line_numbers;
17222 }
17223 EditorSettings::get_global(cx).gutter.line_numbers
17224 }
17225
17226 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17227 self.use_relative_line_numbers
17228 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17229 }
17230
17231 pub fn toggle_relative_line_numbers(
17232 &mut self,
17233 _: &ToggleRelativeLineNumbers,
17234 _: &mut Window,
17235 cx: &mut Context<Self>,
17236 ) {
17237 let is_relative = self.should_use_relative_line_numbers(cx);
17238 self.set_relative_line_number(Some(!is_relative), cx)
17239 }
17240
17241 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17242 self.use_relative_line_numbers = is_relative;
17243 cx.notify();
17244 }
17245
17246 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17247 self.show_gutter = show_gutter;
17248 cx.notify();
17249 }
17250
17251 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17252 self.show_scrollbars = ScrollbarAxes {
17253 horizontal: show,
17254 vertical: show,
17255 };
17256 cx.notify();
17257 }
17258
17259 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17260 self.show_scrollbars.vertical = show;
17261 cx.notify();
17262 }
17263
17264 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17265 self.show_scrollbars.horizontal = show;
17266 cx.notify();
17267 }
17268
17269 pub fn set_minimap_visibility(
17270 &mut self,
17271 minimap_visibility: MinimapVisibility,
17272 window: &mut Window,
17273 cx: &mut Context<Self>,
17274 ) {
17275 if self.minimap_visibility != minimap_visibility {
17276 if minimap_visibility.visible() && self.minimap.is_none() {
17277 let minimap_settings = EditorSettings::get_global(cx).minimap;
17278 self.minimap =
17279 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17280 }
17281 self.minimap_visibility = minimap_visibility;
17282 cx.notify();
17283 }
17284 }
17285
17286 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17287 self.set_show_scrollbars(false, cx);
17288 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17289 }
17290
17291 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17292 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17293 }
17294
17295 /// Normally the text in full mode and auto height editors is padded on the
17296 /// left side by roughly half a character width for improved hit testing.
17297 ///
17298 /// Use this method to disable this for cases where this is not wanted (e.g.
17299 /// if you want to align the editor text with some other text above or below)
17300 /// or if you want to add this padding to single-line editors.
17301 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17302 self.offset_content = offset_content;
17303 cx.notify();
17304 }
17305
17306 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17307 self.show_line_numbers = Some(show_line_numbers);
17308 cx.notify();
17309 }
17310
17311 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17312 self.disable_expand_excerpt_buttons = true;
17313 cx.notify();
17314 }
17315
17316 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17317 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17318 cx.notify();
17319 }
17320
17321 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17322 self.show_code_actions = Some(show_code_actions);
17323 cx.notify();
17324 }
17325
17326 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17327 self.show_runnables = Some(show_runnables);
17328 cx.notify();
17329 }
17330
17331 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17332 self.show_breakpoints = Some(show_breakpoints);
17333 cx.notify();
17334 }
17335
17336 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17337 if self.display_map.read(cx).masked != masked {
17338 self.display_map.update(cx, |map, _| map.masked = masked);
17339 }
17340 cx.notify()
17341 }
17342
17343 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17344 self.show_wrap_guides = Some(show_wrap_guides);
17345 cx.notify();
17346 }
17347
17348 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17349 self.show_indent_guides = Some(show_indent_guides);
17350 cx.notify();
17351 }
17352
17353 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17354 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17355 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17356 if let Some(dir) = file.abs_path(cx).parent() {
17357 return Some(dir.to_owned());
17358 }
17359 }
17360
17361 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17362 return Some(project_path.path.to_path_buf());
17363 }
17364 }
17365
17366 None
17367 }
17368
17369 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17370 self.active_excerpt(cx)?
17371 .1
17372 .read(cx)
17373 .file()
17374 .and_then(|f| f.as_local())
17375 }
17376
17377 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17378 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17379 let buffer = buffer.read(cx);
17380 if let Some(project_path) = buffer.project_path(cx) {
17381 let project = self.project.as_ref()?.read(cx);
17382 project.absolute_path(&project_path, cx)
17383 } else {
17384 buffer
17385 .file()
17386 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17387 }
17388 })
17389 }
17390
17391 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17392 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17393 let project_path = buffer.read(cx).project_path(cx)?;
17394 let project = self.project.as_ref()?.read(cx);
17395 let entry = project.entry_for_path(&project_path, cx)?;
17396 let path = entry.path.to_path_buf();
17397 Some(path)
17398 })
17399 }
17400
17401 pub fn reveal_in_finder(
17402 &mut self,
17403 _: &RevealInFileManager,
17404 _window: &mut Window,
17405 cx: &mut Context<Self>,
17406 ) {
17407 if let Some(target) = self.target_file(cx) {
17408 cx.reveal_path(&target.abs_path(cx));
17409 }
17410 }
17411
17412 pub fn copy_path(
17413 &mut self,
17414 _: &zed_actions::workspace::CopyPath,
17415 _window: &mut Window,
17416 cx: &mut Context<Self>,
17417 ) {
17418 if let Some(path) = self.target_file_abs_path(cx) {
17419 if let Some(path) = path.to_str() {
17420 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17421 }
17422 }
17423 }
17424
17425 pub fn copy_relative_path(
17426 &mut self,
17427 _: &zed_actions::workspace::CopyRelativePath,
17428 _window: &mut Window,
17429 cx: &mut Context<Self>,
17430 ) {
17431 if let Some(path) = self.target_file_path(cx) {
17432 if let Some(path) = path.to_str() {
17433 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17434 }
17435 }
17436 }
17437
17438 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17439 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17440 buffer.read(cx).project_path(cx)
17441 } else {
17442 None
17443 }
17444 }
17445
17446 // Returns true if the editor handled a go-to-line request
17447 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17448 maybe!({
17449 let breakpoint_store = self.breakpoint_store.as_ref()?;
17450
17451 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17452 else {
17453 self.clear_row_highlights::<ActiveDebugLine>();
17454 return None;
17455 };
17456
17457 let position = active_stack_frame.position;
17458 let buffer_id = position.buffer_id?;
17459 let snapshot = self
17460 .project
17461 .as_ref()?
17462 .read(cx)
17463 .buffer_for_id(buffer_id, cx)?
17464 .read(cx)
17465 .snapshot();
17466
17467 let mut handled = false;
17468 for (id, ExcerptRange { context, .. }) in
17469 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17470 {
17471 if context.start.cmp(&position, &snapshot).is_ge()
17472 || context.end.cmp(&position, &snapshot).is_lt()
17473 {
17474 continue;
17475 }
17476 let snapshot = self.buffer.read(cx).snapshot(cx);
17477 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17478
17479 handled = true;
17480 self.clear_row_highlights::<ActiveDebugLine>();
17481
17482 self.go_to_line::<ActiveDebugLine>(
17483 multibuffer_anchor,
17484 Some(cx.theme().colors().editor_debugger_active_line_background),
17485 window,
17486 cx,
17487 );
17488
17489 cx.notify();
17490 }
17491
17492 handled.then_some(())
17493 })
17494 .is_some()
17495 }
17496
17497 pub fn copy_file_name_without_extension(
17498 &mut self,
17499 _: &CopyFileNameWithoutExtension,
17500 _: &mut Window,
17501 cx: &mut Context<Self>,
17502 ) {
17503 if let Some(file) = self.target_file(cx) {
17504 if let Some(file_stem) = file.path().file_stem() {
17505 if let Some(name) = file_stem.to_str() {
17506 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17507 }
17508 }
17509 }
17510 }
17511
17512 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17513 if let Some(file) = self.target_file(cx) {
17514 if let Some(file_name) = file.path().file_name() {
17515 if let Some(name) = file_name.to_str() {
17516 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17517 }
17518 }
17519 }
17520 }
17521
17522 pub fn toggle_git_blame(
17523 &mut self,
17524 _: &::git::Blame,
17525 window: &mut Window,
17526 cx: &mut Context<Self>,
17527 ) {
17528 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17529
17530 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17531 self.start_git_blame(true, window, cx);
17532 }
17533
17534 cx.notify();
17535 }
17536
17537 pub fn toggle_git_blame_inline(
17538 &mut self,
17539 _: &ToggleGitBlameInline,
17540 window: &mut Window,
17541 cx: &mut Context<Self>,
17542 ) {
17543 self.toggle_git_blame_inline_internal(true, window, cx);
17544 cx.notify();
17545 }
17546
17547 pub fn open_git_blame_commit(
17548 &mut self,
17549 _: &OpenGitBlameCommit,
17550 window: &mut Window,
17551 cx: &mut Context<Self>,
17552 ) {
17553 self.open_git_blame_commit_internal(window, cx);
17554 }
17555
17556 fn open_git_blame_commit_internal(
17557 &mut self,
17558 window: &mut Window,
17559 cx: &mut Context<Self>,
17560 ) -> Option<()> {
17561 let blame = self.blame.as_ref()?;
17562 let snapshot = self.snapshot(window, cx);
17563 let cursor = self.selections.newest::<Point>(cx).head();
17564 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17565 let blame_entry = blame
17566 .update(cx, |blame, cx| {
17567 blame
17568 .blame_for_rows(
17569 &[RowInfo {
17570 buffer_id: Some(buffer.remote_id()),
17571 buffer_row: Some(point.row),
17572 ..Default::default()
17573 }],
17574 cx,
17575 )
17576 .next()
17577 })
17578 .flatten()?;
17579 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17580 let repo = blame.read(cx).repository(cx)?;
17581 let workspace = self.workspace()?.downgrade();
17582 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17583 None
17584 }
17585
17586 pub fn git_blame_inline_enabled(&self) -> bool {
17587 self.git_blame_inline_enabled
17588 }
17589
17590 pub fn toggle_selection_menu(
17591 &mut self,
17592 _: &ToggleSelectionMenu,
17593 _: &mut Window,
17594 cx: &mut Context<Self>,
17595 ) {
17596 self.show_selection_menu = self
17597 .show_selection_menu
17598 .map(|show_selections_menu| !show_selections_menu)
17599 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17600
17601 cx.notify();
17602 }
17603
17604 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17605 self.show_selection_menu
17606 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17607 }
17608
17609 fn start_git_blame(
17610 &mut self,
17611 user_triggered: bool,
17612 window: &mut Window,
17613 cx: &mut Context<Self>,
17614 ) {
17615 if let Some(project) = self.project.as_ref() {
17616 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17617 return;
17618 };
17619
17620 if buffer.read(cx).file().is_none() {
17621 return;
17622 }
17623
17624 let focused = self.focus_handle(cx).contains_focused(window, cx);
17625
17626 let project = project.clone();
17627 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17628 self.blame_subscription =
17629 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17630 self.blame = Some(blame);
17631 }
17632 }
17633
17634 fn toggle_git_blame_inline_internal(
17635 &mut self,
17636 user_triggered: bool,
17637 window: &mut Window,
17638 cx: &mut Context<Self>,
17639 ) {
17640 if self.git_blame_inline_enabled {
17641 self.git_blame_inline_enabled = false;
17642 self.show_git_blame_inline = false;
17643 self.show_git_blame_inline_delay_task.take();
17644 } else {
17645 self.git_blame_inline_enabled = true;
17646 self.start_git_blame_inline(user_triggered, window, cx);
17647 }
17648
17649 cx.notify();
17650 }
17651
17652 fn start_git_blame_inline(
17653 &mut self,
17654 user_triggered: bool,
17655 window: &mut Window,
17656 cx: &mut Context<Self>,
17657 ) {
17658 self.start_git_blame(user_triggered, window, cx);
17659
17660 if ProjectSettings::get_global(cx)
17661 .git
17662 .inline_blame_delay()
17663 .is_some()
17664 {
17665 self.start_inline_blame_timer(window, cx);
17666 } else {
17667 self.show_git_blame_inline = true
17668 }
17669 }
17670
17671 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17672 self.blame.as_ref()
17673 }
17674
17675 pub fn show_git_blame_gutter(&self) -> bool {
17676 self.show_git_blame_gutter
17677 }
17678
17679 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17680 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17681 }
17682
17683 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17684 self.show_git_blame_inline
17685 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17686 && !self.newest_selection_head_on_empty_line(cx)
17687 && self.has_blame_entries(cx)
17688 }
17689
17690 fn has_blame_entries(&self, cx: &App) -> bool {
17691 self.blame()
17692 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17693 }
17694
17695 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17696 let cursor_anchor = self.selections.newest_anchor().head();
17697
17698 let snapshot = self.buffer.read(cx).snapshot(cx);
17699 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17700
17701 snapshot.line_len(buffer_row) == 0
17702 }
17703
17704 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17705 let buffer_and_selection = maybe!({
17706 let selection = self.selections.newest::<Point>(cx);
17707 let selection_range = selection.range();
17708
17709 let multi_buffer = self.buffer().read(cx);
17710 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17711 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17712
17713 let (buffer, range, _) = if selection.reversed {
17714 buffer_ranges.first()
17715 } else {
17716 buffer_ranges.last()
17717 }?;
17718
17719 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17720 ..text::ToPoint::to_point(&range.end, &buffer).row;
17721 Some((
17722 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17723 selection,
17724 ))
17725 });
17726
17727 let Some((buffer, selection)) = buffer_and_selection else {
17728 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17729 };
17730
17731 let Some(project) = self.project.as_ref() else {
17732 return Task::ready(Err(anyhow!("editor does not have project")));
17733 };
17734
17735 project.update(cx, |project, cx| {
17736 project.get_permalink_to_line(&buffer, selection, cx)
17737 })
17738 }
17739
17740 pub fn copy_permalink_to_line(
17741 &mut self,
17742 _: &CopyPermalinkToLine,
17743 window: &mut Window,
17744 cx: &mut Context<Self>,
17745 ) {
17746 let permalink_task = self.get_permalink_to_line(cx);
17747 let workspace = self.workspace();
17748
17749 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17750 Ok(permalink) => {
17751 cx.update(|_, cx| {
17752 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17753 })
17754 .ok();
17755 }
17756 Err(err) => {
17757 let message = format!("Failed to copy permalink: {err}");
17758
17759 anyhow::Result::<()>::Err(err).log_err();
17760
17761 if let Some(workspace) = workspace {
17762 workspace
17763 .update_in(cx, |workspace, _, cx| {
17764 struct CopyPermalinkToLine;
17765
17766 workspace.show_toast(
17767 Toast::new(
17768 NotificationId::unique::<CopyPermalinkToLine>(),
17769 message,
17770 ),
17771 cx,
17772 )
17773 })
17774 .ok();
17775 }
17776 }
17777 })
17778 .detach();
17779 }
17780
17781 pub fn copy_file_location(
17782 &mut self,
17783 _: &CopyFileLocation,
17784 _: &mut Window,
17785 cx: &mut Context<Self>,
17786 ) {
17787 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17788 if let Some(file) = self.target_file(cx) {
17789 if let Some(path) = file.path().to_str() {
17790 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17791 }
17792 }
17793 }
17794
17795 pub fn open_permalink_to_line(
17796 &mut self,
17797 _: &OpenPermalinkToLine,
17798 window: &mut Window,
17799 cx: &mut Context<Self>,
17800 ) {
17801 let permalink_task = self.get_permalink_to_line(cx);
17802 let workspace = self.workspace();
17803
17804 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17805 Ok(permalink) => {
17806 cx.update(|_, cx| {
17807 cx.open_url(permalink.as_ref());
17808 })
17809 .ok();
17810 }
17811 Err(err) => {
17812 let message = format!("Failed to open permalink: {err}");
17813
17814 anyhow::Result::<()>::Err(err).log_err();
17815
17816 if let Some(workspace) = workspace {
17817 workspace
17818 .update(cx, |workspace, cx| {
17819 struct OpenPermalinkToLine;
17820
17821 workspace.show_toast(
17822 Toast::new(
17823 NotificationId::unique::<OpenPermalinkToLine>(),
17824 message,
17825 ),
17826 cx,
17827 )
17828 })
17829 .ok();
17830 }
17831 }
17832 })
17833 .detach();
17834 }
17835
17836 pub fn insert_uuid_v4(
17837 &mut self,
17838 _: &InsertUuidV4,
17839 window: &mut Window,
17840 cx: &mut Context<Self>,
17841 ) {
17842 self.insert_uuid(UuidVersion::V4, window, cx);
17843 }
17844
17845 pub fn insert_uuid_v7(
17846 &mut self,
17847 _: &InsertUuidV7,
17848 window: &mut Window,
17849 cx: &mut Context<Self>,
17850 ) {
17851 self.insert_uuid(UuidVersion::V7, window, cx);
17852 }
17853
17854 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17855 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17856 self.transact(window, cx, |this, window, cx| {
17857 let edits = this
17858 .selections
17859 .all::<Point>(cx)
17860 .into_iter()
17861 .map(|selection| {
17862 let uuid = match version {
17863 UuidVersion::V4 => uuid::Uuid::new_v4(),
17864 UuidVersion::V7 => uuid::Uuid::now_v7(),
17865 };
17866
17867 (selection.range(), uuid.to_string())
17868 });
17869 this.edit(edits, cx);
17870 this.refresh_inline_completion(true, false, window, cx);
17871 });
17872 }
17873
17874 pub fn open_selections_in_multibuffer(
17875 &mut self,
17876 _: &OpenSelectionsInMultibuffer,
17877 window: &mut Window,
17878 cx: &mut Context<Self>,
17879 ) {
17880 let multibuffer = self.buffer.read(cx);
17881
17882 let Some(buffer) = multibuffer.as_singleton() else {
17883 return;
17884 };
17885
17886 let Some(workspace) = self.workspace() else {
17887 return;
17888 };
17889
17890 let locations = self
17891 .selections
17892 .disjoint_anchors()
17893 .iter()
17894 .map(|selection| {
17895 let range = if selection.reversed {
17896 selection.end.text_anchor..selection.start.text_anchor
17897 } else {
17898 selection.start.text_anchor..selection.end.text_anchor
17899 };
17900 Location {
17901 buffer: buffer.clone(),
17902 range,
17903 }
17904 })
17905 .collect::<Vec<_>>();
17906
17907 let title = multibuffer.title(cx).to_string();
17908
17909 cx.spawn_in(window, async move |_, cx| {
17910 workspace.update_in(cx, |workspace, window, cx| {
17911 Self::open_locations_in_multibuffer(
17912 workspace,
17913 locations,
17914 format!("Selections for '{title}'"),
17915 false,
17916 MultibufferSelectionMode::All,
17917 window,
17918 cx,
17919 );
17920 })
17921 })
17922 .detach();
17923 }
17924
17925 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17926 /// last highlight added will be used.
17927 ///
17928 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17929 pub fn highlight_rows<T: 'static>(
17930 &mut self,
17931 range: Range<Anchor>,
17932 color: Hsla,
17933 options: RowHighlightOptions,
17934 cx: &mut Context<Self>,
17935 ) {
17936 let snapshot = self.buffer().read(cx).snapshot(cx);
17937 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17938 let ix = row_highlights.binary_search_by(|highlight| {
17939 Ordering::Equal
17940 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17941 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17942 });
17943
17944 if let Err(mut ix) = ix {
17945 let index = post_inc(&mut self.highlight_order);
17946
17947 // If this range intersects with the preceding highlight, then merge it with
17948 // the preceding highlight. Otherwise insert a new highlight.
17949 let mut merged = false;
17950 if ix > 0 {
17951 let prev_highlight = &mut row_highlights[ix - 1];
17952 if prev_highlight
17953 .range
17954 .end
17955 .cmp(&range.start, &snapshot)
17956 .is_ge()
17957 {
17958 ix -= 1;
17959 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17960 prev_highlight.range.end = range.end;
17961 }
17962 merged = true;
17963 prev_highlight.index = index;
17964 prev_highlight.color = color;
17965 prev_highlight.options = options;
17966 }
17967 }
17968
17969 if !merged {
17970 row_highlights.insert(
17971 ix,
17972 RowHighlight {
17973 range: range.clone(),
17974 index,
17975 color,
17976 options,
17977 type_id: TypeId::of::<T>(),
17978 },
17979 );
17980 }
17981
17982 // If any of the following highlights intersect with this one, merge them.
17983 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17984 let highlight = &row_highlights[ix];
17985 if next_highlight
17986 .range
17987 .start
17988 .cmp(&highlight.range.end, &snapshot)
17989 .is_le()
17990 {
17991 if next_highlight
17992 .range
17993 .end
17994 .cmp(&highlight.range.end, &snapshot)
17995 .is_gt()
17996 {
17997 row_highlights[ix].range.end = next_highlight.range.end;
17998 }
17999 row_highlights.remove(ix + 1);
18000 } else {
18001 break;
18002 }
18003 }
18004 }
18005 }
18006
18007 /// Remove any highlighted row ranges of the given type that intersect the
18008 /// given ranges.
18009 pub fn remove_highlighted_rows<T: 'static>(
18010 &mut self,
18011 ranges_to_remove: Vec<Range<Anchor>>,
18012 cx: &mut Context<Self>,
18013 ) {
18014 let snapshot = self.buffer().read(cx).snapshot(cx);
18015 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18016 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18017 row_highlights.retain(|highlight| {
18018 while let Some(range_to_remove) = ranges_to_remove.peek() {
18019 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18020 Ordering::Less | Ordering::Equal => {
18021 ranges_to_remove.next();
18022 }
18023 Ordering::Greater => {
18024 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18025 Ordering::Less | Ordering::Equal => {
18026 return false;
18027 }
18028 Ordering::Greater => break,
18029 }
18030 }
18031 }
18032 }
18033
18034 true
18035 })
18036 }
18037
18038 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18039 pub fn clear_row_highlights<T: 'static>(&mut self) {
18040 self.highlighted_rows.remove(&TypeId::of::<T>());
18041 }
18042
18043 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18044 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18045 self.highlighted_rows
18046 .get(&TypeId::of::<T>())
18047 .map_or(&[] as &[_], |vec| vec.as_slice())
18048 .iter()
18049 .map(|highlight| (highlight.range.clone(), highlight.color))
18050 }
18051
18052 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18053 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18054 /// Allows to ignore certain kinds of highlights.
18055 pub fn highlighted_display_rows(
18056 &self,
18057 window: &mut Window,
18058 cx: &mut App,
18059 ) -> BTreeMap<DisplayRow, LineHighlight> {
18060 let snapshot = self.snapshot(window, cx);
18061 let mut used_highlight_orders = HashMap::default();
18062 self.highlighted_rows
18063 .iter()
18064 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18065 .fold(
18066 BTreeMap::<DisplayRow, LineHighlight>::new(),
18067 |mut unique_rows, highlight| {
18068 let start = highlight.range.start.to_display_point(&snapshot);
18069 let end = highlight.range.end.to_display_point(&snapshot);
18070 let start_row = start.row().0;
18071 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18072 && end.column() == 0
18073 {
18074 end.row().0.saturating_sub(1)
18075 } else {
18076 end.row().0
18077 };
18078 for row in start_row..=end_row {
18079 let used_index =
18080 used_highlight_orders.entry(row).or_insert(highlight.index);
18081 if highlight.index >= *used_index {
18082 *used_index = highlight.index;
18083 unique_rows.insert(
18084 DisplayRow(row),
18085 LineHighlight {
18086 include_gutter: highlight.options.include_gutter,
18087 border: None,
18088 background: highlight.color.into(),
18089 type_id: Some(highlight.type_id),
18090 },
18091 );
18092 }
18093 }
18094 unique_rows
18095 },
18096 )
18097 }
18098
18099 pub fn highlighted_display_row_for_autoscroll(
18100 &self,
18101 snapshot: &DisplaySnapshot,
18102 ) -> Option<DisplayRow> {
18103 self.highlighted_rows
18104 .values()
18105 .flat_map(|highlighted_rows| highlighted_rows.iter())
18106 .filter_map(|highlight| {
18107 if highlight.options.autoscroll {
18108 Some(highlight.range.start.to_display_point(snapshot).row())
18109 } else {
18110 None
18111 }
18112 })
18113 .min()
18114 }
18115
18116 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18117 self.highlight_background::<SearchWithinRange>(
18118 ranges,
18119 |colors| colors.editor_document_highlight_read_background,
18120 cx,
18121 )
18122 }
18123
18124 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18125 self.breadcrumb_header = Some(new_header);
18126 }
18127
18128 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18129 self.clear_background_highlights::<SearchWithinRange>(cx);
18130 }
18131
18132 pub fn highlight_background<T: 'static>(
18133 &mut self,
18134 ranges: &[Range<Anchor>],
18135 color_fetcher: fn(&ThemeColors) -> Hsla,
18136 cx: &mut Context<Self>,
18137 ) {
18138 self.background_highlights
18139 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18140 self.scrollbar_marker_state.dirty = true;
18141 cx.notify();
18142 }
18143
18144 pub fn clear_background_highlights<T: 'static>(
18145 &mut self,
18146 cx: &mut Context<Self>,
18147 ) -> Option<BackgroundHighlight> {
18148 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18149 if !text_highlights.1.is_empty() {
18150 self.scrollbar_marker_state.dirty = true;
18151 cx.notify();
18152 }
18153 Some(text_highlights)
18154 }
18155
18156 pub fn highlight_gutter<T: 'static>(
18157 &mut self,
18158 ranges: &[Range<Anchor>],
18159 color_fetcher: fn(&App) -> Hsla,
18160 cx: &mut Context<Self>,
18161 ) {
18162 self.gutter_highlights
18163 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18164 cx.notify();
18165 }
18166
18167 pub fn clear_gutter_highlights<T: 'static>(
18168 &mut self,
18169 cx: &mut Context<Self>,
18170 ) -> Option<GutterHighlight> {
18171 cx.notify();
18172 self.gutter_highlights.remove(&TypeId::of::<T>())
18173 }
18174
18175 #[cfg(feature = "test-support")]
18176 pub fn all_text_background_highlights(
18177 &self,
18178 window: &mut Window,
18179 cx: &mut Context<Self>,
18180 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18181 let snapshot = self.snapshot(window, cx);
18182 let buffer = &snapshot.buffer_snapshot;
18183 let start = buffer.anchor_before(0);
18184 let end = buffer.anchor_after(buffer.len());
18185 let theme = cx.theme().colors();
18186 self.background_highlights_in_range(start..end, &snapshot, theme)
18187 }
18188
18189 #[cfg(feature = "test-support")]
18190 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18191 let snapshot = self.buffer().read(cx).snapshot(cx);
18192
18193 let highlights = self
18194 .background_highlights
18195 .get(&TypeId::of::<items::BufferSearchHighlights>());
18196
18197 if let Some((_color, ranges)) = highlights {
18198 ranges
18199 .iter()
18200 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18201 .collect_vec()
18202 } else {
18203 vec![]
18204 }
18205 }
18206
18207 fn document_highlights_for_position<'a>(
18208 &'a self,
18209 position: Anchor,
18210 buffer: &'a MultiBufferSnapshot,
18211 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18212 let read_highlights = self
18213 .background_highlights
18214 .get(&TypeId::of::<DocumentHighlightRead>())
18215 .map(|h| &h.1);
18216 let write_highlights = self
18217 .background_highlights
18218 .get(&TypeId::of::<DocumentHighlightWrite>())
18219 .map(|h| &h.1);
18220 let left_position = position.bias_left(buffer);
18221 let right_position = position.bias_right(buffer);
18222 read_highlights
18223 .into_iter()
18224 .chain(write_highlights)
18225 .flat_map(move |ranges| {
18226 let start_ix = match ranges.binary_search_by(|probe| {
18227 let cmp = probe.end.cmp(&left_position, buffer);
18228 if cmp.is_ge() {
18229 Ordering::Greater
18230 } else {
18231 Ordering::Less
18232 }
18233 }) {
18234 Ok(i) | Err(i) => i,
18235 };
18236
18237 ranges[start_ix..]
18238 .iter()
18239 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18240 })
18241 }
18242
18243 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18244 self.background_highlights
18245 .get(&TypeId::of::<T>())
18246 .map_or(false, |(_, highlights)| !highlights.is_empty())
18247 }
18248
18249 pub fn background_highlights_in_range(
18250 &self,
18251 search_range: Range<Anchor>,
18252 display_snapshot: &DisplaySnapshot,
18253 theme: &ThemeColors,
18254 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18255 let mut results = Vec::new();
18256 for (color_fetcher, ranges) in self.background_highlights.values() {
18257 let color = color_fetcher(theme);
18258 let start_ix = match ranges.binary_search_by(|probe| {
18259 let cmp = probe
18260 .end
18261 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18262 if cmp.is_gt() {
18263 Ordering::Greater
18264 } else {
18265 Ordering::Less
18266 }
18267 }) {
18268 Ok(i) | Err(i) => i,
18269 };
18270 for range in &ranges[start_ix..] {
18271 if range
18272 .start
18273 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18274 .is_ge()
18275 {
18276 break;
18277 }
18278
18279 let start = range.start.to_display_point(display_snapshot);
18280 let end = range.end.to_display_point(display_snapshot);
18281 results.push((start..end, color))
18282 }
18283 }
18284 results
18285 }
18286
18287 pub fn background_highlight_row_ranges<T: 'static>(
18288 &self,
18289 search_range: Range<Anchor>,
18290 display_snapshot: &DisplaySnapshot,
18291 count: usize,
18292 ) -> Vec<RangeInclusive<DisplayPoint>> {
18293 let mut results = Vec::new();
18294 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18295 return vec![];
18296 };
18297
18298 let start_ix = match ranges.binary_search_by(|probe| {
18299 let cmp = probe
18300 .end
18301 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18302 if cmp.is_gt() {
18303 Ordering::Greater
18304 } else {
18305 Ordering::Less
18306 }
18307 }) {
18308 Ok(i) | Err(i) => i,
18309 };
18310 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18311 if let (Some(start_display), Some(end_display)) = (start, end) {
18312 results.push(
18313 start_display.to_display_point(display_snapshot)
18314 ..=end_display.to_display_point(display_snapshot),
18315 );
18316 }
18317 };
18318 let mut start_row: Option<Point> = None;
18319 let mut end_row: Option<Point> = None;
18320 if ranges.len() > count {
18321 return Vec::new();
18322 }
18323 for range in &ranges[start_ix..] {
18324 if range
18325 .start
18326 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18327 .is_ge()
18328 {
18329 break;
18330 }
18331 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18332 if let Some(current_row) = &end_row {
18333 if end.row == current_row.row {
18334 continue;
18335 }
18336 }
18337 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18338 if start_row.is_none() {
18339 assert_eq!(end_row, None);
18340 start_row = Some(start);
18341 end_row = Some(end);
18342 continue;
18343 }
18344 if let Some(current_end) = end_row.as_mut() {
18345 if start.row > current_end.row + 1 {
18346 push_region(start_row, end_row);
18347 start_row = Some(start);
18348 end_row = Some(end);
18349 } else {
18350 // Merge two hunks.
18351 *current_end = end;
18352 }
18353 } else {
18354 unreachable!();
18355 }
18356 }
18357 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18358 push_region(start_row, end_row);
18359 results
18360 }
18361
18362 pub fn gutter_highlights_in_range(
18363 &self,
18364 search_range: Range<Anchor>,
18365 display_snapshot: &DisplaySnapshot,
18366 cx: &App,
18367 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18368 let mut results = Vec::new();
18369 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18370 let color = color_fetcher(cx);
18371 let start_ix = match ranges.binary_search_by(|probe| {
18372 let cmp = probe
18373 .end
18374 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18375 if cmp.is_gt() {
18376 Ordering::Greater
18377 } else {
18378 Ordering::Less
18379 }
18380 }) {
18381 Ok(i) | Err(i) => i,
18382 };
18383 for range in &ranges[start_ix..] {
18384 if range
18385 .start
18386 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18387 .is_ge()
18388 {
18389 break;
18390 }
18391
18392 let start = range.start.to_display_point(display_snapshot);
18393 let end = range.end.to_display_point(display_snapshot);
18394 results.push((start..end, color))
18395 }
18396 }
18397 results
18398 }
18399
18400 /// Get the text ranges corresponding to the redaction query
18401 pub fn redacted_ranges(
18402 &self,
18403 search_range: Range<Anchor>,
18404 display_snapshot: &DisplaySnapshot,
18405 cx: &App,
18406 ) -> Vec<Range<DisplayPoint>> {
18407 display_snapshot
18408 .buffer_snapshot
18409 .redacted_ranges(search_range, |file| {
18410 if let Some(file) = file {
18411 file.is_private()
18412 && EditorSettings::get(
18413 Some(SettingsLocation {
18414 worktree_id: file.worktree_id(cx),
18415 path: file.path().as_ref(),
18416 }),
18417 cx,
18418 )
18419 .redact_private_values
18420 } else {
18421 false
18422 }
18423 })
18424 .map(|range| {
18425 range.start.to_display_point(display_snapshot)
18426 ..range.end.to_display_point(display_snapshot)
18427 })
18428 .collect()
18429 }
18430
18431 pub fn highlight_text<T: 'static>(
18432 &mut self,
18433 ranges: Vec<Range<Anchor>>,
18434 style: HighlightStyle,
18435 cx: &mut Context<Self>,
18436 ) {
18437 self.display_map.update(cx, |map, _| {
18438 map.highlight_text(TypeId::of::<T>(), ranges, style)
18439 });
18440 cx.notify();
18441 }
18442
18443 pub(crate) fn highlight_inlays<T: 'static>(
18444 &mut self,
18445 highlights: Vec<InlayHighlight>,
18446 style: HighlightStyle,
18447 cx: &mut Context<Self>,
18448 ) {
18449 self.display_map.update(cx, |map, _| {
18450 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18451 });
18452 cx.notify();
18453 }
18454
18455 pub fn text_highlights<'a, T: 'static>(
18456 &'a self,
18457 cx: &'a App,
18458 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18459 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18460 }
18461
18462 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18463 let cleared = self
18464 .display_map
18465 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18466 if cleared {
18467 cx.notify();
18468 }
18469 }
18470
18471 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18472 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18473 && self.focus_handle.is_focused(window)
18474 }
18475
18476 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18477 self.show_cursor_when_unfocused = is_enabled;
18478 cx.notify();
18479 }
18480
18481 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18482 cx.notify();
18483 }
18484
18485 fn on_debug_session_event(
18486 &mut self,
18487 _session: Entity<Session>,
18488 event: &SessionEvent,
18489 cx: &mut Context<Self>,
18490 ) {
18491 match event {
18492 SessionEvent::InvalidateInlineValue => {
18493 self.refresh_inline_values(cx);
18494 }
18495 _ => {}
18496 }
18497 }
18498
18499 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18500 let Some(project) = self.project.clone() else {
18501 return;
18502 };
18503
18504 if !self.inline_value_cache.enabled {
18505 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18506 self.splice_inlays(&inlays, Vec::new(), cx);
18507 return;
18508 }
18509
18510 let current_execution_position = self
18511 .highlighted_rows
18512 .get(&TypeId::of::<ActiveDebugLine>())
18513 .and_then(|lines| lines.last().map(|line| line.range.start));
18514
18515 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18516 let inline_values = editor
18517 .update(cx, |editor, cx| {
18518 let Some(current_execution_position) = current_execution_position else {
18519 return Some(Task::ready(Ok(Vec::new())));
18520 };
18521
18522 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18523 let snapshot = buffer.snapshot(cx);
18524
18525 let excerpt = snapshot.excerpt_containing(
18526 current_execution_position..current_execution_position,
18527 )?;
18528
18529 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18530 })?;
18531
18532 let range =
18533 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18534
18535 project.inline_values(buffer, range, cx)
18536 })
18537 .ok()
18538 .flatten()?
18539 .await
18540 .context("refreshing debugger inlays")
18541 .log_err()?;
18542
18543 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18544
18545 for (buffer_id, inline_value) in inline_values
18546 .into_iter()
18547 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18548 {
18549 buffer_inline_values
18550 .entry(buffer_id)
18551 .or_default()
18552 .push(inline_value);
18553 }
18554
18555 editor
18556 .update(cx, |editor, cx| {
18557 let snapshot = editor.buffer.read(cx).snapshot(cx);
18558 let mut new_inlays = Vec::default();
18559
18560 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18561 let buffer_id = buffer_snapshot.remote_id();
18562 buffer_inline_values
18563 .get(&buffer_id)
18564 .into_iter()
18565 .flatten()
18566 .for_each(|hint| {
18567 let inlay = Inlay::debugger_hint(
18568 post_inc(&mut editor.next_inlay_id),
18569 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18570 hint.text(),
18571 );
18572
18573 new_inlays.push(inlay);
18574 });
18575 }
18576
18577 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18578 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18579
18580 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18581 })
18582 .ok()?;
18583 Some(())
18584 });
18585 }
18586
18587 fn on_buffer_event(
18588 &mut self,
18589 multibuffer: &Entity<MultiBuffer>,
18590 event: &multi_buffer::Event,
18591 window: &mut Window,
18592 cx: &mut Context<Self>,
18593 ) {
18594 match event {
18595 multi_buffer::Event::Edited {
18596 singleton_buffer_edited,
18597 edited_buffer: buffer_edited,
18598 } => {
18599 self.scrollbar_marker_state.dirty = true;
18600 self.active_indent_guides_state.dirty = true;
18601 self.refresh_active_diagnostics(cx);
18602 self.refresh_code_actions(window, cx);
18603 self.refresh_selected_text_highlights(true, window, cx);
18604 refresh_matching_bracket_highlights(self, window, cx);
18605 if self.has_active_inline_completion() {
18606 self.update_visible_inline_completion(window, cx);
18607 }
18608 if let Some(buffer) = buffer_edited {
18609 let buffer_id = buffer.read(cx).remote_id();
18610 if !self.registered_buffers.contains_key(&buffer_id) {
18611 if let Some(project) = self.project.as_ref() {
18612 project.update(cx, |project, cx| {
18613 self.registered_buffers.insert(
18614 buffer_id,
18615 project.register_buffer_with_language_servers(&buffer, cx),
18616 );
18617 })
18618 }
18619 }
18620 }
18621 cx.emit(EditorEvent::BufferEdited);
18622 cx.emit(SearchEvent::MatchesInvalidated);
18623 if *singleton_buffer_edited {
18624 if let Some(project) = &self.project {
18625 #[allow(clippy::mutable_key_type)]
18626 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18627 multibuffer
18628 .all_buffers()
18629 .into_iter()
18630 .filter_map(|buffer| {
18631 buffer.update(cx, |buffer, cx| {
18632 let language = buffer.language()?;
18633 let should_discard = project.update(cx, |project, cx| {
18634 project.is_local()
18635 && !project.has_language_servers_for(buffer, cx)
18636 });
18637 should_discard.not().then_some(language.clone())
18638 })
18639 })
18640 .collect::<HashSet<_>>()
18641 });
18642 if !languages_affected.is_empty() {
18643 self.refresh_inlay_hints(
18644 InlayHintRefreshReason::BufferEdited(languages_affected),
18645 cx,
18646 );
18647 }
18648 }
18649 }
18650
18651 let Some(project) = &self.project else { return };
18652 let (telemetry, is_via_ssh) = {
18653 let project = project.read(cx);
18654 let telemetry = project.client().telemetry().clone();
18655 let is_via_ssh = project.is_via_ssh();
18656 (telemetry, is_via_ssh)
18657 };
18658 refresh_linked_ranges(self, window, cx);
18659 telemetry.log_edit_event("editor", is_via_ssh);
18660 }
18661 multi_buffer::Event::ExcerptsAdded {
18662 buffer,
18663 predecessor,
18664 excerpts,
18665 } => {
18666 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18667 let buffer_id = buffer.read(cx).remote_id();
18668 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18669 if let Some(project) = &self.project {
18670 update_uncommitted_diff_for_buffer(
18671 cx.entity(),
18672 project,
18673 [buffer.clone()],
18674 self.buffer.clone(),
18675 cx,
18676 )
18677 .detach();
18678 }
18679 }
18680 cx.emit(EditorEvent::ExcerptsAdded {
18681 buffer: buffer.clone(),
18682 predecessor: *predecessor,
18683 excerpts: excerpts.clone(),
18684 });
18685 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18686 }
18687 multi_buffer::Event::ExcerptsRemoved {
18688 ids,
18689 removed_buffer_ids,
18690 } => {
18691 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18692 let buffer = self.buffer.read(cx);
18693 self.registered_buffers
18694 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18695 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18696 cx.emit(EditorEvent::ExcerptsRemoved {
18697 ids: ids.clone(),
18698 removed_buffer_ids: removed_buffer_ids.clone(),
18699 })
18700 }
18701 multi_buffer::Event::ExcerptsEdited {
18702 excerpt_ids,
18703 buffer_ids,
18704 } => {
18705 self.display_map.update(cx, |map, cx| {
18706 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18707 });
18708 cx.emit(EditorEvent::ExcerptsEdited {
18709 ids: excerpt_ids.clone(),
18710 })
18711 }
18712 multi_buffer::Event::ExcerptsExpanded { ids } => {
18713 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18714 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18715 }
18716 multi_buffer::Event::Reparsed(buffer_id) => {
18717 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18718 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18719
18720 cx.emit(EditorEvent::Reparsed(*buffer_id));
18721 }
18722 multi_buffer::Event::DiffHunksToggled => {
18723 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18724 }
18725 multi_buffer::Event::LanguageChanged(buffer_id) => {
18726 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18727 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18728 cx.emit(EditorEvent::Reparsed(*buffer_id));
18729 cx.notify();
18730 }
18731 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18732 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18733 multi_buffer::Event::FileHandleChanged
18734 | multi_buffer::Event::Reloaded
18735 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18736 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18737 multi_buffer::Event::DiagnosticsUpdated => {
18738 self.refresh_active_diagnostics(cx);
18739 self.refresh_inline_diagnostics(true, window, cx);
18740 self.scrollbar_marker_state.dirty = true;
18741 cx.notify();
18742 }
18743 _ => {}
18744 };
18745 }
18746
18747 pub fn start_temporary_diff_override(&mut self) {
18748 self.load_diff_task.take();
18749 self.temporary_diff_override = true;
18750 }
18751
18752 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18753 self.temporary_diff_override = false;
18754 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18755 self.buffer.update(cx, |buffer, cx| {
18756 buffer.set_all_diff_hunks_collapsed(cx);
18757 });
18758
18759 if let Some(project) = self.project.clone() {
18760 self.load_diff_task = Some(
18761 update_uncommitted_diff_for_buffer(
18762 cx.entity(),
18763 &project,
18764 self.buffer.read(cx).all_buffers(),
18765 self.buffer.clone(),
18766 cx,
18767 )
18768 .shared(),
18769 );
18770 }
18771 }
18772
18773 fn on_display_map_changed(
18774 &mut self,
18775 _: Entity<DisplayMap>,
18776 _: &mut Window,
18777 cx: &mut Context<Self>,
18778 ) {
18779 cx.notify();
18780 }
18781
18782 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18783 let new_severity = if self.diagnostics_enabled() {
18784 EditorSettings::get_global(cx)
18785 .diagnostics_max_severity
18786 .unwrap_or(DiagnosticSeverity::Hint)
18787 } else {
18788 DiagnosticSeverity::Off
18789 };
18790 self.set_max_diagnostics_severity(new_severity, cx);
18791 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18792 self.update_edit_prediction_settings(cx);
18793 self.refresh_inline_completion(true, false, window, cx);
18794 self.refresh_inlay_hints(
18795 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18796 self.selections.newest_anchor().head(),
18797 &self.buffer.read(cx).snapshot(cx),
18798 cx,
18799 )),
18800 cx,
18801 );
18802
18803 let old_cursor_shape = self.cursor_shape;
18804
18805 {
18806 let editor_settings = EditorSettings::get_global(cx);
18807 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18808 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18809 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18810 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18811 }
18812
18813 if old_cursor_shape != self.cursor_shape {
18814 cx.emit(EditorEvent::CursorShapeChanged);
18815 }
18816
18817 let project_settings = ProjectSettings::get_global(cx);
18818 self.serialize_dirty_buffers =
18819 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18820
18821 if self.mode.is_full() {
18822 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18823 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18824 if self.show_inline_diagnostics != show_inline_diagnostics {
18825 self.show_inline_diagnostics = show_inline_diagnostics;
18826 self.refresh_inline_diagnostics(false, window, cx);
18827 }
18828
18829 if self.git_blame_inline_enabled != inline_blame_enabled {
18830 self.toggle_git_blame_inline_internal(false, window, cx);
18831 }
18832
18833 let minimap_settings = EditorSettings::get_global(cx).minimap;
18834 if self.minimap_visibility != MinimapVisibility::Disabled {
18835 if self.minimap_visibility.settings_visibility()
18836 != minimap_settings.minimap_enabled()
18837 {
18838 self.set_minimap_visibility(
18839 MinimapVisibility::for_mode(self.mode(), cx),
18840 window,
18841 cx,
18842 );
18843 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18844 minimap_entity.update(cx, |minimap_editor, cx| {
18845 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18846 })
18847 }
18848 }
18849 }
18850
18851 cx.notify();
18852 }
18853
18854 pub fn set_searchable(&mut self, searchable: bool) {
18855 self.searchable = searchable;
18856 }
18857
18858 pub fn searchable(&self) -> bool {
18859 self.searchable
18860 }
18861
18862 fn open_proposed_changes_editor(
18863 &mut self,
18864 _: &OpenProposedChangesEditor,
18865 window: &mut Window,
18866 cx: &mut Context<Self>,
18867 ) {
18868 let Some(workspace) = self.workspace() else {
18869 cx.propagate();
18870 return;
18871 };
18872
18873 let selections = self.selections.all::<usize>(cx);
18874 let multi_buffer = self.buffer.read(cx);
18875 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18876 let mut new_selections_by_buffer = HashMap::default();
18877 for selection in selections {
18878 for (buffer, range, _) in
18879 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18880 {
18881 let mut range = range.to_point(buffer);
18882 range.start.column = 0;
18883 range.end.column = buffer.line_len(range.end.row);
18884 new_selections_by_buffer
18885 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18886 .or_insert(Vec::new())
18887 .push(range)
18888 }
18889 }
18890
18891 let proposed_changes_buffers = new_selections_by_buffer
18892 .into_iter()
18893 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18894 .collect::<Vec<_>>();
18895 let proposed_changes_editor = cx.new(|cx| {
18896 ProposedChangesEditor::new(
18897 "Proposed changes",
18898 proposed_changes_buffers,
18899 self.project.clone(),
18900 window,
18901 cx,
18902 )
18903 });
18904
18905 window.defer(cx, move |window, cx| {
18906 workspace.update(cx, |workspace, cx| {
18907 workspace.active_pane().update(cx, |pane, cx| {
18908 pane.add_item(
18909 Box::new(proposed_changes_editor),
18910 true,
18911 true,
18912 None,
18913 window,
18914 cx,
18915 );
18916 });
18917 });
18918 });
18919 }
18920
18921 pub fn open_excerpts_in_split(
18922 &mut self,
18923 _: &OpenExcerptsSplit,
18924 window: &mut Window,
18925 cx: &mut Context<Self>,
18926 ) {
18927 self.open_excerpts_common(None, true, window, cx)
18928 }
18929
18930 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18931 self.open_excerpts_common(None, false, window, cx)
18932 }
18933
18934 fn open_excerpts_common(
18935 &mut self,
18936 jump_data: Option<JumpData>,
18937 split: bool,
18938 window: &mut Window,
18939 cx: &mut Context<Self>,
18940 ) {
18941 let Some(workspace) = self.workspace() else {
18942 cx.propagate();
18943 return;
18944 };
18945
18946 if self.buffer.read(cx).is_singleton() {
18947 cx.propagate();
18948 return;
18949 }
18950
18951 let mut new_selections_by_buffer = HashMap::default();
18952 match &jump_data {
18953 Some(JumpData::MultiBufferPoint {
18954 excerpt_id,
18955 position,
18956 anchor,
18957 line_offset_from_top,
18958 }) => {
18959 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18960 if let Some(buffer) = multi_buffer_snapshot
18961 .buffer_id_for_excerpt(*excerpt_id)
18962 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18963 {
18964 let buffer_snapshot = buffer.read(cx).snapshot();
18965 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18966 language::ToPoint::to_point(anchor, &buffer_snapshot)
18967 } else {
18968 buffer_snapshot.clip_point(*position, Bias::Left)
18969 };
18970 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18971 new_selections_by_buffer.insert(
18972 buffer,
18973 (
18974 vec![jump_to_offset..jump_to_offset],
18975 Some(*line_offset_from_top),
18976 ),
18977 );
18978 }
18979 }
18980 Some(JumpData::MultiBufferRow {
18981 row,
18982 line_offset_from_top,
18983 }) => {
18984 let point = MultiBufferPoint::new(row.0, 0);
18985 if let Some((buffer, buffer_point, _)) =
18986 self.buffer.read(cx).point_to_buffer_point(point, cx)
18987 {
18988 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18989 new_selections_by_buffer
18990 .entry(buffer)
18991 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18992 .0
18993 .push(buffer_offset..buffer_offset)
18994 }
18995 }
18996 None => {
18997 let selections = self.selections.all::<usize>(cx);
18998 let multi_buffer = self.buffer.read(cx);
18999 for selection in selections {
19000 for (snapshot, range, _, anchor) in multi_buffer
19001 .snapshot(cx)
19002 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19003 {
19004 if let Some(anchor) = anchor {
19005 // selection is in a deleted hunk
19006 let Some(buffer_id) = anchor.buffer_id else {
19007 continue;
19008 };
19009 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19010 continue;
19011 };
19012 let offset = text::ToOffset::to_offset(
19013 &anchor.text_anchor,
19014 &buffer_handle.read(cx).snapshot(),
19015 );
19016 let range = offset..offset;
19017 new_selections_by_buffer
19018 .entry(buffer_handle)
19019 .or_insert((Vec::new(), None))
19020 .0
19021 .push(range)
19022 } else {
19023 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19024 else {
19025 continue;
19026 };
19027 new_selections_by_buffer
19028 .entry(buffer_handle)
19029 .or_insert((Vec::new(), None))
19030 .0
19031 .push(range)
19032 }
19033 }
19034 }
19035 }
19036 }
19037
19038 new_selections_by_buffer
19039 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19040
19041 if new_selections_by_buffer.is_empty() {
19042 return;
19043 }
19044
19045 // We defer the pane interaction because we ourselves are a workspace item
19046 // and activating a new item causes the pane to call a method on us reentrantly,
19047 // which panics if we're on the stack.
19048 window.defer(cx, move |window, cx| {
19049 workspace.update(cx, |workspace, cx| {
19050 let pane = if split {
19051 workspace.adjacent_pane(window, cx)
19052 } else {
19053 workspace.active_pane().clone()
19054 };
19055
19056 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19057 let editor = buffer
19058 .read(cx)
19059 .file()
19060 .is_none()
19061 .then(|| {
19062 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19063 // so `workspace.open_project_item` will never find them, always opening a new editor.
19064 // Instead, we try to activate the existing editor in the pane first.
19065 let (editor, pane_item_index) =
19066 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19067 let editor = item.downcast::<Editor>()?;
19068 let singleton_buffer =
19069 editor.read(cx).buffer().read(cx).as_singleton()?;
19070 if singleton_buffer == buffer {
19071 Some((editor, i))
19072 } else {
19073 None
19074 }
19075 })?;
19076 pane.update(cx, |pane, cx| {
19077 pane.activate_item(pane_item_index, true, true, window, cx)
19078 });
19079 Some(editor)
19080 })
19081 .flatten()
19082 .unwrap_or_else(|| {
19083 workspace.open_project_item::<Self>(
19084 pane.clone(),
19085 buffer,
19086 true,
19087 true,
19088 window,
19089 cx,
19090 )
19091 });
19092
19093 editor.update(cx, |editor, cx| {
19094 let autoscroll = match scroll_offset {
19095 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19096 None => Autoscroll::newest(),
19097 };
19098 let nav_history = editor.nav_history.take();
19099 editor.change_selections(Some(autoscroll), window, cx, |s| {
19100 s.select_ranges(ranges);
19101 });
19102 editor.nav_history = nav_history;
19103 });
19104 }
19105 })
19106 });
19107 }
19108
19109 // For now, don't allow opening excerpts in buffers that aren't backed by
19110 // regular project files.
19111 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19112 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19113 }
19114
19115 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19116 let snapshot = self.buffer.read(cx).read(cx);
19117 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19118 Some(
19119 ranges
19120 .iter()
19121 .map(move |range| {
19122 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19123 })
19124 .collect(),
19125 )
19126 }
19127
19128 fn selection_replacement_ranges(
19129 &self,
19130 range: Range<OffsetUtf16>,
19131 cx: &mut App,
19132 ) -> Vec<Range<OffsetUtf16>> {
19133 let selections = self.selections.all::<OffsetUtf16>(cx);
19134 let newest_selection = selections
19135 .iter()
19136 .max_by_key(|selection| selection.id)
19137 .unwrap();
19138 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19139 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19140 let snapshot = self.buffer.read(cx).read(cx);
19141 selections
19142 .into_iter()
19143 .map(|mut selection| {
19144 selection.start.0 =
19145 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19146 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19147 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19148 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19149 })
19150 .collect()
19151 }
19152
19153 fn report_editor_event(
19154 &self,
19155 event_type: &'static str,
19156 file_extension: Option<String>,
19157 cx: &App,
19158 ) {
19159 if cfg!(any(test, feature = "test-support")) {
19160 return;
19161 }
19162
19163 let Some(project) = &self.project else { return };
19164
19165 // If None, we are in a file without an extension
19166 let file = self
19167 .buffer
19168 .read(cx)
19169 .as_singleton()
19170 .and_then(|b| b.read(cx).file());
19171 let file_extension = file_extension.or(file
19172 .as_ref()
19173 .and_then(|file| Path::new(file.file_name(cx)).extension())
19174 .and_then(|e| e.to_str())
19175 .map(|a| a.to_string()));
19176
19177 let vim_mode = vim_enabled(cx);
19178
19179 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19180 let copilot_enabled = edit_predictions_provider
19181 == language::language_settings::EditPredictionProvider::Copilot;
19182 let copilot_enabled_for_language = self
19183 .buffer
19184 .read(cx)
19185 .language_settings(cx)
19186 .show_edit_predictions;
19187
19188 let project = project.read(cx);
19189 telemetry::event!(
19190 event_type,
19191 file_extension,
19192 vim_mode,
19193 copilot_enabled,
19194 copilot_enabled_for_language,
19195 edit_predictions_provider,
19196 is_via_ssh = project.is_via_ssh(),
19197 );
19198 }
19199
19200 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19201 /// with each line being an array of {text, highlight} objects.
19202 fn copy_highlight_json(
19203 &mut self,
19204 _: &CopyHighlightJson,
19205 window: &mut Window,
19206 cx: &mut Context<Self>,
19207 ) {
19208 #[derive(Serialize)]
19209 struct Chunk<'a> {
19210 text: String,
19211 highlight: Option<&'a str>,
19212 }
19213
19214 let snapshot = self.buffer.read(cx).snapshot(cx);
19215 let range = self
19216 .selected_text_range(false, window, cx)
19217 .and_then(|selection| {
19218 if selection.range.is_empty() {
19219 None
19220 } else {
19221 Some(selection.range)
19222 }
19223 })
19224 .unwrap_or_else(|| 0..snapshot.len());
19225
19226 let chunks = snapshot.chunks(range, true);
19227 let mut lines = Vec::new();
19228 let mut line: VecDeque<Chunk> = VecDeque::new();
19229
19230 let Some(style) = self.style.as_ref() else {
19231 return;
19232 };
19233
19234 for chunk in chunks {
19235 let highlight = chunk
19236 .syntax_highlight_id
19237 .and_then(|id| id.name(&style.syntax));
19238 let mut chunk_lines = chunk.text.split('\n').peekable();
19239 while let Some(text) = chunk_lines.next() {
19240 let mut merged_with_last_token = false;
19241 if let Some(last_token) = line.back_mut() {
19242 if last_token.highlight == highlight {
19243 last_token.text.push_str(text);
19244 merged_with_last_token = true;
19245 }
19246 }
19247
19248 if !merged_with_last_token {
19249 line.push_back(Chunk {
19250 text: text.into(),
19251 highlight,
19252 });
19253 }
19254
19255 if chunk_lines.peek().is_some() {
19256 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19257 line.pop_front();
19258 }
19259 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19260 line.pop_back();
19261 }
19262
19263 lines.push(mem::take(&mut line));
19264 }
19265 }
19266 }
19267
19268 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19269 return;
19270 };
19271 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19272 }
19273
19274 pub fn open_context_menu(
19275 &mut self,
19276 _: &OpenContextMenu,
19277 window: &mut Window,
19278 cx: &mut Context<Self>,
19279 ) {
19280 self.request_autoscroll(Autoscroll::newest(), cx);
19281 let position = self.selections.newest_display(cx).start;
19282 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19283 }
19284
19285 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19286 &self.inlay_hint_cache
19287 }
19288
19289 pub fn replay_insert_event(
19290 &mut self,
19291 text: &str,
19292 relative_utf16_range: Option<Range<isize>>,
19293 window: &mut Window,
19294 cx: &mut Context<Self>,
19295 ) {
19296 if !self.input_enabled {
19297 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19298 return;
19299 }
19300 if let Some(relative_utf16_range) = relative_utf16_range {
19301 let selections = self.selections.all::<OffsetUtf16>(cx);
19302 self.change_selections(None, window, cx, |s| {
19303 let new_ranges = selections.into_iter().map(|range| {
19304 let start = OffsetUtf16(
19305 range
19306 .head()
19307 .0
19308 .saturating_add_signed(relative_utf16_range.start),
19309 );
19310 let end = OffsetUtf16(
19311 range
19312 .head()
19313 .0
19314 .saturating_add_signed(relative_utf16_range.end),
19315 );
19316 start..end
19317 });
19318 s.select_ranges(new_ranges);
19319 });
19320 }
19321
19322 self.handle_input(text, window, cx);
19323 }
19324
19325 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19326 let Some(provider) = self.semantics_provider.as_ref() else {
19327 return false;
19328 };
19329
19330 let mut supports = false;
19331 self.buffer().update(cx, |this, cx| {
19332 this.for_each_buffer(|buffer| {
19333 supports |= provider.supports_inlay_hints(buffer, cx);
19334 });
19335 });
19336
19337 supports
19338 }
19339
19340 pub fn is_focused(&self, window: &Window) -> bool {
19341 self.focus_handle.is_focused(window)
19342 }
19343
19344 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19345 cx.emit(EditorEvent::Focused);
19346
19347 if let Some(descendant) = self
19348 .last_focused_descendant
19349 .take()
19350 .and_then(|descendant| descendant.upgrade())
19351 {
19352 window.focus(&descendant);
19353 } else {
19354 if let Some(blame) = self.blame.as_ref() {
19355 blame.update(cx, GitBlame::focus)
19356 }
19357
19358 self.blink_manager.update(cx, BlinkManager::enable);
19359 self.show_cursor_names(window, cx);
19360 self.buffer.update(cx, |buffer, cx| {
19361 buffer.finalize_last_transaction(cx);
19362 if self.leader_id.is_none() {
19363 buffer.set_active_selections(
19364 &self.selections.disjoint_anchors(),
19365 self.selections.line_mode,
19366 self.cursor_shape,
19367 cx,
19368 );
19369 }
19370 });
19371 }
19372 }
19373
19374 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19375 cx.emit(EditorEvent::FocusedIn)
19376 }
19377
19378 fn handle_focus_out(
19379 &mut self,
19380 event: FocusOutEvent,
19381 _window: &mut Window,
19382 cx: &mut Context<Self>,
19383 ) {
19384 if event.blurred != self.focus_handle {
19385 self.last_focused_descendant = Some(event.blurred);
19386 }
19387 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19388 }
19389
19390 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19391 self.blink_manager.update(cx, BlinkManager::disable);
19392 self.buffer
19393 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19394
19395 if let Some(blame) = self.blame.as_ref() {
19396 blame.update(cx, GitBlame::blur)
19397 }
19398 if !self.hover_state.focused(window, cx) {
19399 hide_hover(self, cx);
19400 }
19401 if !self
19402 .context_menu
19403 .borrow()
19404 .as_ref()
19405 .is_some_and(|context_menu| context_menu.focused(window, cx))
19406 {
19407 self.hide_context_menu(window, cx);
19408 }
19409 self.discard_inline_completion(false, cx);
19410 cx.emit(EditorEvent::Blurred);
19411 cx.notify();
19412 }
19413
19414 pub fn register_action<A: Action>(
19415 &mut self,
19416 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19417 ) -> Subscription {
19418 let id = self.next_editor_action_id.post_inc();
19419 let listener = Arc::new(listener);
19420 self.editor_actions.borrow_mut().insert(
19421 id,
19422 Box::new(move |window, _| {
19423 let listener = listener.clone();
19424 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19425 let action = action.downcast_ref().unwrap();
19426 if phase == DispatchPhase::Bubble {
19427 listener(action, window, cx)
19428 }
19429 })
19430 }),
19431 );
19432
19433 let editor_actions = self.editor_actions.clone();
19434 Subscription::new(move || {
19435 editor_actions.borrow_mut().remove(&id);
19436 })
19437 }
19438
19439 pub fn file_header_size(&self) -> u32 {
19440 FILE_HEADER_HEIGHT
19441 }
19442
19443 pub fn restore(
19444 &mut self,
19445 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19446 window: &mut Window,
19447 cx: &mut Context<Self>,
19448 ) {
19449 let workspace = self.workspace();
19450 let project = self.project.as_ref();
19451 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19452 let mut tasks = Vec::new();
19453 for (buffer_id, changes) in revert_changes {
19454 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19455 buffer.update(cx, |buffer, cx| {
19456 buffer.edit(
19457 changes
19458 .into_iter()
19459 .map(|(range, text)| (range, text.to_string())),
19460 None,
19461 cx,
19462 );
19463 });
19464
19465 if let Some(project) =
19466 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19467 {
19468 project.update(cx, |project, cx| {
19469 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19470 })
19471 }
19472 }
19473 }
19474 tasks
19475 });
19476 cx.spawn_in(window, async move |_, cx| {
19477 for (buffer, task) in save_tasks {
19478 let result = task.await;
19479 if result.is_err() {
19480 let Some(path) = buffer
19481 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19482 .ok()
19483 else {
19484 continue;
19485 };
19486 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19487 let Some(task) = cx
19488 .update_window_entity(&workspace, |workspace, window, cx| {
19489 workspace
19490 .open_path_preview(path, None, false, false, false, window, cx)
19491 })
19492 .ok()
19493 else {
19494 continue;
19495 };
19496 task.await.log_err();
19497 }
19498 }
19499 }
19500 })
19501 .detach();
19502 self.change_selections(None, window, cx, |selections| selections.refresh());
19503 }
19504
19505 pub fn to_pixel_point(
19506 &self,
19507 source: multi_buffer::Anchor,
19508 editor_snapshot: &EditorSnapshot,
19509 window: &mut Window,
19510 ) -> Option<gpui::Point<Pixels>> {
19511 let source_point = source.to_display_point(editor_snapshot);
19512 self.display_to_pixel_point(source_point, editor_snapshot, window)
19513 }
19514
19515 pub fn display_to_pixel_point(
19516 &self,
19517 source: DisplayPoint,
19518 editor_snapshot: &EditorSnapshot,
19519 window: &mut Window,
19520 ) -> Option<gpui::Point<Pixels>> {
19521 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19522 let text_layout_details = self.text_layout_details(window);
19523 let scroll_top = text_layout_details
19524 .scroll_anchor
19525 .scroll_position(editor_snapshot)
19526 .y;
19527
19528 if source.row().as_f32() < scroll_top.floor() {
19529 return None;
19530 }
19531 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19532 let source_y = line_height * (source.row().as_f32() - scroll_top);
19533 Some(gpui::Point::new(source_x, source_y))
19534 }
19535
19536 pub fn has_visible_completions_menu(&self) -> bool {
19537 !self.edit_prediction_preview_is_active()
19538 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19539 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19540 })
19541 }
19542
19543 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19544 if self.mode.is_minimap() {
19545 return;
19546 }
19547 self.addons
19548 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19549 }
19550
19551 pub fn unregister_addon<T: Addon>(&mut self) {
19552 self.addons.remove(&std::any::TypeId::of::<T>());
19553 }
19554
19555 pub fn addon<T: Addon>(&self) -> Option<&T> {
19556 let type_id = std::any::TypeId::of::<T>();
19557 self.addons
19558 .get(&type_id)
19559 .and_then(|item| item.to_any().downcast_ref::<T>())
19560 }
19561
19562 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19563 let type_id = std::any::TypeId::of::<T>();
19564 self.addons
19565 .get_mut(&type_id)
19566 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19567 }
19568
19569 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19570 let text_layout_details = self.text_layout_details(window);
19571 let style = &text_layout_details.editor_style;
19572 let font_id = window.text_system().resolve_font(&style.text.font());
19573 let font_size = style.text.font_size.to_pixels(window.rem_size());
19574 let line_height = style.text.line_height_in_pixels(window.rem_size());
19575 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19576
19577 gpui::Size::new(em_width, line_height)
19578 }
19579
19580 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19581 self.load_diff_task.clone()
19582 }
19583
19584 fn read_metadata_from_db(
19585 &mut self,
19586 item_id: u64,
19587 workspace_id: WorkspaceId,
19588 window: &mut Window,
19589 cx: &mut Context<Editor>,
19590 ) {
19591 if self.is_singleton(cx)
19592 && !self.mode.is_minimap()
19593 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19594 {
19595 let buffer_snapshot = OnceCell::new();
19596
19597 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19598 if !folds.is_empty() {
19599 let snapshot =
19600 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19601 self.fold_ranges(
19602 folds
19603 .into_iter()
19604 .map(|(start, end)| {
19605 snapshot.clip_offset(start, Bias::Left)
19606 ..snapshot.clip_offset(end, Bias::Right)
19607 })
19608 .collect(),
19609 false,
19610 window,
19611 cx,
19612 );
19613 }
19614 }
19615
19616 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19617 if !selections.is_empty() {
19618 let snapshot =
19619 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19620 self.change_selections(None, window, cx, |s| {
19621 s.select_ranges(selections.into_iter().map(|(start, end)| {
19622 snapshot.clip_offset(start, Bias::Left)
19623 ..snapshot.clip_offset(end, Bias::Right)
19624 }));
19625 });
19626 }
19627 };
19628 }
19629
19630 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19631 }
19632}
19633
19634fn vim_enabled(cx: &App) -> bool {
19635 cx.global::<SettingsStore>()
19636 .raw_user_settings()
19637 .get("vim_mode")
19638 == Some(&serde_json::Value::Bool(true))
19639}
19640
19641fn process_completion_for_edit(
19642 completion: &Completion,
19643 intent: CompletionIntent,
19644 buffer: &Entity<Buffer>,
19645 cursor_position: &text::Anchor,
19646 cx: &mut Context<Editor>,
19647) -> CompletionEdit {
19648 let buffer = buffer.read(cx);
19649 let buffer_snapshot = buffer.snapshot();
19650 let (snippet, new_text) = if completion.is_snippet() {
19651 let mut snippet_source = completion.new_text.clone();
19652 if let Some(scope) = buffer_snapshot.language_scope_at(cursor_position) {
19653 if scope.prefers_label_for_snippet_in_completion() {
19654 if let Some(label) = completion.label() {
19655 if matches!(
19656 completion.kind(),
19657 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
19658 ) {
19659 snippet_source = label;
19660 }
19661 }
19662 }
19663 }
19664 match Snippet::parse(&snippet_source).log_err() {
19665 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
19666 None => (None, completion.new_text.clone()),
19667 }
19668 } else {
19669 (None, completion.new_text.clone())
19670 };
19671
19672 let mut range_to_replace = {
19673 let replace_range = &completion.replace_range;
19674 if let CompletionSource::Lsp {
19675 insert_range: Some(insert_range),
19676 ..
19677 } = &completion.source
19678 {
19679 debug_assert_eq!(
19680 insert_range.start, replace_range.start,
19681 "insert_range and replace_range should start at the same position"
19682 );
19683 debug_assert!(
19684 insert_range
19685 .start
19686 .cmp(&cursor_position, &buffer_snapshot)
19687 .is_le(),
19688 "insert_range should start before or at cursor position"
19689 );
19690 debug_assert!(
19691 replace_range
19692 .start
19693 .cmp(&cursor_position, &buffer_snapshot)
19694 .is_le(),
19695 "replace_range should start before or at cursor position"
19696 );
19697 debug_assert!(
19698 insert_range
19699 .end
19700 .cmp(&cursor_position, &buffer_snapshot)
19701 .is_le(),
19702 "insert_range should end before or at cursor position"
19703 );
19704
19705 let should_replace = match intent {
19706 CompletionIntent::CompleteWithInsert => false,
19707 CompletionIntent::CompleteWithReplace => true,
19708 CompletionIntent::Complete | CompletionIntent::Compose => {
19709 let insert_mode =
19710 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19711 .completions
19712 .lsp_insert_mode;
19713 match insert_mode {
19714 LspInsertMode::Insert => false,
19715 LspInsertMode::Replace => true,
19716 LspInsertMode::ReplaceSubsequence => {
19717 let mut text_to_replace = buffer.chars_for_range(
19718 buffer.anchor_before(replace_range.start)
19719 ..buffer.anchor_after(replace_range.end),
19720 );
19721 let mut current_needle = text_to_replace.next();
19722 for haystack_ch in completion.label.text.chars() {
19723 if let Some(needle_ch) = current_needle {
19724 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
19725 current_needle = text_to_replace.next();
19726 }
19727 }
19728 }
19729 current_needle.is_none()
19730 }
19731 LspInsertMode::ReplaceSuffix => {
19732 if replace_range
19733 .end
19734 .cmp(&cursor_position, &buffer_snapshot)
19735 .is_gt()
19736 {
19737 let range_after_cursor = *cursor_position..replace_range.end;
19738 let text_after_cursor = buffer
19739 .text_for_range(
19740 buffer.anchor_before(range_after_cursor.start)
19741 ..buffer.anchor_after(range_after_cursor.end),
19742 )
19743 .collect::<String>()
19744 .to_ascii_lowercase();
19745 completion
19746 .label
19747 .text
19748 .to_ascii_lowercase()
19749 .ends_with(&text_after_cursor)
19750 } else {
19751 true
19752 }
19753 }
19754 }
19755 }
19756 };
19757
19758 if should_replace {
19759 replace_range.clone()
19760 } else {
19761 insert_range.clone()
19762 }
19763 } else {
19764 replace_range.clone()
19765 }
19766 };
19767
19768 if range_to_replace
19769 .end
19770 .cmp(&cursor_position, &buffer_snapshot)
19771 .is_lt()
19772 {
19773 range_to_replace.end = *cursor_position;
19774 }
19775
19776 CompletionEdit {
19777 new_text,
19778 replace_range: range_to_replace.to_offset(&buffer),
19779 snippet,
19780 }
19781}
19782
19783struct CompletionEdit {
19784 new_text: String,
19785 replace_range: Range<usize>,
19786 snippet: Option<Snippet>,
19787}
19788
19789fn insert_extra_newline_brackets(
19790 buffer: &MultiBufferSnapshot,
19791 range: Range<usize>,
19792 language: &language::LanguageScope,
19793) -> bool {
19794 let leading_whitespace_len = buffer
19795 .reversed_chars_at(range.start)
19796 .take_while(|c| c.is_whitespace() && *c != '\n')
19797 .map(|c| c.len_utf8())
19798 .sum::<usize>();
19799 let trailing_whitespace_len = buffer
19800 .chars_at(range.end)
19801 .take_while(|c| c.is_whitespace() && *c != '\n')
19802 .map(|c| c.len_utf8())
19803 .sum::<usize>();
19804 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19805
19806 language.brackets().any(|(pair, enabled)| {
19807 let pair_start = pair.start.trim_end();
19808 let pair_end = pair.end.trim_start();
19809
19810 enabled
19811 && pair.newline
19812 && buffer.contains_str_at(range.end, pair_end)
19813 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19814 })
19815}
19816
19817fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19818 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19819 [(buffer, range, _)] => (*buffer, range.clone()),
19820 _ => return false,
19821 };
19822 let pair = {
19823 let mut result: Option<BracketMatch> = None;
19824
19825 for pair in buffer
19826 .all_bracket_ranges(range.clone())
19827 .filter(move |pair| {
19828 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19829 })
19830 {
19831 let len = pair.close_range.end - pair.open_range.start;
19832
19833 if let Some(existing) = &result {
19834 let existing_len = existing.close_range.end - existing.open_range.start;
19835 if len > existing_len {
19836 continue;
19837 }
19838 }
19839
19840 result = Some(pair);
19841 }
19842
19843 result
19844 };
19845 let Some(pair) = pair else {
19846 return false;
19847 };
19848 pair.newline_only
19849 && buffer
19850 .chars_for_range(pair.open_range.end..range.start)
19851 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19852 .all(|c| c.is_whitespace() && c != '\n')
19853}
19854
19855fn update_uncommitted_diff_for_buffer(
19856 editor: Entity<Editor>,
19857 project: &Entity<Project>,
19858 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19859 buffer: Entity<MultiBuffer>,
19860 cx: &mut App,
19861) -> Task<()> {
19862 let mut tasks = Vec::new();
19863 project.update(cx, |project, cx| {
19864 for buffer in buffers {
19865 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19866 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19867 }
19868 }
19869 });
19870 cx.spawn(async move |cx| {
19871 let diffs = future::join_all(tasks).await;
19872 if editor
19873 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19874 .unwrap_or(false)
19875 {
19876 return;
19877 }
19878
19879 buffer
19880 .update(cx, |buffer, cx| {
19881 for diff in diffs.into_iter().flatten() {
19882 buffer.add_diff(diff, cx);
19883 }
19884 })
19885 .ok();
19886 })
19887}
19888
19889fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19890 let tab_size = tab_size.get() as usize;
19891 let mut width = offset;
19892
19893 for ch in text.chars() {
19894 width += if ch == '\t' {
19895 tab_size - (width % tab_size)
19896 } else {
19897 1
19898 };
19899 }
19900
19901 width - offset
19902}
19903
19904#[cfg(test)]
19905mod tests {
19906 use super::*;
19907
19908 #[test]
19909 fn test_string_size_with_expanded_tabs() {
19910 let nz = |val| NonZeroU32::new(val).unwrap();
19911 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19912 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19913 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19914 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19915 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19916 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19917 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19918 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19919 }
19920}
19921
19922/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19923struct WordBreakingTokenizer<'a> {
19924 input: &'a str,
19925}
19926
19927impl<'a> WordBreakingTokenizer<'a> {
19928 fn new(input: &'a str) -> Self {
19929 Self { input }
19930 }
19931}
19932
19933fn is_char_ideographic(ch: char) -> bool {
19934 use unicode_script::Script::*;
19935 use unicode_script::UnicodeScript;
19936 matches!(ch.script(), Han | Tangut | Yi)
19937}
19938
19939fn is_grapheme_ideographic(text: &str) -> bool {
19940 text.chars().any(is_char_ideographic)
19941}
19942
19943fn is_grapheme_whitespace(text: &str) -> bool {
19944 text.chars().any(|x| x.is_whitespace())
19945}
19946
19947fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19948 text.chars().next().map_or(false, |ch| {
19949 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19950 })
19951}
19952
19953#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19954enum WordBreakToken<'a> {
19955 Word { token: &'a str, grapheme_len: usize },
19956 InlineWhitespace { token: &'a str, grapheme_len: usize },
19957 Newline,
19958}
19959
19960impl<'a> Iterator for WordBreakingTokenizer<'a> {
19961 /// Yields a span, the count of graphemes in the token, and whether it was
19962 /// whitespace. Note that it also breaks at word boundaries.
19963 type Item = WordBreakToken<'a>;
19964
19965 fn next(&mut self) -> Option<Self::Item> {
19966 use unicode_segmentation::UnicodeSegmentation;
19967 if self.input.is_empty() {
19968 return None;
19969 }
19970
19971 let mut iter = self.input.graphemes(true).peekable();
19972 let mut offset = 0;
19973 let mut grapheme_len = 0;
19974 if let Some(first_grapheme) = iter.next() {
19975 let is_newline = first_grapheme == "\n";
19976 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19977 offset += first_grapheme.len();
19978 grapheme_len += 1;
19979 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19980 if let Some(grapheme) = iter.peek().copied() {
19981 if should_stay_with_preceding_ideograph(grapheme) {
19982 offset += grapheme.len();
19983 grapheme_len += 1;
19984 }
19985 }
19986 } else {
19987 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19988 let mut next_word_bound = words.peek().copied();
19989 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19990 next_word_bound = words.next();
19991 }
19992 while let Some(grapheme) = iter.peek().copied() {
19993 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19994 break;
19995 };
19996 if is_grapheme_whitespace(grapheme) != is_whitespace
19997 || (grapheme == "\n") != is_newline
19998 {
19999 break;
20000 };
20001 offset += grapheme.len();
20002 grapheme_len += 1;
20003 iter.next();
20004 }
20005 }
20006 let token = &self.input[..offset];
20007 self.input = &self.input[offset..];
20008 if token == "\n" {
20009 Some(WordBreakToken::Newline)
20010 } else if is_whitespace {
20011 Some(WordBreakToken::InlineWhitespace {
20012 token,
20013 grapheme_len,
20014 })
20015 } else {
20016 Some(WordBreakToken::Word {
20017 token,
20018 grapheme_len,
20019 })
20020 }
20021 } else {
20022 None
20023 }
20024 }
20025}
20026
20027#[test]
20028fn test_word_breaking_tokenizer() {
20029 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20030 ("", &[]),
20031 (" ", &[whitespace(" ", 2)]),
20032 ("Ʒ", &[word("Ʒ", 1)]),
20033 ("Ǽ", &[word("Ǽ", 1)]),
20034 ("⋑", &[word("⋑", 1)]),
20035 ("⋑⋑", &[word("⋑⋑", 2)]),
20036 (
20037 "原理,进而",
20038 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20039 ),
20040 (
20041 "hello world",
20042 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20043 ),
20044 (
20045 "hello, world",
20046 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20047 ),
20048 (
20049 " hello world",
20050 &[
20051 whitespace(" ", 2),
20052 word("hello", 5),
20053 whitespace(" ", 1),
20054 word("world", 5),
20055 ],
20056 ),
20057 (
20058 "这是什么 \n 钢笔",
20059 &[
20060 word("这", 1),
20061 word("是", 1),
20062 word("什", 1),
20063 word("么", 1),
20064 whitespace(" ", 1),
20065 newline(),
20066 whitespace(" ", 1),
20067 word("钢", 1),
20068 word("笔", 1),
20069 ],
20070 ),
20071 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20072 ];
20073
20074 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20075 WordBreakToken::Word {
20076 token,
20077 grapheme_len,
20078 }
20079 }
20080
20081 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20082 WordBreakToken::InlineWhitespace {
20083 token,
20084 grapheme_len,
20085 }
20086 }
20087
20088 fn newline() -> WordBreakToken<'static> {
20089 WordBreakToken::Newline
20090 }
20091
20092 for (input, result) in tests {
20093 assert_eq!(
20094 WordBreakingTokenizer::new(input)
20095 .collect::<Vec<_>>()
20096 .as_slice(),
20097 *result,
20098 );
20099 }
20100}
20101
20102fn wrap_with_prefix(
20103 line_prefix: String,
20104 unwrapped_text: String,
20105 wrap_column: usize,
20106 tab_size: NonZeroU32,
20107 preserve_existing_whitespace: bool,
20108) -> String {
20109 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20110 let mut wrapped_text = String::new();
20111 let mut current_line = line_prefix.clone();
20112
20113 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20114 let mut current_line_len = line_prefix_len;
20115 let mut in_whitespace = false;
20116 for token in tokenizer {
20117 let have_preceding_whitespace = in_whitespace;
20118 match token {
20119 WordBreakToken::Word {
20120 token,
20121 grapheme_len,
20122 } => {
20123 in_whitespace = false;
20124 if current_line_len + grapheme_len > wrap_column
20125 && current_line_len != line_prefix_len
20126 {
20127 wrapped_text.push_str(current_line.trim_end());
20128 wrapped_text.push('\n');
20129 current_line.truncate(line_prefix.len());
20130 current_line_len = line_prefix_len;
20131 }
20132 current_line.push_str(token);
20133 current_line_len += grapheme_len;
20134 }
20135 WordBreakToken::InlineWhitespace {
20136 mut token,
20137 mut grapheme_len,
20138 } => {
20139 in_whitespace = true;
20140 if have_preceding_whitespace && !preserve_existing_whitespace {
20141 continue;
20142 }
20143 if !preserve_existing_whitespace {
20144 token = " ";
20145 grapheme_len = 1;
20146 }
20147 if current_line_len + grapheme_len > wrap_column {
20148 wrapped_text.push_str(current_line.trim_end());
20149 wrapped_text.push('\n');
20150 current_line.truncate(line_prefix.len());
20151 current_line_len = line_prefix_len;
20152 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20153 current_line.push_str(token);
20154 current_line_len += grapheme_len;
20155 }
20156 }
20157 WordBreakToken::Newline => {
20158 in_whitespace = true;
20159 if preserve_existing_whitespace {
20160 wrapped_text.push_str(current_line.trim_end());
20161 wrapped_text.push('\n');
20162 current_line.truncate(line_prefix.len());
20163 current_line_len = line_prefix_len;
20164 } else if have_preceding_whitespace {
20165 continue;
20166 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20167 {
20168 wrapped_text.push_str(current_line.trim_end());
20169 wrapped_text.push('\n');
20170 current_line.truncate(line_prefix.len());
20171 current_line_len = line_prefix_len;
20172 } else if current_line_len != line_prefix_len {
20173 current_line.push(' ');
20174 current_line_len += 1;
20175 }
20176 }
20177 }
20178 }
20179
20180 if !current_line.is_empty() {
20181 wrapped_text.push_str(¤t_line);
20182 }
20183 wrapped_text
20184}
20185
20186#[test]
20187fn test_wrap_with_prefix() {
20188 assert_eq!(
20189 wrap_with_prefix(
20190 "# ".to_string(),
20191 "abcdefg".to_string(),
20192 4,
20193 NonZeroU32::new(4).unwrap(),
20194 false,
20195 ),
20196 "# abcdefg"
20197 );
20198 assert_eq!(
20199 wrap_with_prefix(
20200 "".to_string(),
20201 "\thello world".to_string(),
20202 8,
20203 NonZeroU32::new(4).unwrap(),
20204 false,
20205 ),
20206 "hello\nworld"
20207 );
20208 assert_eq!(
20209 wrap_with_prefix(
20210 "// ".to_string(),
20211 "xx \nyy zz aa bb cc".to_string(),
20212 12,
20213 NonZeroU32::new(4).unwrap(),
20214 false,
20215 ),
20216 "// xx yy zz\n// aa bb cc"
20217 );
20218 assert_eq!(
20219 wrap_with_prefix(
20220 String::new(),
20221 "这是什么 \n 钢笔".to_string(),
20222 3,
20223 NonZeroU32::new(4).unwrap(),
20224 false,
20225 ),
20226 "这是什\n么 钢\n笔"
20227 );
20228}
20229
20230pub trait CollaborationHub {
20231 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20232 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20233 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20234}
20235
20236impl CollaborationHub for Entity<Project> {
20237 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20238 self.read(cx).collaborators()
20239 }
20240
20241 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20242 self.read(cx).user_store().read(cx).participant_indices()
20243 }
20244
20245 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20246 let this = self.read(cx);
20247 let user_ids = this.collaborators().values().map(|c| c.user_id);
20248 this.user_store().read(cx).participant_names(user_ids, cx)
20249 }
20250}
20251
20252pub trait SemanticsProvider {
20253 fn hover(
20254 &self,
20255 buffer: &Entity<Buffer>,
20256 position: text::Anchor,
20257 cx: &mut App,
20258 ) -> Option<Task<Vec<project::Hover>>>;
20259
20260 fn inline_values(
20261 &self,
20262 buffer_handle: Entity<Buffer>,
20263 range: Range<text::Anchor>,
20264 cx: &mut App,
20265 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20266
20267 fn inlay_hints(
20268 &self,
20269 buffer_handle: Entity<Buffer>,
20270 range: Range<text::Anchor>,
20271 cx: &mut App,
20272 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20273
20274 fn resolve_inlay_hint(
20275 &self,
20276 hint: InlayHint,
20277 buffer_handle: Entity<Buffer>,
20278 server_id: LanguageServerId,
20279 cx: &mut App,
20280 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20281
20282 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20283
20284 fn document_highlights(
20285 &self,
20286 buffer: &Entity<Buffer>,
20287 position: text::Anchor,
20288 cx: &mut App,
20289 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20290
20291 fn definitions(
20292 &self,
20293 buffer: &Entity<Buffer>,
20294 position: text::Anchor,
20295 kind: GotoDefinitionKind,
20296 cx: &mut App,
20297 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20298
20299 fn range_for_rename(
20300 &self,
20301 buffer: &Entity<Buffer>,
20302 position: text::Anchor,
20303 cx: &mut App,
20304 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20305
20306 fn perform_rename(
20307 &self,
20308 buffer: &Entity<Buffer>,
20309 position: text::Anchor,
20310 new_name: String,
20311 cx: &mut App,
20312 ) -> Option<Task<Result<ProjectTransaction>>>;
20313}
20314
20315pub trait CompletionProvider {
20316 fn completions(
20317 &self,
20318 excerpt_id: ExcerptId,
20319 buffer: &Entity<Buffer>,
20320 buffer_position: text::Anchor,
20321 trigger: CompletionContext,
20322 window: &mut Window,
20323 cx: &mut Context<Editor>,
20324 ) -> Task<Result<Vec<CompletionResponse>>>;
20325
20326 fn resolve_completions(
20327 &self,
20328 buffer: Entity<Buffer>,
20329 completion_indices: Vec<usize>,
20330 completions: Rc<RefCell<Box<[Completion]>>>,
20331 cx: &mut Context<Editor>,
20332 ) -> Task<Result<bool>>;
20333
20334 fn apply_additional_edits_for_completion(
20335 &self,
20336 _buffer: Entity<Buffer>,
20337 _completions: Rc<RefCell<Box<[Completion]>>>,
20338 _completion_index: usize,
20339 _push_to_history: bool,
20340 _cx: &mut Context<Editor>,
20341 ) -> Task<Result<Option<language::Transaction>>> {
20342 Task::ready(Ok(None))
20343 }
20344
20345 fn is_completion_trigger(
20346 &self,
20347 buffer: &Entity<Buffer>,
20348 position: language::Anchor,
20349 text: &str,
20350 trigger_in_words: bool,
20351 menu_is_open: bool,
20352 cx: &mut Context<Editor>,
20353 ) -> bool;
20354
20355 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20356
20357 fn sort_completions(&self) -> bool {
20358 true
20359 }
20360
20361 fn filter_completions(&self) -> bool {
20362 true
20363 }
20364}
20365
20366pub trait CodeActionProvider {
20367 fn id(&self) -> Arc<str>;
20368
20369 fn code_actions(
20370 &self,
20371 buffer: &Entity<Buffer>,
20372 range: Range<text::Anchor>,
20373 window: &mut Window,
20374 cx: &mut App,
20375 ) -> Task<Result<Vec<CodeAction>>>;
20376
20377 fn apply_code_action(
20378 &self,
20379 buffer_handle: Entity<Buffer>,
20380 action: CodeAction,
20381 excerpt_id: ExcerptId,
20382 push_to_history: bool,
20383 window: &mut Window,
20384 cx: &mut App,
20385 ) -> Task<Result<ProjectTransaction>>;
20386}
20387
20388impl CodeActionProvider for Entity<Project> {
20389 fn id(&self) -> Arc<str> {
20390 "project".into()
20391 }
20392
20393 fn code_actions(
20394 &self,
20395 buffer: &Entity<Buffer>,
20396 range: Range<text::Anchor>,
20397 _window: &mut Window,
20398 cx: &mut App,
20399 ) -> Task<Result<Vec<CodeAction>>> {
20400 self.update(cx, |project, cx| {
20401 let code_lens = project.code_lens(buffer, range.clone(), cx);
20402 let code_actions = project.code_actions(buffer, range, None, cx);
20403 cx.background_spawn(async move {
20404 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20405 Ok(code_lens
20406 .context("code lens fetch")?
20407 .into_iter()
20408 .chain(code_actions.context("code action fetch")?)
20409 .collect())
20410 })
20411 })
20412 }
20413
20414 fn apply_code_action(
20415 &self,
20416 buffer_handle: Entity<Buffer>,
20417 action: CodeAction,
20418 _excerpt_id: ExcerptId,
20419 push_to_history: bool,
20420 _window: &mut Window,
20421 cx: &mut App,
20422 ) -> Task<Result<ProjectTransaction>> {
20423 self.update(cx, |project, cx| {
20424 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20425 })
20426 }
20427}
20428
20429fn snippet_completions(
20430 project: &Project,
20431 buffer: &Entity<Buffer>,
20432 buffer_position: text::Anchor,
20433 cx: &mut App,
20434) -> Task<Result<CompletionResponse>> {
20435 let languages = buffer.read(cx).languages_at(buffer_position);
20436 let snippet_store = project.snippets().read(cx);
20437
20438 let scopes: Vec<_> = languages
20439 .iter()
20440 .filter_map(|language| {
20441 let language_name = language.lsp_id();
20442 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20443
20444 if snippets.is_empty() {
20445 None
20446 } else {
20447 Some((language.default_scope(), snippets))
20448 }
20449 })
20450 .collect();
20451
20452 if scopes.is_empty() {
20453 return Task::ready(Ok(CompletionResponse {
20454 completions: vec![],
20455 is_incomplete: false,
20456 }));
20457 }
20458
20459 let snapshot = buffer.read(cx).text_snapshot();
20460 let chars: String = snapshot
20461 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20462 .collect();
20463 let executor = cx.background_executor().clone();
20464
20465 cx.background_spawn(async move {
20466 let mut is_incomplete = false;
20467 let mut completions: Vec<Completion> = Vec::new();
20468 for (scope, snippets) in scopes.into_iter() {
20469 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20470 let mut last_word = chars
20471 .chars()
20472 .take_while(|c| classifier.is_word(*c))
20473 .collect::<String>();
20474 last_word = last_word.chars().rev().collect();
20475
20476 if last_word.is_empty() {
20477 return Ok(CompletionResponse {
20478 completions: vec![],
20479 is_incomplete: true,
20480 });
20481 }
20482
20483 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20484 let to_lsp = |point: &text::Anchor| {
20485 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20486 point_to_lsp(end)
20487 };
20488 let lsp_end = to_lsp(&buffer_position);
20489
20490 let candidates = snippets
20491 .iter()
20492 .enumerate()
20493 .flat_map(|(ix, snippet)| {
20494 snippet
20495 .prefix
20496 .iter()
20497 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20498 })
20499 .collect::<Vec<StringMatchCandidate>>();
20500
20501 const MAX_RESULTS: usize = 100;
20502 let mut matches = fuzzy::match_strings(
20503 &candidates,
20504 &last_word,
20505 last_word.chars().any(|c| c.is_uppercase()),
20506 MAX_RESULTS,
20507 &Default::default(),
20508 executor.clone(),
20509 )
20510 .await;
20511
20512 if matches.len() >= MAX_RESULTS {
20513 is_incomplete = true;
20514 }
20515
20516 // Remove all candidates where the query's start does not match the start of any word in the candidate
20517 if let Some(query_start) = last_word.chars().next() {
20518 matches.retain(|string_match| {
20519 split_words(&string_match.string).any(|word| {
20520 // Check that the first codepoint of the word as lowercase matches the first
20521 // codepoint of the query as lowercase
20522 word.chars()
20523 .flat_map(|codepoint| codepoint.to_lowercase())
20524 .zip(query_start.to_lowercase())
20525 .all(|(word_cp, query_cp)| word_cp == query_cp)
20526 })
20527 });
20528 }
20529
20530 let matched_strings = matches
20531 .into_iter()
20532 .map(|m| m.string)
20533 .collect::<HashSet<_>>();
20534
20535 completions.extend(snippets.iter().filter_map(|snippet| {
20536 let matching_prefix = snippet
20537 .prefix
20538 .iter()
20539 .find(|prefix| matched_strings.contains(*prefix))?;
20540 let start = as_offset - last_word.len();
20541 let start = snapshot.anchor_before(start);
20542 let range = start..buffer_position;
20543 let lsp_start = to_lsp(&start);
20544 let lsp_range = lsp::Range {
20545 start: lsp_start,
20546 end: lsp_end,
20547 };
20548 Some(Completion {
20549 replace_range: range,
20550 new_text: snippet.body.clone(),
20551 source: CompletionSource::Lsp {
20552 insert_range: None,
20553 server_id: LanguageServerId(usize::MAX),
20554 resolved: true,
20555 lsp_completion: Box::new(lsp::CompletionItem {
20556 label: snippet.prefix.first().unwrap().clone(),
20557 kind: Some(CompletionItemKind::SNIPPET),
20558 label_details: snippet.description.as_ref().map(|description| {
20559 lsp::CompletionItemLabelDetails {
20560 detail: Some(description.clone()),
20561 description: None,
20562 }
20563 }),
20564 insert_text_format: Some(InsertTextFormat::SNIPPET),
20565 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20566 lsp::InsertReplaceEdit {
20567 new_text: snippet.body.clone(),
20568 insert: lsp_range,
20569 replace: lsp_range,
20570 },
20571 )),
20572 filter_text: Some(snippet.body.clone()),
20573 sort_text: Some(char::MAX.to_string()),
20574 ..lsp::CompletionItem::default()
20575 }),
20576 lsp_defaults: None,
20577 },
20578 label: CodeLabel {
20579 text: matching_prefix.clone(),
20580 runs: Vec::new(),
20581 filter_range: 0..matching_prefix.len(),
20582 },
20583 icon_path: None,
20584 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
20585 single_line: snippet.name.clone().into(),
20586 plain_text: snippet
20587 .description
20588 .clone()
20589 .map(|description| description.into()),
20590 }),
20591 insert_text_mode: None,
20592 confirm: None,
20593 })
20594 }))
20595 }
20596
20597 Ok(CompletionResponse {
20598 completions,
20599 is_incomplete,
20600 })
20601 })
20602}
20603
20604impl CompletionProvider for Entity<Project> {
20605 fn completions(
20606 &self,
20607 _excerpt_id: ExcerptId,
20608 buffer: &Entity<Buffer>,
20609 buffer_position: text::Anchor,
20610 options: CompletionContext,
20611 _window: &mut Window,
20612 cx: &mut Context<Editor>,
20613 ) -> Task<Result<Vec<CompletionResponse>>> {
20614 self.update(cx, |project, cx| {
20615 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20616 let project_completions = project.completions(buffer, buffer_position, options, cx);
20617 cx.background_spawn(async move {
20618 let mut responses = project_completions.await?;
20619 let snippets = snippets.await?;
20620 if !snippets.completions.is_empty() {
20621 responses.push(snippets);
20622 }
20623 Ok(responses)
20624 })
20625 })
20626 }
20627
20628 fn resolve_completions(
20629 &self,
20630 buffer: Entity<Buffer>,
20631 completion_indices: Vec<usize>,
20632 completions: Rc<RefCell<Box<[Completion]>>>,
20633 cx: &mut Context<Editor>,
20634 ) -> Task<Result<bool>> {
20635 self.update(cx, |project, cx| {
20636 project.lsp_store().update(cx, |lsp_store, cx| {
20637 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20638 })
20639 })
20640 }
20641
20642 fn apply_additional_edits_for_completion(
20643 &self,
20644 buffer: Entity<Buffer>,
20645 completions: Rc<RefCell<Box<[Completion]>>>,
20646 completion_index: usize,
20647 push_to_history: bool,
20648 cx: &mut Context<Editor>,
20649 ) -> Task<Result<Option<language::Transaction>>> {
20650 self.update(cx, |project, cx| {
20651 project.lsp_store().update(cx, |lsp_store, cx| {
20652 lsp_store.apply_additional_edits_for_completion(
20653 buffer,
20654 completions,
20655 completion_index,
20656 push_to_history,
20657 cx,
20658 )
20659 })
20660 })
20661 }
20662
20663 fn is_completion_trigger(
20664 &self,
20665 buffer: &Entity<Buffer>,
20666 position: language::Anchor,
20667 text: &str,
20668 trigger_in_words: bool,
20669 menu_is_open: bool,
20670 cx: &mut Context<Editor>,
20671 ) -> bool {
20672 let mut chars = text.chars();
20673 let char = if let Some(char) = chars.next() {
20674 char
20675 } else {
20676 return false;
20677 };
20678 if chars.next().is_some() {
20679 return false;
20680 }
20681
20682 let buffer = buffer.read(cx);
20683 let snapshot = buffer.snapshot();
20684 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
20685 return false;
20686 }
20687 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20688 if trigger_in_words && classifier.is_word(char) {
20689 return true;
20690 }
20691
20692 buffer.completion_triggers().contains(text)
20693 }
20694}
20695
20696impl SemanticsProvider for Entity<Project> {
20697 fn hover(
20698 &self,
20699 buffer: &Entity<Buffer>,
20700 position: text::Anchor,
20701 cx: &mut App,
20702 ) -> Option<Task<Vec<project::Hover>>> {
20703 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20704 }
20705
20706 fn document_highlights(
20707 &self,
20708 buffer: &Entity<Buffer>,
20709 position: text::Anchor,
20710 cx: &mut App,
20711 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20712 Some(self.update(cx, |project, cx| {
20713 project.document_highlights(buffer, position, cx)
20714 }))
20715 }
20716
20717 fn definitions(
20718 &self,
20719 buffer: &Entity<Buffer>,
20720 position: text::Anchor,
20721 kind: GotoDefinitionKind,
20722 cx: &mut App,
20723 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20724 Some(self.update(cx, |project, cx| match kind {
20725 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20726 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20727 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20728 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20729 }))
20730 }
20731
20732 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20733 // TODO: make this work for remote projects
20734 self.update(cx, |project, cx| {
20735 if project
20736 .active_debug_session(cx)
20737 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20738 {
20739 return true;
20740 }
20741
20742 buffer.update(cx, |buffer, cx| {
20743 project.any_language_server_supports_inlay_hints(buffer, cx)
20744 })
20745 })
20746 }
20747
20748 fn inline_values(
20749 &self,
20750 buffer_handle: Entity<Buffer>,
20751
20752 range: Range<text::Anchor>,
20753 cx: &mut App,
20754 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20755 self.update(cx, |project, cx| {
20756 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20757
20758 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20759 })
20760 }
20761
20762 fn inlay_hints(
20763 &self,
20764 buffer_handle: Entity<Buffer>,
20765 range: Range<text::Anchor>,
20766 cx: &mut App,
20767 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20768 Some(self.update(cx, |project, cx| {
20769 project.inlay_hints(buffer_handle, range, cx)
20770 }))
20771 }
20772
20773 fn resolve_inlay_hint(
20774 &self,
20775 hint: InlayHint,
20776 buffer_handle: Entity<Buffer>,
20777 server_id: LanguageServerId,
20778 cx: &mut App,
20779 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20780 Some(self.update(cx, |project, cx| {
20781 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20782 }))
20783 }
20784
20785 fn range_for_rename(
20786 &self,
20787 buffer: &Entity<Buffer>,
20788 position: text::Anchor,
20789 cx: &mut App,
20790 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20791 Some(self.update(cx, |project, cx| {
20792 let buffer = buffer.clone();
20793 let task = project.prepare_rename(buffer.clone(), position, cx);
20794 cx.spawn(async move |_, cx| {
20795 Ok(match task.await? {
20796 PrepareRenameResponse::Success(range) => Some(range),
20797 PrepareRenameResponse::InvalidPosition => None,
20798 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20799 // Fallback on using TreeSitter info to determine identifier range
20800 buffer.read_with(cx, |buffer, _| {
20801 let snapshot = buffer.snapshot();
20802 let (range, kind) = snapshot.surrounding_word(position);
20803 if kind != Some(CharKind::Word) {
20804 return None;
20805 }
20806 Some(
20807 snapshot.anchor_before(range.start)
20808 ..snapshot.anchor_after(range.end),
20809 )
20810 })?
20811 }
20812 })
20813 })
20814 }))
20815 }
20816
20817 fn perform_rename(
20818 &self,
20819 buffer: &Entity<Buffer>,
20820 position: text::Anchor,
20821 new_name: String,
20822 cx: &mut App,
20823 ) -> Option<Task<Result<ProjectTransaction>>> {
20824 Some(self.update(cx, |project, cx| {
20825 project.perform_rename(buffer.clone(), position, new_name, cx)
20826 }))
20827 }
20828}
20829
20830fn inlay_hint_settings(
20831 location: Anchor,
20832 snapshot: &MultiBufferSnapshot,
20833 cx: &mut Context<Editor>,
20834) -> InlayHintSettings {
20835 let file = snapshot.file_at(location);
20836 let language = snapshot.language_at(location).map(|l| l.name());
20837 language_settings(language, file, cx).inlay_hints
20838}
20839
20840fn consume_contiguous_rows(
20841 contiguous_row_selections: &mut Vec<Selection<Point>>,
20842 selection: &Selection<Point>,
20843 display_map: &DisplaySnapshot,
20844 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20845) -> (MultiBufferRow, MultiBufferRow) {
20846 contiguous_row_selections.push(selection.clone());
20847 let start_row = MultiBufferRow(selection.start.row);
20848 let mut end_row = ending_row(selection, display_map);
20849
20850 while let Some(next_selection) = selections.peek() {
20851 if next_selection.start.row <= end_row.0 {
20852 end_row = ending_row(next_selection, display_map);
20853 contiguous_row_selections.push(selections.next().unwrap().clone());
20854 } else {
20855 break;
20856 }
20857 }
20858 (start_row, end_row)
20859}
20860
20861fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20862 if next_selection.end.column > 0 || next_selection.is_empty() {
20863 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20864 } else {
20865 MultiBufferRow(next_selection.end.row)
20866 }
20867}
20868
20869impl EditorSnapshot {
20870 pub fn remote_selections_in_range<'a>(
20871 &'a self,
20872 range: &'a Range<Anchor>,
20873 collaboration_hub: &dyn CollaborationHub,
20874 cx: &'a App,
20875 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20876 let participant_names = collaboration_hub.user_names(cx);
20877 let participant_indices = collaboration_hub.user_participant_indices(cx);
20878 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20879 let collaborators_by_replica_id = collaborators_by_peer_id
20880 .values()
20881 .map(|collaborator| (collaborator.replica_id, collaborator))
20882 .collect::<HashMap<_, _>>();
20883 self.buffer_snapshot
20884 .selections_in_range(range, false)
20885 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20886 if replica_id == AGENT_REPLICA_ID {
20887 Some(RemoteSelection {
20888 replica_id,
20889 selection,
20890 cursor_shape,
20891 line_mode,
20892 collaborator_id: CollaboratorId::Agent,
20893 user_name: Some("Agent".into()),
20894 color: cx.theme().players().agent(),
20895 })
20896 } else {
20897 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20898 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20899 let user_name = participant_names.get(&collaborator.user_id).cloned();
20900 Some(RemoteSelection {
20901 replica_id,
20902 selection,
20903 cursor_shape,
20904 line_mode,
20905 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20906 user_name,
20907 color: if let Some(index) = participant_index {
20908 cx.theme().players().color_for_participant(index.0)
20909 } else {
20910 cx.theme().players().absent()
20911 },
20912 })
20913 }
20914 })
20915 }
20916
20917 pub fn hunks_for_ranges(
20918 &self,
20919 ranges: impl IntoIterator<Item = Range<Point>>,
20920 ) -> Vec<MultiBufferDiffHunk> {
20921 let mut hunks = Vec::new();
20922 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20923 HashMap::default();
20924 for query_range in ranges {
20925 let query_rows =
20926 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20927 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20928 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20929 ) {
20930 // Include deleted hunks that are adjacent to the query range, because
20931 // otherwise they would be missed.
20932 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20933 if hunk.status().is_deleted() {
20934 intersects_range |= hunk.row_range.start == query_rows.end;
20935 intersects_range |= hunk.row_range.end == query_rows.start;
20936 }
20937 if intersects_range {
20938 if !processed_buffer_rows
20939 .entry(hunk.buffer_id)
20940 .or_default()
20941 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20942 {
20943 continue;
20944 }
20945 hunks.push(hunk);
20946 }
20947 }
20948 }
20949
20950 hunks
20951 }
20952
20953 fn display_diff_hunks_for_rows<'a>(
20954 &'a self,
20955 display_rows: Range<DisplayRow>,
20956 folded_buffers: &'a HashSet<BufferId>,
20957 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20958 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20959 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20960
20961 self.buffer_snapshot
20962 .diff_hunks_in_range(buffer_start..buffer_end)
20963 .filter_map(|hunk| {
20964 if folded_buffers.contains(&hunk.buffer_id) {
20965 return None;
20966 }
20967
20968 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20969 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20970
20971 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20972 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20973
20974 let display_hunk = if hunk_display_start.column() != 0 {
20975 DisplayDiffHunk::Folded {
20976 display_row: hunk_display_start.row(),
20977 }
20978 } else {
20979 let mut end_row = hunk_display_end.row();
20980 if hunk_display_end.column() > 0 {
20981 end_row.0 += 1;
20982 }
20983 let is_created_file = hunk.is_created_file();
20984 DisplayDiffHunk::Unfolded {
20985 status: hunk.status(),
20986 diff_base_byte_range: hunk.diff_base_byte_range,
20987 display_row_range: hunk_display_start.row()..end_row,
20988 multi_buffer_range: Anchor::range_in_buffer(
20989 hunk.excerpt_id,
20990 hunk.buffer_id,
20991 hunk.buffer_range,
20992 ),
20993 is_created_file,
20994 }
20995 };
20996
20997 Some(display_hunk)
20998 })
20999 }
21000
21001 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21002 self.display_snapshot.buffer_snapshot.language_at(position)
21003 }
21004
21005 pub fn is_focused(&self) -> bool {
21006 self.is_focused
21007 }
21008
21009 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21010 self.placeholder_text.as_ref()
21011 }
21012
21013 pub fn scroll_position(&self) -> gpui::Point<f32> {
21014 self.scroll_anchor.scroll_position(&self.display_snapshot)
21015 }
21016
21017 fn gutter_dimensions(
21018 &self,
21019 font_id: FontId,
21020 font_size: Pixels,
21021 max_line_number_width: Pixels,
21022 cx: &App,
21023 ) -> Option<GutterDimensions> {
21024 if !self.show_gutter {
21025 return None;
21026 }
21027
21028 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
21029 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
21030
21031 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21032 matches!(
21033 ProjectSettings::get_global(cx).git.git_gutter,
21034 Some(GitGutterSetting::TrackedFiles)
21035 )
21036 });
21037 let gutter_settings = EditorSettings::get_global(cx).gutter;
21038 let show_line_numbers = self
21039 .show_line_numbers
21040 .unwrap_or(gutter_settings.line_numbers);
21041 let line_gutter_width = if show_line_numbers {
21042 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
21043 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
21044 max_line_number_width.max(min_width_for_number_on_gutter)
21045 } else {
21046 0.0.into()
21047 };
21048
21049 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21050 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21051
21052 let git_blame_entries_width =
21053 self.git_blame_gutter_max_author_length
21054 .map(|max_author_length| {
21055 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21056 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21057
21058 /// The number of characters to dedicate to gaps and margins.
21059 const SPACING_WIDTH: usize = 4;
21060
21061 let max_char_count = max_author_length.min(renderer.max_author_length())
21062 + ::git::SHORT_SHA_LENGTH
21063 + MAX_RELATIVE_TIMESTAMP.len()
21064 + SPACING_WIDTH;
21065
21066 em_advance * max_char_count
21067 });
21068
21069 let is_singleton = self.buffer_snapshot.is_singleton();
21070
21071 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21072 left_padding += if !is_singleton {
21073 em_width * 4.0
21074 } else if show_runnables || show_breakpoints {
21075 em_width * 3.0
21076 } else if show_git_gutter && show_line_numbers {
21077 em_width * 2.0
21078 } else if show_git_gutter || show_line_numbers {
21079 em_width
21080 } else {
21081 px(0.)
21082 };
21083
21084 let shows_folds = is_singleton && gutter_settings.folds;
21085
21086 let right_padding = if shows_folds && show_line_numbers {
21087 em_width * 4.0
21088 } else if shows_folds || (!is_singleton && show_line_numbers) {
21089 em_width * 3.0
21090 } else if show_line_numbers {
21091 em_width
21092 } else {
21093 px(0.)
21094 };
21095
21096 Some(GutterDimensions {
21097 left_padding,
21098 right_padding,
21099 width: line_gutter_width + left_padding + right_padding,
21100 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21101 git_blame_entries_width,
21102 })
21103 }
21104
21105 pub fn render_crease_toggle(
21106 &self,
21107 buffer_row: MultiBufferRow,
21108 row_contains_cursor: bool,
21109 editor: Entity<Editor>,
21110 window: &mut Window,
21111 cx: &mut App,
21112 ) -> Option<AnyElement> {
21113 let folded = self.is_line_folded(buffer_row);
21114 let mut is_foldable = false;
21115
21116 if let Some(crease) = self
21117 .crease_snapshot
21118 .query_row(buffer_row, &self.buffer_snapshot)
21119 {
21120 is_foldable = true;
21121 match crease {
21122 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21123 if let Some(render_toggle) = render_toggle {
21124 let toggle_callback =
21125 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21126 if folded {
21127 editor.update(cx, |editor, cx| {
21128 editor.fold_at(buffer_row, window, cx)
21129 });
21130 } else {
21131 editor.update(cx, |editor, cx| {
21132 editor.unfold_at(buffer_row, window, cx)
21133 });
21134 }
21135 });
21136 return Some((render_toggle)(
21137 buffer_row,
21138 folded,
21139 toggle_callback,
21140 window,
21141 cx,
21142 ));
21143 }
21144 }
21145 }
21146 }
21147
21148 is_foldable |= self.starts_indent(buffer_row);
21149
21150 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21151 Some(
21152 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21153 .toggle_state(folded)
21154 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21155 if folded {
21156 this.unfold_at(buffer_row, window, cx);
21157 } else {
21158 this.fold_at(buffer_row, window, cx);
21159 }
21160 }))
21161 .into_any_element(),
21162 )
21163 } else {
21164 None
21165 }
21166 }
21167
21168 pub fn render_crease_trailer(
21169 &self,
21170 buffer_row: MultiBufferRow,
21171 window: &mut Window,
21172 cx: &mut App,
21173 ) -> Option<AnyElement> {
21174 let folded = self.is_line_folded(buffer_row);
21175 if let Crease::Inline { render_trailer, .. } = self
21176 .crease_snapshot
21177 .query_row(buffer_row, &self.buffer_snapshot)?
21178 {
21179 let render_trailer = render_trailer.as_ref()?;
21180 Some(render_trailer(buffer_row, folded, window, cx))
21181 } else {
21182 None
21183 }
21184 }
21185}
21186
21187impl Deref for EditorSnapshot {
21188 type Target = DisplaySnapshot;
21189
21190 fn deref(&self) -> &Self::Target {
21191 &self.display_snapshot
21192 }
21193}
21194
21195#[derive(Clone, Debug, PartialEq, Eq)]
21196pub enum EditorEvent {
21197 InputIgnored {
21198 text: Arc<str>,
21199 },
21200 InputHandled {
21201 utf16_range_to_replace: Option<Range<isize>>,
21202 text: Arc<str>,
21203 },
21204 ExcerptsAdded {
21205 buffer: Entity<Buffer>,
21206 predecessor: ExcerptId,
21207 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21208 },
21209 ExcerptsRemoved {
21210 ids: Vec<ExcerptId>,
21211 removed_buffer_ids: Vec<BufferId>,
21212 },
21213 BufferFoldToggled {
21214 ids: Vec<ExcerptId>,
21215 folded: bool,
21216 },
21217 ExcerptsEdited {
21218 ids: Vec<ExcerptId>,
21219 },
21220 ExcerptsExpanded {
21221 ids: Vec<ExcerptId>,
21222 },
21223 BufferEdited,
21224 Edited {
21225 transaction_id: clock::Lamport,
21226 },
21227 Reparsed(BufferId),
21228 Focused,
21229 FocusedIn,
21230 Blurred,
21231 DirtyChanged,
21232 Saved,
21233 TitleChanged,
21234 DiffBaseChanged,
21235 SelectionsChanged {
21236 local: bool,
21237 },
21238 ScrollPositionChanged {
21239 local: bool,
21240 autoscroll: bool,
21241 },
21242 Closed,
21243 TransactionUndone {
21244 transaction_id: clock::Lamport,
21245 },
21246 TransactionBegun {
21247 transaction_id: clock::Lamport,
21248 },
21249 Reloaded,
21250 CursorShapeChanged,
21251 PushedToNavHistory {
21252 anchor: Anchor,
21253 is_deactivate: bool,
21254 },
21255}
21256
21257impl EventEmitter<EditorEvent> for Editor {}
21258
21259impl Focusable for Editor {
21260 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21261 self.focus_handle.clone()
21262 }
21263}
21264
21265impl Render for Editor {
21266 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21267 let settings = ThemeSettings::get_global(cx);
21268
21269 let mut text_style = match self.mode {
21270 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21271 color: cx.theme().colors().editor_foreground,
21272 font_family: settings.ui_font.family.clone(),
21273 font_features: settings.ui_font.features.clone(),
21274 font_fallbacks: settings.ui_font.fallbacks.clone(),
21275 font_size: rems(0.875).into(),
21276 font_weight: settings.ui_font.weight,
21277 line_height: relative(settings.buffer_line_height.value()),
21278 ..Default::default()
21279 },
21280 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21281 color: cx.theme().colors().editor_foreground,
21282 font_family: settings.buffer_font.family.clone(),
21283 font_features: settings.buffer_font.features.clone(),
21284 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21285 font_size: settings.buffer_font_size(cx).into(),
21286 font_weight: settings.buffer_font.weight,
21287 line_height: relative(settings.buffer_line_height.value()),
21288 ..Default::default()
21289 },
21290 };
21291 if let Some(text_style_refinement) = &self.text_style_refinement {
21292 text_style.refine(text_style_refinement)
21293 }
21294
21295 let background = match self.mode {
21296 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21297 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21298 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21299 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21300 };
21301
21302 EditorElement::new(
21303 &cx.entity(),
21304 EditorStyle {
21305 background,
21306 local_player: cx.theme().players().local(),
21307 text: text_style,
21308 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21309 syntax: cx.theme().syntax().clone(),
21310 status: cx.theme().status().clone(),
21311 inlay_hints_style: make_inlay_hints_style(cx),
21312 inline_completion_styles: make_suggestion_styles(cx),
21313 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21314 show_underlines: !self.mode.is_minimap(),
21315 },
21316 )
21317 }
21318}
21319
21320impl EntityInputHandler for Editor {
21321 fn text_for_range(
21322 &mut self,
21323 range_utf16: Range<usize>,
21324 adjusted_range: &mut Option<Range<usize>>,
21325 _: &mut Window,
21326 cx: &mut Context<Self>,
21327 ) -> Option<String> {
21328 let snapshot = self.buffer.read(cx).read(cx);
21329 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21330 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21331 if (start.0..end.0) != range_utf16 {
21332 adjusted_range.replace(start.0..end.0);
21333 }
21334 Some(snapshot.text_for_range(start..end).collect())
21335 }
21336
21337 fn selected_text_range(
21338 &mut self,
21339 ignore_disabled_input: bool,
21340 _: &mut Window,
21341 cx: &mut Context<Self>,
21342 ) -> Option<UTF16Selection> {
21343 // Prevent the IME menu from appearing when holding down an alphabetic key
21344 // while input is disabled.
21345 if !ignore_disabled_input && !self.input_enabled {
21346 return None;
21347 }
21348
21349 let selection = self.selections.newest::<OffsetUtf16>(cx);
21350 let range = selection.range();
21351
21352 Some(UTF16Selection {
21353 range: range.start.0..range.end.0,
21354 reversed: selection.reversed,
21355 })
21356 }
21357
21358 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21359 let snapshot = self.buffer.read(cx).read(cx);
21360 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21361 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21362 }
21363
21364 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21365 self.clear_highlights::<InputComposition>(cx);
21366 self.ime_transaction.take();
21367 }
21368
21369 fn replace_text_in_range(
21370 &mut self,
21371 range_utf16: Option<Range<usize>>,
21372 text: &str,
21373 window: &mut Window,
21374 cx: &mut Context<Self>,
21375 ) {
21376 if !self.input_enabled {
21377 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21378 return;
21379 }
21380
21381 self.transact(window, cx, |this, window, cx| {
21382 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21383 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21384 Some(this.selection_replacement_ranges(range_utf16, cx))
21385 } else {
21386 this.marked_text_ranges(cx)
21387 };
21388
21389 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21390 let newest_selection_id = this.selections.newest_anchor().id;
21391 this.selections
21392 .all::<OffsetUtf16>(cx)
21393 .iter()
21394 .zip(ranges_to_replace.iter())
21395 .find_map(|(selection, range)| {
21396 if selection.id == newest_selection_id {
21397 Some(
21398 (range.start.0 as isize - selection.head().0 as isize)
21399 ..(range.end.0 as isize - selection.head().0 as isize),
21400 )
21401 } else {
21402 None
21403 }
21404 })
21405 });
21406
21407 cx.emit(EditorEvent::InputHandled {
21408 utf16_range_to_replace: range_to_replace,
21409 text: text.into(),
21410 });
21411
21412 if let Some(new_selected_ranges) = new_selected_ranges {
21413 this.change_selections(None, window, cx, |selections| {
21414 selections.select_ranges(new_selected_ranges)
21415 });
21416 this.backspace(&Default::default(), window, cx);
21417 }
21418
21419 this.handle_input(text, window, cx);
21420 });
21421
21422 if let Some(transaction) = self.ime_transaction {
21423 self.buffer.update(cx, |buffer, cx| {
21424 buffer.group_until_transaction(transaction, cx);
21425 });
21426 }
21427
21428 self.unmark_text(window, cx);
21429 }
21430
21431 fn replace_and_mark_text_in_range(
21432 &mut self,
21433 range_utf16: Option<Range<usize>>,
21434 text: &str,
21435 new_selected_range_utf16: Option<Range<usize>>,
21436 window: &mut Window,
21437 cx: &mut Context<Self>,
21438 ) {
21439 if !self.input_enabled {
21440 return;
21441 }
21442
21443 let transaction = self.transact(window, cx, |this, window, cx| {
21444 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21445 let snapshot = this.buffer.read(cx).read(cx);
21446 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21447 for marked_range in &mut marked_ranges {
21448 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21449 marked_range.start.0 += relative_range_utf16.start;
21450 marked_range.start =
21451 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21452 marked_range.end =
21453 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21454 }
21455 }
21456 Some(marked_ranges)
21457 } else if let Some(range_utf16) = range_utf16 {
21458 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21459 Some(this.selection_replacement_ranges(range_utf16, cx))
21460 } else {
21461 None
21462 };
21463
21464 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21465 let newest_selection_id = this.selections.newest_anchor().id;
21466 this.selections
21467 .all::<OffsetUtf16>(cx)
21468 .iter()
21469 .zip(ranges_to_replace.iter())
21470 .find_map(|(selection, range)| {
21471 if selection.id == newest_selection_id {
21472 Some(
21473 (range.start.0 as isize - selection.head().0 as isize)
21474 ..(range.end.0 as isize - selection.head().0 as isize),
21475 )
21476 } else {
21477 None
21478 }
21479 })
21480 });
21481
21482 cx.emit(EditorEvent::InputHandled {
21483 utf16_range_to_replace: range_to_replace,
21484 text: text.into(),
21485 });
21486
21487 if let Some(ranges) = ranges_to_replace {
21488 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21489 }
21490
21491 let marked_ranges = {
21492 let snapshot = this.buffer.read(cx).read(cx);
21493 this.selections
21494 .disjoint_anchors()
21495 .iter()
21496 .map(|selection| {
21497 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21498 })
21499 .collect::<Vec<_>>()
21500 };
21501
21502 if text.is_empty() {
21503 this.unmark_text(window, cx);
21504 } else {
21505 this.highlight_text::<InputComposition>(
21506 marked_ranges.clone(),
21507 HighlightStyle {
21508 underline: Some(UnderlineStyle {
21509 thickness: px(1.),
21510 color: None,
21511 wavy: false,
21512 }),
21513 ..Default::default()
21514 },
21515 cx,
21516 );
21517 }
21518
21519 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21520 let use_autoclose = this.use_autoclose;
21521 let use_auto_surround = this.use_auto_surround;
21522 this.set_use_autoclose(false);
21523 this.set_use_auto_surround(false);
21524 this.handle_input(text, window, cx);
21525 this.set_use_autoclose(use_autoclose);
21526 this.set_use_auto_surround(use_auto_surround);
21527
21528 if let Some(new_selected_range) = new_selected_range_utf16 {
21529 let snapshot = this.buffer.read(cx).read(cx);
21530 let new_selected_ranges = marked_ranges
21531 .into_iter()
21532 .map(|marked_range| {
21533 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21534 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21535 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21536 snapshot.clip_offset_utf16(new_start, Bias::Left)
21537 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21538 })
21539 .collect::<Vec<_>>();
21540
21541 drop(snapshot);
21542 this.change_selections(None, window, cx, |selections| {
21543 selections.select_ranges(new_selected_ranges)
21544 });
21545 }
21546 });
21547
21548 self.ime_transaction = self.ime_transaction.or(transaction);
21549 if let Some(transaction) = self.ime_transaction {
21550 self.buffer.update(cx, |buffer, cx| {
21551 buffer.group_until_transaction(transaction, cx);
21552 });
21553 }
21554
21555 if self.text_highlights::<InputComposition>(cx).is_none() {
21556 self.ime_transaction.take();
21557 }
21558 }
21559
21560 fn bounds_for_range(
21561 &mut self,
21562 range_utf16: Range<usize>,
21563 element_bounds: gpui::Bounds<Pixels>,
21564 window: &mut Window,
21565 cx: &mut Context<Self>,
21566 ) -> Option<gpui::Bounds<Pixels>> {
21567 let text_layout_details = self.text_layout_details(window);
21568 let gpui::Size {
21569 width: em_width,
21570 height: line_height,
21571 } = self.character_size(window);
21572
21573 let snapshot = self.snapshot(window, cx);
21574 let scroll_position = snapshot.scroll_position();
21575 let scroll_left = scroll_position.x * em_width;
21576
21577 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21578 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21579 + self.gutter_dimensions.width
21580 + self.gutter_dimensions.margin;
21581 let y = line_height * (start.row().as_f32() - scroll_position.y);
21582
21583 Some(Bounds {
21584 origin: element_bounds.origin + point(x, y),
21585 size: size(em_width, line_height),
21586 })
21587 }
21588
21589 fn character_index_for_point(
21590 &mut self,
21591 point: gpui::Point<Pixels>,
21592 _window: &mut Window,
21593 _cx: &mut Context<Self>,
21594 ) -> Option<usize> {
21595 let position_map = self.last_position_map.as_ref()?;
21596 if !position_map.text_hitbox.contains(&point) {
21597 return None;
21598 }
21599 let display_point = position_map.point_for_position(point).previous_valid;
21600 let anchor = position_map
21601 .snapshot
21602 .display_point_to_anchor(display_point, Bias::Left);
21603 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21604 Some(utf16_offset.0)
21605 }
21606}
21607
21608trait SelectionExt {
21609 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21610 fn spanned_rows(
21611 &self,
21612 include_end_if_at_line_start: bool,
21613 map: &DisplaySnapshot,
21614 ) -> Range<MultiBufferRow>;
21615}
21616
21617impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21618 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21619 let start = self
21620 .start
21621 .to_point(&map.buffer_snapshot)
21622 .to_display_point(map);
21623 let end = self
21624 .end
21625 .to_point(&map.buffer_snapshot)
21626 .to_display_point(map);
21627 if self.reversed {
21628 end..start
21629 } else {
21630 start..end
21631 }
21632 }
21633
21634 fn spanned_rows(
21635 &self,
21636 include_end_if_at_line_start: bool,
21637 map: &DisplaySnapshot,
21638 ) -> Range<MultiBufferRow> {
21639 let start = self.start.to_point(&map.buffer_snapshot);
21640 let mut end = self.end.to_point(&map.buffer_snapshot);
21641 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21642 end.row -= 1;
21643 }
21644
21645 let buffer_start = map.prev_line_boundary(start).0;
21646 let buffer_end = map.next_line_boundary(end).0;
21647 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21648 }
21649}
21650
21651impl<T: InvalidationRegion> InvalidationStack<T> {
21652 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21653 where
21654 S: Clone + ToOffset,
21655 {
21656 while let Some(region) = self.last() {
21657 let all_selections_inside_invalidation_ranges =
21658 if selections.len() == region.ranges().len() {
21659 selections
21660 .iter()
21661 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21662 .all(|(selection, invalidation_range)| {
21663 let head = selection.head().to_offset(buffer);
21664 invalidation_range.start <= head && invalidation_range.end >= head
21665 })
21666 } else {
21667 false
21668 };
21669
21670 if all_selections_inside_invalidation_ranges {
21671 break;
21672 } else {
21673 self.pop();
21674 }
21675 }
21676 }
21677}
21678
21679impl<T> Default for InvalidationStack<T> {
21680 fn default() -> Self {
21681 Self(Default::default())
21682 }
21683}
21684
21685impl<T> Deref for InvalidationStack<T> {
21686 type Target = Vec<T>;
21687
21688 fn deref(&self) -> &Self::Target {
21689 &self.0
21690 }
21691}
21692
21693impl<T> DerefMut for InvalidationStack<T> {
21694 fn deref_mut(&mut self) -> &mut Self::Target {
21695 &mut self.0
21696 }
21697}
21698
21699impl InvalidationRegion for SnippetState {
21700 fn ranges(&self) -> &[Range<Anchor>] {
21701 &self.ranges[self.active_index]
21702 }
21703}
21704
21705fn inline_completion_edit_text(
21706 current_snapshot: &BufferSnapshot,
21707 edits: &[(Range<Anchor>, String)],
21708 edit_preview: &EditPreview,
21709 include_deletions: bool,
21710 cx: &App,
21711) -> HighlightedText {
21712 let edits = edits
21713 .iter()
21714 .map(|(anchor, text)| {
21715 (
21716 anchor.start.text_anchor..anchor.end.text_anchor,
21717 text.clone(),
21718 )
21719 })
21720 .collect::<Vec<_>>();
21721
21722 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21723}
21724
21725pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21726 match severity {
21727 lsp::DiagnosticSeverity::ERROR => colors.error,
21728 lsp::DiagnosticSeverity::WARNING => colors.warning,
21729 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21730 lsp::DiagnosticSeverity::HINT => colors.info,
21731 _ => colors.ignored,
21732 }
21733}
21734
21735pub fn styled_runs_for_code_label<'a>(
21736 label: &'a CodeLabel,
21737 syntax_theme: &'a theme::SyntaxTheme,
21738) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21739 let fade_out = HighlightStyle {
21740 fade_out: Some(0.35),
21741 ..Default::default()
21742 };
21743
21744 let mut prev_end = label.filter_range.end;
21745 label
21746 .runs
21747 .iter()
21748 .enumerate()
21749 .flat_map(move |(ix, (range, highlight_id))| {
21750 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21751 style
21752 } else {
21753 return Default::default();
21754 };
21755 let mut muted_style = style;
21756 muted_style.highlight(fade_out);
21757
21758 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21759 if range.start >= label.filter_range.end {
21760 if range.start > prev_end {
21761 runs.push((prev_end..range.start, fade_out));
21762 }
21763 runs.push((range.clone(), muted_style));
21764 } else if range.end <= label.filter_range.end {
21765 runs.push((range.clone(), style));
21766 } else {
21767 runs.push((range.start..label.filter_range.end, style));
21768 runs.push((label.filter_range.end..range.end, muted_style));
21769 }
21770 prev_end = cmp::max(prev_end, range.end);
21771
21772 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21773 runs.push((prev_end..label.text.len(), fade_out));
21774 }
21775
21776 runs
21777 })
21778}
21779
21780pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21781 let mut prev_index = 0;
21782 let mut prev_codepoint: Option<char> = None;
21783 text.char_indices()
21784 .chain([(text.len(), '\0')])
21785 .filter_map(move |(index, codepoint)| {
21786 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21787 let is_boundary = index == text.len()
21788 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21789 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21790 if is_boundary {
21791 let chunk = &text[prev_index..index];
21792 prev_index = index;
21793 Some(chunk)
21794 } else {
21795 None
21796 }
21797 })
21798}
21799
21800pub trait RangeToAnchorExt: Sized {
21801 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21802
21803 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21804 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21805 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21806 }
21807}
21808
21809impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21810 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21811 let start_offset = self.start.to_offset(snapshot);
21812 let end_offset = self.end.to_offset(snapshot);
21813 if start_offset == end_offset {
21814 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21815 } else {
21816 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21817 }
21818 }
21819}
21820
21821pub trait RowExt {
21822 fn as_f32(&self) -> f32;
21823
21824 fn next_row(&self) -> Self;
21825
21826 fn previous_row(&self) -> Self;
21827
21828 fn minus(&self, other: Self) -> u32;
21829}
21830
21831impl RowExt for DisplayRow {
21832 fn as_f32(&self) -> f32 {
21833 self.0 as f32
21834 }
21835
21836 fn next_row(&self) -> Self {
21837 Self(self.0 + 1)
21838 }
21839
21840 fn previous_row(&self) -> Self {
21841 Self(self.0.saturating_sub(1))
21842 }
21843
21844 fn minus(&self, other: Self) -> u32 {
21845 self.0 - other.0
21846 }
21847}
21848
21849impl RowExt for MultiBufferRow {
21850 fn as_f32(&self) -> f32 {
21851 self.0 as f32
21852 }
21853
21854 fn next_row(&self) -> Self {
21855 Self(self.0 + 1)
21856 }
21857
21858 fn previous_row(&self) -> Self {
21859 Self(self.0.saturating_sub(1))
21860 }
21861
21862 fn minus(&self, other: Self) -> u32 {
21863 self.0 - other.0
21864 }
21865}
21866
21867trait RowRangeExt {
21868 type Row;
21869
21870 fn len(&self) -> usize;
21871
21872 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21873}
21874
21875impl RowRangeExt for Range<MultiBufferRow> {
21876 type Row = MultiBufferRow;
21877
21878 fn len(&self) -> usize {
21879 (self.end.0 - self.start.0) as usize
21880 }
21881
21882 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21883 (self.start.0..self.end.0).map(MultiBufferRow)
21884 }
21885}
21886
21887impl RowRangeExt for Range<DisplayRow> {
21888 type Row = DisplayRow;
21889
21890 fn len(&self) -> usize {
21891 (self.end.0 - self.start.0) as usize
21892 }
21893
21894 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21895 (self.start.0..self.end.0).map(DisplayRow)
21896 }
21897}
21898
21899/// If select range has more than one line, we
21900/// just point the cursor to range.start.
21901fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21902 if range.start.row == range.end.row {
21903 range
21904 } else {
21905 range.start..range.start
21906 }
21907}
21908pub struct KillRing(ClipboardItem);
21909impl Global for KillRing {}
21910
21911const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21912
21913enum BreakpointPromptEditAction {
21914 Log,
21915 Condition,
21916 HitCondition,
21917}
21918
21919struct BreakpointPromptEditor {
21920 pub(crate) prompt: Entity<Editor>,
21921 editor: WeakEntity<Editor>,
21922 breakpoint_anchor: Anchor,
21923 breakpoint: Breakpoint,
21924 edit_action: BreakpointPromptEditAction,
21925 block_ids: HashSet<CustomBlockId>,
21926 editor_margins: Arc<Mutex<EditorMargins>>,
21927 _subscriptions: Vec<Subscription>,
21928}
21929
21930impl BreakpointPromptEditor {
21931 const MAX_LINES: u8 = 4;
21932
21933 fn new(
21934 editor: WeakEntity<Editor>,
21935 breakpoint_anchor: Anchor,
21936 breakpoint: Breakpoint,
21937 edit_action: BreakpointPromptEditAction,
21938 window: &mut Window,
21939 cx: &mut Context<Self>,
21940 ) -> Self {
21941 let base_text = match edit_action {
21942 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21943 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21944 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21945 }
21946 .map(|msg| msg.to_string())
21947 .unwrap_or_default();
21948
21949 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21950 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21951
21952 let prompt = cx.new(|cx| {
21953 let mut prompt = Editor::new(
21954 EditorMode::AutoHeight {
21955 max_lines: Self::MAX_LINES as usize,
21956 },
21957 buffer,
21958 None,
21959 window,
21960 cx,
21961 );
21962 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21963 prompt.set_show_cursor_when_unfocused(false, cx);
21964 prompt.set_placeholder_text(
21965 match edit_action {
21966 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21967 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21968 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21969 },
21970 cx,
21971 );
21972
21973 prompt
21974 });
21975
21976 Self {
21977 prompt,
21978 editor,
21979 breakpoint_anchor,
21980 breakpoint,
21981 edit_action,
21982 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21983 block_ids: Default::default(),
21984 _subscriptions: vec![],
21985 }
21986 }
21987
21988 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21989 self.block_ids.extend(block_ids)
21990 }
21991
21992 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21993 if let Some(editor) = self.editor.upgrade() {
21994 let message = self
21995 .prompt
21996 .read(cx)
21997 .buffer
21998 .read(cx)
21999 .as_singleton()
22000 .expect("A multi buffer in breakpoint prompt isn't possible")
22001 .read(cx)
22002 .as_rope()
22003 .to_string();
22004
22005 editor.update(cx, |editor, cx| {
22006 editor.edit_breakpoint_at_anchor(
22007 self.breakpoint_anchor,
22008 self.breakpoint.clone(),
22009 match self.edit_action {
22010 BreakpointPromptEditAction::Log => {
22011 BreakpointEditAction::EditLogMessage(message.into())
22012 }
22013 BreakpointPromptEditAction::Condition => {
22014 BreakpointEditAction::EditCondition(message.into())
22015 }
22016 BreakpointPromptEditAction::HitCondition => {
22017 BreakpointEditAction::EditHitCondition(message.into())
22018 }
22019 },
22020 cx,
22021 );
22022
22023 editor.remove_blocks(self.block_ids.clone(), None, cx);
22024 cx.focus_self(window);
22025 });
22026 }
22027 }
22028
22029 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22030 self.editor
22031 .update(cx, |editor, cx| {
22032 editor.remove_blocks(self.block_ids.clone(), None, cx);
22033 window.focus(&editor.focus_handle);
22034 })
22035 .log_err();
22036 }
22037
22038 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22039 let settings = ThemeSettings::get_global(cx);
22040 let text_style = TextStyle {
22041 color: if self.prompt.read(cx).read_only(cx) {
22042 cx.theme().colors().text_disabled
22043 } else {
22044 cx.theme().colors().text
22045 },
22046 font_family: settings.buffer_font.family.clone(),
22047 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22048 font_size: settings.buffer_font_size(cx).into(),
22049 font_weight: settings.buffer_font.weight,
22050 line_height: relative(settings.buffer_line_height.value()),
22051 ..Default::default()
22052 };
22053 EditorElement::new(
22054 &self.prompt,
22055 EditorStyle {
22056 background: cx.theme().colors().editor_background,
22057 local_player: cx.theme().players().local(),
22058 text: text_style,
22059 ..Default::default()
22060 },
22061 )
22062 }
22063}
22064
22065impl Render for BreakpointPromptEditor {
22066 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22067 let editor_margins = *self.editor_margins.lock();
22068 let gutter_dimensions = editor_margins.gutter;
22069 h_flex()
22070 .key_context("Editor")
22071 .bg(cx.theme().colors().editor_background)
22072 .border_y_1()
22073 .border_color(cx.theme().status().info_border)
22074 .size_full()
22075 .py(window.line_height() / 2.5)
22076 .on_action(cx.listener(Self::confirm))
22077 .on_action(cx.listener(Self::cancel))
22078 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22079 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22080 }
22081}
22082
22083impl Focusable for BreakpointPromptEditor {
22084 fn focus_handle(&self, cx: &App) -> FocusHandle {
22085 self.prompt.focus_handle(cx)
22086 }
22087}
22088
22089fn all_edits_insertions_or_deletions(
22090 edits: &Vec<(Range<Anchor>, String)>,
22091 snapshot: &MultiBufferSnapshot,
22092) -> bool {
22093 let mut all_insertions = true;
22094 let mut all_deletions = true;
22095
22096 for (range, new_text) in edits.iter() {
22097 let range_is_empty = range.to_offset(&snapshot).is_empty();
22098 let text_is_empty = new_text.is_empty();
22099
22100 if range_is_empty != text_is_empty {
22101 if range_is_empty {
22102 all_deletions = false;
22103 } else {
22104 all_insertions = false;
22105 }
22106 } else {
22107 return false;
22108 }
22109
22110 if !all_insertions && !all_deletions {
22111 return false;
22112 }
22113 }
22114 all_insertions || all_deletions
22115}
22116
22117struct MissingEditPredictionKeybindingTooltip;
22118
22119impl Render for MissingEditPredictionKeybindingTooltip {
22120 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22121 ui::tooltip_container(window, cx, |container, _, cx| {
22122 container
22123 .flex_shrink_0()
22124 .max_w_80()
22125 .min_h(rems_from_px(124.))
22126 .justify_between()
22127 .child(
22128 v_flex()
22129 .flex_1()
22130 .text_ui_sm(cx)
22131 .child(Label::new("Conflict with Accept Keybinding"))
22132 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22133 )
22134 .child(
22135 h_flex()
22136 .pb_1()
22137 .gap_1()
22138 .items_end()
22139 .w_full()
22140 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22141 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22142 }))
22143 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22144 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22145 })),
22146 )
22147 })
22148 }
22149}
22150
22151#[derive(Debug, Clone, Copy, PartialEq)]
22152pub struct LineHighlight {
22153 pub background: Background,
22154 pub border: Option<gpui::Hsla>,
22155 pub include_gutter: bool,
22156 pub type_id: Option<TypeId>,
22157}
22158
22159fn render_diff_hunk_controls(
22160 row: u32,
22161 status: &DiffHunkStatus,
22162 hunk_range: Range<Anchor>,
22163 is_created_file: bool,
22164 line_height: Pixels,
22165 editor: &Entity<Editor>,
22166 _window: &mut Window,
22167 cx: &mut App,
22168) -> AnyElement {
22169 h_flex()
22170 .h(line_height)
22171 .mr_1()
22172 .gap_1()
22173 .px_0p5()
22174 .pb_1()
22175 .border_x_1()
22176 .border_b_1()
22177 .border_color(cx.theme().colors().border_variant)
22178 .rounded_b_lg()
22179 .bg(cx.theme().colors().editor_background)
22180 .gap_1()
22181 .block_mouse_except_scroll()
22182 .shadow_md()
22183 .child(if status.has_secondary_hunk() {
22184 Button::new(("stage", row as u64), "Stage")
22185 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22186 .tooltip({
22187 let focus_handle = editor.focus_handle(cx);
22188 move |window, cx| {
22189 Tooltip::for_action_in(
22190 "Stage Hunk",
22191 &::git::ToggleStaged,
22192 &focus_handle,
22193 window,
22194 cx,
22195 )
22196 }
22197 })
22198 .on_click({
22199 let editor = editor.clone();
22200 move |_event, _window, cx| {
22201 editor.update(cx, |editor, cx| {
22202 editor.stage_or_unstage_diff_hunks(
22203 true,
22204 vec![hunk_range.start..hunk_range.start],
22205 cx,
22206 );
22207 });
22208 }
22209 })
22210 } else {
22211 Button::new(("unstage", row as u64), "Unstage")
22212 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22213 .tooltip({
22214 let focus_handle = editor.focus_handle(cx);
22215 move |window, cx| {
22216 Tooltip::for_action_in(
22217 "Unstage Hunk",
22218 &::git::ToggleStaged,
22219 &focus_handle,
22220 window,
22221 cx,
22222 )
22223 }
22224 })
22225 .on_click({
22226 let editor = editor.clone();
22227 move |_event, _window, cx| {
22228 editor.update(cx, |editor, cx| {
22229 editor.stage_or_unstage_diff_hunks(
22230 false,
22231 vec![hunk_range.start..hunk_range.start],
22232 cx,
22233 );
22234 });
22235 }
22236 })
22237 })
22238 .child(
22239 Button::new(("restore", row as u64), "Restore")
22240 .tooltip({
22241 let focus_handle = editor.focus_handle(cx);
22242 move |window, cx| {
22243 Tooltip::for_action_in(
22244 "Restore Hunk",
22245 &::git::Restore,
22246 &focus_handle,
22247 window,
22248 cx,
22249 )
22250 }
22251 })
22252 .on_click({
22253 let editor = editor.clone();
22254 move |_event, window, cx| {
22255 editor.update(cx, |editor, cx| {
22256 let snapshot = editor.snapshot(window, cx);
22257 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22258 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22259 });
22260 }
22261 })
22262 .disabled(is_created_file),
22263 )
22264 .when(
22265 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22266 |el| {
22267 el.child(
22268 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22269 .shape(IconButtonShape::Square)
22270 .icon_size(IconSize::Small)
22271 // .disabled(!has_multiple_hunks)
22272 .tooltip({
22273 let focus_handle = editor.focus_handle(cx);
22274 move |window, cx| {
22275 Tooltip::for_action_in(
22276 "Next Hunk",
22277 &GoToHunk,
22278 &focus_handle,
22279 window,
22280 cx,
22281 )
22282 }
22283 })
22284 .on_click({
22285 let editor = editor.clone();
22286 move |_event, window, cx| {
22287 editor.update(cx, |editor, cx| {
22288 let snapshot = editor.snapshot(window, cx);
22289 let position =
22290 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22291 editor.go_to_hunk_before_or_after_position(
22292 &snapshot,
22293 position,
22294 Direction::Next,
22295 window,
22296 cx,
22297 );
22298 editor.expand_selected_diff_hunks(cx);
22299 });
22300 }
22301 }),
22302 )
22303 .child(
22304 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22305 .shape(IconButtonShape::Square)
22306 .icon_size(IconSize::Small)
22307 // .disabled(!has_multiple_hunks)
22308 .tooltip({
22309 let focus_handle = editor.focus_handle(cx);
22310 move |window, cx| {
22311 Tooltip::for_action_in(
22312 "Previous Hunk",
22313 &GoToPreviousHunk,
22314 &focus_handle,
22315 window,
22316 cx,
22317 )
22318 }
22319 })
22320 .on_click({
22321 let editor = editor.clone();
22322 move |_event, window, cx| {
22323 editor.update(cx, |editor, cx| {
22324 let snapshot = editor.snapshot(window, cx);
22325 let point =
22326 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22327 editor.go_to_hunk_before_or_after_position(
22328 &snapshot,
22329 point,
22330 Direction::Prev,
22331 window,
22332 cx,
22333 );
22334 editor.expand_selected_diff_hunks(cx);
22335 });
22336 }
22337 }),
22338 )
22339 },
22340 )
22341 .into_any_element()
22342}