@@ -1996,6 +1996,7 @@ impl InputState {
19961996 pub ( crate ) fn select_to ( & mut self , offset : usize , cx : & mut Context < Self > ) {
19971997 self . clear_inline_completion ( cx) ;
19981998
1999+ let previous_cursor = self . cursor ( ) ;
19992000 let offset = offset. clamp ( 0 , self . text . len ( ) ) ;
20002001 if self . selection_reversed {
20012002 self . selected_range . start = offset
@@ -2020,6 +2021,15 @@ impl InputState {
20202021 if self . selected_range . is_empty ( ) {
20212022 self . update_preferred_column ( ) ;
20222023 }
2024+ let cursor = self . cursor ( ) ;
2025+ let direction = if cursor < previous_cursor {
2026+ Some ( MoveDirection :: Up )
2027+ } else if cursor > previous_cursor {
2028+ Some ( MoveDirection :: Down )
2029+ } else {
2030+ None
2031+ } ;
2032+ self . scroll_to ( cursor, direction, cx) ;
20232033 cx. notify ( )
20242034 }
20252035
@@ -2816,45 +2826,32 @@ mod tests {
28162826 use crate :: theme:: Theme ;
28172827 use gpui:: { TestAppContext , VisualTestContext } ;
28182828
2819- struct InputView {
2820- input : Entity < InputState > ,
2821- window_handle : gpui:: WindowHandle < Root > ,
2822- }
2823-
28242829 /// Helper to create an InputState in a window for testing
2825- impl InputView {
2826- pub fn new ( cx : & mut TestAppContext ) -> Self {
2827- let mut input: Option < Entity < InputState > > = None ;
2828-
2829- let window = cx. update ( |cx| {
2830- cx. open_window ( Default :: default ( ) , |window, cx| {
2831- // Set up the theme first
2832- cx. set_global ( Theme :: default ( ) ) ;
2833- // Initialize input keybindings
2834- super :: super :: init ( cx) ;
2835-
2836- input = Some ( cx. new ( |cx| InputState :: new ( window, cx) . code_editor ( "sql" ) ) ) ;
2837-
2838- cx. new ( |cx| crate :: Root :: new ( input. clone ( ) . unwrap ( ) , window, cx) )
2839- } )
2840- . unwrap ( )
2841- } ) ;
2830+ fn new_code_editor ( cx : & mut TestAppContext ) -> ( Entity < InputState > , & mut VisualTestContext ) {
2831+ cx. update ( |cx| {
2832+ // Set up the theme first
2833+ cx. set_global ( Theme :: default ( ) ) ;
2834+ // Initialize input keybindings
2835+ super :: super :: init ( cx) ;
2836+ } ) ;
2837+ cx. add_window_view ( |window, cx| InputState :: new ( window, cx) . code_editor ( "sql" ) )
2838+ }
28422839
2843- Self {
2844- input : input. clone ( ) . unwrap ( ) ,
2845- window_handle : window,
2846- }
2847- }
2840+ fn draw_input ( cx : & mut VisualTestContext , input : & Entity < InputState > ) {
2841+ cx. draw (
2842+ point ( px ( 0. ) , px ( 0. ) ) ,
2843+ gpui:: size ( px ( 800. ) , px ( 240. ) ) ,
2844+ |_, _| input. clone ( ) . into_any_element ( ) ,
2845+ ) ;
28482846 }
28492847
28502848 #[ gpui:: test]
28512849 fn test_highlighting_preserved_after_fold ( cx : & mut TestAppContext ) {
28522850 use crate :: highlighter:: HighlightTheme ;
28532851 use crate :: input:: display_map:: FoldRange ;
28542852
2855- let input_view = InputView :: new ( cx) ;
2856- let mut cx = VisualTestContext :: from_window ( input_view. window_handle . into ( ) , cx) ;
2857- let input = input_view. input ;
2853+ let ( input, cx) = new_code_editor ( cx) ;
2854+ let cx: & mut VisualTestContext = cx;
28582855
28592856 // SQL text: fold the SELECT..WHERE block, verify comments keep highlighting.
28602857 // Lines 0-9: SELECT block (fold range 0..9 hides lines 1-8)
@@ -2880,7 +2877,7 @@ ORDER BY id
28802877 state. set_value ( text, window, cx) ;
28812878 } ) ;
28822879 } ) ;
2883- cx . run_until_parked ( ) ;
2880+ draw_input ( cx , & input ) ;
28842881
28852882 // Grab styles for "-- Comment 1" (line 11) before folding
28862883 let theme = HighlightTheme :: default_dark ( ) ;
@@ -2911,7 +2908,7 @@ ORDER BY id
29112908 state. display_map . set_folded ( 0 , true ) ;
29122909 } ) ;
29132910 } ) ;
2914- cx . run_until_parked ( ) ;
2911+ draw_input ( cx , & input ) ;
29152912
29162913 // Verify fold is active and lines 1-8 are hidden
29172914 cx. update ( |_, cx| {
@@ -2964,4 +2961,54 @@ ORDER BY id
29642961 colored_before, colored_after
29652962 ) ;
29662963 }
2964+
2965+ #[ gpui:: test]
2966+ fn select_to_scrolls_reversed_selection_anchor_into_view ( cx : & mut TestAppContext ) {
2967+ let ( input, cx) = new_code_editor ( cx) ;
2968+ let cx: & mut VisualTestContext = cx;
2969+ let text = ( 0 ..120 )
2970+ . map ( |line| format ! ( "select {line};" ) )
2971+ . collect :: < Vec < _ > > ( )
2972+ . join ( "\n " ) ;
2973+
2974+ cx. update ( |window, cx| {
2975+ input. update ( cx, |state, cx| state. set_value ( text, window, cx) ) ;
2976+ } ) ;
2977+ draw_input ( cx, & input) ;
2978+
2979+ let bottom_offset =
2980+ cx. update ( |_, cx| input. read_with ( cx, |state, _| state. text . line_start_offset ( 100 ) ) ) ;
2981+ cx. update ( |_, cx| {
2982+ input. update ( cx, |state, cx| {
2983+ state. move_to ( bottom_offset, Some ( MoveDirection :: Down ) , cx) ;
2984+ } ) ;
2985+ } ) ;
2986+ draw_input ( cx, & input) ;
2987+
2988+ let before_scroll =
2989+ cx. update ( |_, cx| input. read_with ( cx, |state, _| state. scroll_offset ( ) ) ) ;
2990+ let target_offset =
2991+ cx. update ( |_, cx| input. read_with ( cx, |state, _| state. text . line_start_offset ( 40 ) ) ) ;
2992+ cx. update ( |_, cx| {
2993+ input. update ( cx, |state, cx| {
2994+ state. select_to ( target_offset, cx) ;
2995+ } ) ;
2996+ } ) ;
2997+ draw_input ( cx, & input) ;
2998+
2999+ let after_select = cx. update ( |_, cx| {
3000+ input. read_with ( cx, |state, _| {
3001+ (
3002+ state. cursor ( ) ,
3003+ state. selection_reversed ,
3004+ state. selected_range ,
3005+ state. scroll_offset ( ) ,
3006+ )
3007+ } )
3008+ } ) ;
3009+ assert ! (
3010+ after_select. 3 . y > before_scroll. y,
3011+ "expected upward selection to move viewport up, bottom={bottom_offset}, target={target_offset}, before={before_scroll:?}, state={after_select:?}"
3012+ ) ;
3013+ }
29673014}
0 commit comments