1+ use std:: fmt;
12use std:: io;
23use std:: io:: Write ;
34
45use crate :: tui;
6+ use crossterm:: Command ;
57use crossterm:: queue;
68use crossterm:: style:: Color as CColor ;
79use crossterm:: style:: Colors ;
@@ -11,46 +13,127 @@ use crossterm::style::SetBackgroundColor;
1113use crossterm:: style:: SetColors ;
1214use crossterm:: style:: SetForegroundColor ;
1315use ratatui:: layout:: Position ;
16+ use ratatui:: layout:: Size ;
1417use ratatui:: prelude:: Backend ;
1518use ratatui:: style:: Color ;
1619use ratatui:: style:: Modifier ;
1720use ratatui:: text:: Line ;
1821use ratatui:: text:: Span ;
1922
23+ /// Insert `lines` above the viewport.
2024pub ( crate ) fn insert_history_lines ( terminal : & mut tui:: Tui , lines : Vec < Line < ' static > > ) {
21- let screen_height = terminal
22- . backend ( )
23- . size ( )
24- . map ( |s| s. height )
25- . unwrap_or ( 0xffffu16 ) ;
25+ let screen_size = terminal. backend ( ) . size ( ) . unwrap_or ( Size :: new ( 0 , 0 ) ) ;
26+
2627 let mut area = terminal. get_frame ( ) . area ( ) ;
27- // We scroll up one line at a time because we can't position the cursor
28- // above the top of the screen. i.e. if
29- // lines.len() > screen_height - area.top()
30- // we would need to print the first line above the top of the screen, which
31- // can't be done.
32- for line in lines. into_iter ( ) {
33- // 1. Scroll everything above the viewport up by one line
34- if area. bottom ( ) >= screen_height {
35- let top = area. top ( ) ;
36- terminal. backend_mut ( ) . scroll_region_up ( 0 ..top, 1 ) . ok ( ) ;
37- // 2. Move the cursor to the blank line
38- terminal. set_cursor_position ( Position :: new ( 0 , top - 1 ) ) . ok ( ) ;
39- } else {
40- // If the viewport isn't at the bottom of the screen, scroll down instead
41- terminal
42- . backend_mut ( )
43- . scroll_region_down ( area. top ( ) ..area. bottom ( ) + 1 , 1 )
44- . ok ( ) ;
45- terminal
46- . set_cursor_position ( Position :: new ( 0 , area. top ( ) ) )
47- . ok ( ) ;
48- area. y += 1 ;
49- }
50- // 3. Write the line
28+
29+ let wrapped_lines = wrapped_line_count ( & lines, area. width ) ;
30+ let cursor_top = if area. bottom ( ) < screen_size. height {
31+ // If the viewport is not at the bottom of the screen, scroll it down to make room.
32+ // Don't scroll it past the bottom of the screen.
33+ let scroll_amount = wrapped_lines. min ( screen_size. height - area. bottom ( ) ) ;
34+ terminal
35+ . backend_mut ( )
36+ . scroll_region_down ( area. top ( ) ..screen_size. height , scroll_amount)
37+ . ok ( ) ;
38+ let cursor_top = area. top ( ) - 1 ;
39+ area. y += scroll_amount;
40+ terminal. set_viewport_area ( area) ;
41+ cursor_top
42+ } else {
43+ area. top ( ) - 1
44+ } ;
45+
46+ // Limit the scroll region to the lines from the top of the screen to the
47+ // top of the viewport. With this in place, when we add lines inside this
48+ // area, only the lines in this area will be scrolled. We place the cursor
49+ // at the end of the scroll region, and add lines starting there.
50+ //
51+ // ┌─Screen───────────────────────┐
52+ // │┌╌Scroll region╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐│
53+ // │┆ ┆│
54+ // │┆ ┆│
55+ // │┆ ┆│
56+ // │█╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘│
57+ // │╭─Viewport───────────────────╮│
58+ // ││ ││
59+ // │╰────────────────────────────╯│
60+ // └──────────────────────────────┘
61+ queue ! ( std:: io:: stdout( ) , SetScrollRegion ( 1 ..area. top( ) ) ) . ok ( ) ;
62+
63+ terminal
64+ . set_cursor_position ( Position :: new ( 0 , cursor_top) )
65+ . ok ( ) ;
66+
67+ for line in lines {
68+ queue ! ( std:: io:: stdout( ) , Print ( "\r \n " ) ) . ok ( ) ;
5169 write_spans ( & mut std:: io:: stdout ( ) , line. iter ( ) ) . ok ( ) ;
5270 }
53- terminal. set_viewport_area ( area) ;
71+
72+ queue ! ( std:: io:: stdout( ) , ResetScrollRegion ) . ok ( ) ;
73+ }
74+
75+ fn wrapped_line_count ( lines : & [ Line ] , width : u16 ) -> u16 {
76+ let mut count = 0 ;
77+ for line in lines {
78+ count += line_height ( line, width) ;
79+ }
80+ count
81+ }
82+
83+ fn line_height ( line : & Line , width : u16 ) -> u16 {
84+ use unicode_width:: UnicodeWidthStr ;
85+ // get the total display width of the line, accounting for double-width chars
86+ let total_width = line
87+ . spans
88+ . iter ( )
89+ . map ( |span| span. content . width ( ) )
90+ . sum :: < usize > ( ) ;
91+ // divide by width to get the number of lines, rounding up
92+ if width == 0 {
93+ 1
94+ } else {
95+ ( total_width as u16 ) . div_ceil ( width) . max ( 1 )
96+ }
97+ }
98+
99+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
100+ pub struct SetScrollRegion ( pub std:: ops:: Range < u16 > ) ;
101+
102+ impl Command for SetScrollRegion {
103+ fn write_ansi ( & self , f : & mut impl fmt:: Write ) -> fmt:: Result {
104+ write ! ( f, "\x1b [{};{}r" , self . 0 . start, self . 0 . end)
105+ }
106+
107+ #[ cfg( windows) ]
108+ fn execute_winapi ( & self ) -> std:: io:: Result < ( ) > {
109+ panic ! ( "tried to execute SetScrollRegion command using WinAPI, use ANSI instead" ) ;
110+ }
111+
112+ #[ cfg( windows) ]
113+ fn is_ansi_code_supported ( & self ) -> bool {
114+ // TODO(nornagon): is this supported on Windows?
115+ true
116+ }
117+ }
118+
119+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
120+ pub struct ResetScrollRegion ;
121+
122+ impl Command for ResetScrollRegion {
123+ fn write_ansi ( & self , f : & mut impl fmt:: Write ) -> fmt:: Result {
124+ write ! ( f, "\x1b [r" )
125+ }
126+
127+ #[ cfg( windows) ]
128+ fn execute_winapi ( & self ) -> std:: io:: Result < ( ) > {
129+ panic ! ( "tried to execute ResetScrollRegion command using WinAPI, use ANSI instead" ) ;
130+ }
131+
132+ #[ cfg( windows) ]
133+ fn is_ansi_code_supported ( & self ) -> bool {
134+ // TODO(nornagon): is this supported on Windows?
135+ true
136+ }
54137}
55138
56139struct ModifierDiff {
0 commit comments