@@ -2,20 +2,28 @@ use crate::command::command_interceptor;
2
2
use crate :: normal:: repeat:: Replayer ;
3
3
use crate :: surrounds:: SurroundsType ;
4
4
use crate :: { motion:: Motion , object:: Object } ;
5
- use crate :: { UseSystemClipboard , Vim , VimSettings } ;
5
+ use crate :: { ToggleRegistersView , UseSystemClipboard , Vim , VimSettings } ;
6
6
use collections:: HashMap ;
7
7
use command_palette_hooks:: { CommandPaletteFilter , CommandPaletteInterceptor } ;
8
+ use editor:: display_map:: { is_invisible, replacement} ;
8
9
use editor:: { Anchor , ClipboardSelection , Editor } ;
9
10
use gpui:: {
10
- Action , App , BorrowAppContext , ClipboardEntry , ClipboardItem , Entity , Global , WeakEntity ,
11
+ Action , App , BorrowAppContext , ClipboardEntry , ClipboardItem , Entity , Global , HighlightStyle ,
12
+ StyledText , Task , TextStyle , WeakEntity ,
11
13
} ;
12
14
use language:: Point ;
15
+ use picker:: { Picker , PickerDelegate } ;
13
16
use serde:: { Deserialize , Serialize } ;
14
17
use settings:: { Settings , SettingsStore } ;
15
18
use std:: borrow:: BorrowMut ;
16
19
use std:: { fmt:: Display , ops:: Range , sync:: Arc } ;
17
- use ui:: { Context , KeyBinding , SharedString } ;
20
+ use theme:: ThemeSettings ;
21
+ use ui:: {
22
+ h_flex, rems, ActiveTheme , Context , Div , FluentBuilder , KeyBinding , ParentElement ,
23
+ SharedString , Styled , StyledTypography , Window ,
24
+ } ;
18
25
use workspace:: searchable:: Direction ;
26
+ use workspace:: Workspace ;
19
27
20
28
#[ derive( Clone , Copy , Debug , PartialEq , Serialize , Deserialize ) ]
21
29
pub enum Mode {
@@ -215,6 +223,11 @@ impl VimGlobals {
215
223
} )
216
224
. detach ( ) ;
217
225
226
+ cx. observe_new ( |workspace : & mut Workspace , window, _| {
227
+ RegistersView :: register ( workspace, window) ;
228
+ } )
229
+ . detach ( ) ;
230
+
218
231
cx. observe_global :: < SettingsStore > ( move |cx| {
219
232
if Vim :: enabled ( cx) {
220
233
KeyBinding :: set_vim_mode ( cx, true ) ;
@@ -315,10 +328,10 @@ impl VimGlobals {
315
328
}
316
329
317
330
pub ( crate ) fn read_register (
318
- & mut self ,
331
+ & self ,
319
332
register : Option < char > ,
320
333
editor : Option < & mut Editor > ,
321
- cx : & mut Context < Editor > ,
334
+ cx : & mut App ,
322
335
) -> Option < Register > {
323
336
let Some ( register) = register. filter ( |reg| * reg != '"' ) else {
324
337
let setting = VimSettings :: get_global ( cx) . use_system_clipboard ;
@@ -363,7 +376,7 @@ impl VimGlobals {
363
376
}
364
377
}
365
378
366
- fn system_clipboard_is_newer ( & self , cx : & mut Context < Editor > ) -> bool {
379
+ fn system_clipboard_is_newer ( & self , cx : & App ) -> bool {
367
380
cx. read_from_clipboard ( ) . is_some_and ( |item| {
368
381
if let Some ( last_state) = & self . last_yank {
369
382
Some ( last_state. as_ref ( ) ) != item. text ( ) . as_deref ( )
@@ -599,3 +612,190 @@ impl Operator {
599
612
}
600
613
}
601
614
}
615
+
616
+ struct RegisterMatch {
617
+ name : char ,
618
+ contents : SharedString ,
619
+ }
620
+
621
+ pub struct RegistersViewDelegate {
622
+ selected_index : usize ,
623
+ matches : Vec < RegisterMatch > ,
624
+ }
625
+
626
+ impl PickerDelegate for RegistersViewDelegate {
627
+ type ListItem = Div ;
628
+
629
+ fn match_count ( & self ) -> usize {
630
+ self . matches . len ( )
631
+ }
632
+
633
+ fn selected_index ( & self ) -> usize {
634
+ self . selected_index
635
+ }
636
+
637
+ fn set_selected_index ( & mut self , ix : usize , _: & mut Window , cx : & mut Context < Picker < Self > > ) {
638
+ self . selected_index = ix;
639
+ cx. notify ( ) ;
640
+ }
641
+
642
+ fn placeholder_text ( & self , _window : & mut Window , _cx : & mut App ) -> Arc < str > {
643
+ Arc :: default ( )
644
+ }
645
+
646
+ fn update_matches (
647
+ & mut self ,
648
+ _: String ,
649
+ _: & mut Window ,
650
+ _: & mut Context < Picker < Self > > ,
651
+ ) -> gpui:: Task < ( ) > {
652
+ Task :: ready ( ( ) )
653
+ }
654
+
655
+ fn confirm ( & mut self , _: bool , _: & mut Window , _: & mut Context < Picker < Self > > ) { }
656
+
657
+ fn dismissed ( & mut self , _: & mut Window , _: & mut Context < Picker < Self > > ) { }
658
+
659
+ fn render_match (
660
+ & self ,
661
+ ix : usize ,
662
+ selected : bool ,
663
+ _: & mut Window ,
664
+ cx : & mut Context < Picker < Self > > ,
665
+ ) -> Option < Self :: ListItem > {
666
+ let register_match = self
667
+ . matches
668
+ . get ( ix)
669
+ . expect ( "Invalid matches state: no element for index {ix}" ) ;
670
+
671
+ let mut output = String :: new ( ) ;
672
+ let mut runs = Vec :: new ( ) ;
673
+ output. push ( '"' ) ;
674
+ output. push ( register_match. name ) ;
675
+ runs. push ( (
676
+ 0 ..output. len ( ) ,
677
+ HighlightStyle :: color ( cx. theme ( ) . colors ( ) . text_accent ) ,
678
+ ) ) ;
679
+ output. push ( ' ' ) ;
680
+ output. push ( ' ' ) ;
681
+ let mut base = output. len ( ) ;
682
+ for ( ix, c) in register_match. contents . char_indices ( ) {
683
+ if ix > 100 {
684
+ break ;
685
+ }
686
+ let replace = match c {
687
+ '\t' => Some ( "\\ t" . to_string ( ) ) ,
688
+ '\n' => Some ( "\\ n" . to_string ( ) ) ,
689
+ '\r' => Some ( "\\ r" . to_string ( ) ) ,
690
+ c if is_invisible ( c) => {
691
+ if c <= '\x1f' {
692
+ replacement ( c) . map ( |s| s. to_string ( ) )
693
+ } else {
694
+ Some ( format ! ( "\\ u{:04X}" , c as u32 ) )
695
+ }
696
+ }
697
+ _ => None ,
698
+ } ;
699
+ let Some ( replace) = replace else {
700
+ output. push ( c) ;
701
+ continue ;
702
+ } ;
703
+ output. push_str ( & replace) ;
704
+ runs. push ( (
705
+ base + ix..base + ix + replace. len ( ) ,
706
+ HighlightStyle :: color ( cx. theme ( ) . colors ( ) . text_muted ) ,
707
+ ) ) ;
708
+ base += replace. len ( ) - c. len_utf8 ( ) ;
709
+ }
710
+
711
+ let theme = ThemeSettings :: get_global ( cx) ;
712
+ let text_style = TextStyle {
713
+ color : cx. theme ( ) . colors ( ) . editor_foreground ,
714
+ font_family : theme. buffer_font . family . clone ( ) ,
715
+ font_features : theme. buffer_font . features . clone ( ) ,
716
+ font_fallbacks : theme. buffer_font . fallbacks . clone ( ) ,
717
+ font_size : theme. buffer_font_size ( cx) . into ( ) ,
718
+ line_height : ( theme. line_height ( ) * theme. buffer_font_size ( cx) ) . into ( ) ,
719
+ font_weight : theme. buffer_font . weight ,
720
+ font_style : theme. buffer_font . style ,
721
+ ..Default :: default ( )
722
+ } ;
723
+
724
+ Some (
725
+ h_flex ( )
726
+ . when ( selected, |el| el. bg ( cx. theme ( ) . colors ( ) . element_selected ) )
727
+ . font_buffer ( cx)
728
+ . text_buffer ( cx)
729
+ . h ( theme. buffer_font_size ( cx) * theme. line_height ( ) )
730
+ . px_2 ( )
731
+ . gap_1 ( )
732
+ . child ( StyledText :: new ( output) . with_default_highlights ( & text_style, runs) ) ,
733
+ )
734
+ }
735
+ }
736
+
737
+ pub struct RegistersView { }
738
+
739
+ impl RegistersView {
740
+ fn register ( workspace : & mut Workspace , _window : Option < & mut Window > ) {
741
+ workspace. register_action ( |workspace, _: & ToggleRegistersView , window, cx| {
742
+ Self :: toggle ( workspace, window, cx) ;
743
+ } ) ;
744
+ }
745
+
746
+ pub fn toggle ( workspace : & mut Workspace , window : & mut Window , cx : & mut Context < Workspace > ) {
747
+ let editor = workspace
748
+ . active_item ( cx)
749
+ . and_then ( |item| item. act_as :: < Editor > ( cx) ) ;
750
+ workspace. toggle_modal ( window, cx, move |window, cx| {
751
+ RegistersView :: new ( editor, window, cx)
752
+ } ) ;
753
+ }
754
+
755
+ fn new (
756
+ editor : Option < Entity < Editor > > ,
757
+ window : & mut Window ,
758
+ cx : & mut Context < Picker < RegistersViewDelegate > > ,
759
+ ) -> Picker < RegistersViewDelegate > {
760
+ let mut matches = Vec :: default ( ) ;
761
+ cx. update_global ( |globals : & mut VimGlobals , cx| {
762
+ for name in [ '"' , '+' , '*' ] {
763
+ if let Some ( register) = globals. read_register ( Some ( name) , None , cx) {
764
+ matches. push ( RegisterMatch {
765
+ name,
766
+ contents : register. text . clone ( ) ,
767
+ } )
768
+ }
769
+ }
770
+ if let Some ( editor) = editor {
771
+ let register = editor. update ( cx, |editor, cx| {
772
+ globals. read_register ( Some ( '%' ) , Some ( editor) , cx)
773
+ } ) ;
774
+ if let Some ( register) = register {
775
+ matches. push ( RegisterMatch {
776
+ name : '%' ,
777
+ contents : register. text . clone ( ) ,
778
+ } )
779
+ }
780
+ }
781
+ for ( name, register) in globals. registers . iter ( ) {
782
+ if [ '"' , '+' , '*' , '%' ] . contains ( name) {
783
+ continue ;
784
+ } ;
785
+ matches. push ( RegisterMatch {
786
+ name : * name,
787
+ contents : register. text . clone ( ) ,
788
+ } )
789
+ }
790
+ } ) ;
791
+ matches. sort_by ( |a, b| a. name . cmp ( & b. name ) ) ;
792
+ let delegate = RegistersViewDelegate {
793
+ selected_index : 0 ,
794
+ matches,
795
+ } ;
796
+
797
+ Picker :: nonsearchable_uniform_list ( delegate, window, cx)
798
+ . width ( rems ( 36. ) )
799
+ . modal ( true )
800
+ }
801
+ }
0 commit comments