diff --git a/terminal.cpp b/terminal.cpp index 6d9c388..d8071a2 100644 --- a/terminal.cpp +++ b/terminal.cpp @@ -45,24 +45,18 @@ void Emulator::execute( Parser::Execute *act ) switch ( act->ch ) { case 0x0a: /* LF */ - fb.cursor_row++; - fb.autoscroll(); - fb.newgrapheme(); + fb.move_rows_autoscroll( 1 ); act->handled = true; break; case 0x0d: /* CR */ - fb.cursor_col = 0; - fb.newgrapheme(); + fb.ds.move_col( 0 ); act->handled = true; break; case 0x08: /* BS */ - if ( fb.cursor_col > 0 ) { - fb.cursor_col--; - fb.newgrapheme(); /* this is not xterm's behavior */ - act->handled = true; - } + fb.ds.move_col( -1, true ); + act->handled = true; break; } } @@ -71,56 +65,47 @@ void Emulator::print( Parser::Print *act ) { assert( act->char_present ); - if ( (fb.width == 0) || (fb.height == 0) ) { + int chwidth = act->ch == L'\0' ? -1 : wcwidth( act->ch ); + + Cell *this_cell = fb.get_cell(); + + if ( !this_cell ) { /* zero-size framebuffer */ return; } - assert( fb.cursor_row < fb.height ); /* must be on screen */ - assert( fb.cursor_col <= fb.width + 1 ); /* two off is ok */ - assert( fb.combining_char_row < fb.height ); - assert( fb.combining_char_col < fb.width ); - - int chwidth = act->ch == L'\0' ? -1 : wcwidth( act->ch ); - - Cell *this_cell; + Cell *combining_cell = fb.get_combining_cell(); switch ( chwidth ) { case 1: /* normal character */ case 2: /* wide character */ - if ( fb.cursor_col >= fb.width ) { /* wrap */ - fb.cursor_col = 0; - fb.cursor_row++; + if ( fb.ds.next_print_will_wrap ) { + fb.ds.move_col( 0 ); + fb.move_rows_autoscroll( 1 ); } - fb.autoscroll(); - fb.newgrapheme(); + this_cell = fb.get_cell(); - this_cell = &fb.rows[ fb.cursor_row ].cells[ fb.cursor_col ]; this_cell->reset(); this_cell->contents.push_back( act->ch ); + this_cell->width = chwidth; - if ( (fb.cursor_col < fb.width - 1) && (chwidth == 2) ) { - Cell *next_cell = &fb.rows[ fb.cursor_row ].cells[ fb.cursor_col + 1 ]; - this_cell->overlapped_cells.push_back( next_cell ); - next_cell->overlapping_cell = this_cell; - } + fb.claim_overlap( fb.ds.get_cursor_row(), fb.ds.get_cursor_col() ); + + fb.ds.move_col( chwidth, true, true ); - fb.cursor_col += chwidth; act->handled = true; break; case 0: /* combining character */ - if ( fb.rows[ fb.combining_char_row ].cells[ fb.combining_char_col ].contents.size() == 0 ) { + if ( combining_cell->contents.size() == 0 ) { /* cell starts with combining character */ - fb.rows[ fb.combining_char_row ].cells[ fb.combining_char_col ].fallback = true; - assert( fb.cursor_col == fb.combining_char_col ); - assert( fb.cursor_row == fb.combining_char_row ); - assert( fb.cursor_col < fb.width ); - fb.cursor_col++; - /* a combining character should never be able to wrap us */ + assert( this_cell == combining_cell ); + assert( combining_cell->width == 1 ); + combining_cell->fallback = true; + fb.ds.move_col( 1, true, true ); } - if ( fb.rows[ fb.combining_char_row ].cells[ fb.combining_char_col ].contents.size() < 16 ) { /* seems like a reasonable limit on combining character */ - fb.rows[ fb.combining_char_row ].cells[ fb.combining_char_col ].contents.push_back( act->ch ); + if ( combining_cell->contents.size() < 16 ) { /* seems like a reasonable limit on combining characters */ + combining_cell->contents.push_back( act->ch ); } act->handled = true; break; @@ -131,42 +116,6 @@ void Emulator::print( Parser::Print *act ) } } -void Emulator::debug_printout( int fd ) -{ - std::string screen; - screen.append( "\033[H\033[2J" ); - - for ( int y = 0; y < fb.height; y++ ) { - for ( int x = 0; x < fb.width; x++ ) { - char curmove[ 32 ]; - snprintf( curmove, 32, "\033[%d;%dH", y + 1, x + 1 ); - screen.append( curmove ); - Cell *cell = &fb.rows[ y ].cells[ x ]; - if ( cell->overlapping_cell ) continue; - - if ( cell->fallback ) { - char utf8[ 8 ]; - snprintf( utf8, 8, "%lc", 0xA0 ); - screen.append( utf8 ); - } - - for ( std::vector::iterator i = cell->contents.begin(); - i != cell->contents.end(); - i++ ) { - char utf8[ 8 ]; - snprintf( utf8, 8, "%lc", *i ); - screen.append( utf8 ); - } - } - } - - char curmove[ 32 ]; - snprintf( curmove, 32, "\033[%d;%dH", fb.cursor_row + 1, fb.cursor_col + 1 ); - screen.append( curmove ); - - swrite( fd, screen.c_str() ); -} - void Emulator::CSI_dispatch( Parser::CSI_Dispatch *act ) { /* add final char to dispatch key */ @@ -212,3 +161,43 @@ void Emulator::Esc_dispatch( Parser::Esc_Dispatch *act ) act->handled = true; } } + +void Emulator::debug_printout( int fd ) +{ + std::string screen; + screen.append( "\033[H\033[2J" ); + + for ( int y = 0; y < fb.ds.get_height(); y++ ) { + for ( int x = 0; x < fb.ds.get_width(); x++ ) { + char curmove[ 32 ]; + snprintf( curmove, 32, "\033[%d;%dH", y + 1, x + 1 ); + screen.append( curmove ); + Cell *cell = fb.get_cell( y, x ); + if ( cell->overlapping_cell ) continue; + + assert( (cell->overlapped_cells.size() + 1 == (size_t)cell->width) + || (x == fb.ds.get_width() - 1) ); + + if ( cell->fallback ) { + char utf8[ 8 ]; + snprintf( utf8, 8, "%lc", 0xA0 ); + screen.append( utf8 ); + } + + for ( std::vector::iterator i = cell->contents.begin(); + i != cell->contents.end(); + i++ ) { + char utf8[ 8 ]; + snprintf( utf8, 8, "%lc", *i ); + screen.append( utf8 ); + } + } + } + + char curmove[ 32 ]; + snprintf( curmove, 32, "\033[%d;%dH", fb.ds.get_cursor_row() + 1, + fb.ds.get_cursor_col() + 1 ); + screen.append( curmove ); + + swrite( fd, screen.c_str() ); +} diff --git a/terminalcsi.cpp b/terminalcsi.cpp index 446723b..b3d6f11 100644 --- a/terminalcsi.cpp +++ b/terminalcsi.cpp @@ -4,49 +4,42 @@ using namespace Terminal; +static void clearline( Framebuffer *fb, int row, int start, int end ) +{ + for ( int col = start; col <= end; col++ ) { + fb->get_cell( row, col )->reset(); + } +} + void Emulator::CSI_EL( void ) { + /* default: active position to end of line, inclusive */ + int start = fb.ds.get_cursor_col(), end = fb.ds.get_width() - 1; + if ( as.params == "1" ) { /* start of screen to active position, inclusive */ - for ( int x = 0; x <= fb.cursor_col; x++ ) { - if ( x < fb.width ) { - fb.rows[ fb.cursor_row ].cells[ x ].reset(); - } - } + start = 0; + end = fb.ds.get_cursor_col(); } else if ( as.params == "2" ) { /* all of line */ - fb.rows[ fb.cursor_row ] = Row( fb.width ); - } else { /* active position to end of line, inclusive */ - for ( int x = fb.cursor_col; x < fb.width; x++ ) { - fb.rows[ fb.cursor_row ].cells[ x ].reset(); - } + start = 0; } + + clearline( &fb, -1, start, end ); } void Emulator::CSI_ED( void ) { if ( as.params == "1" ) { /* start of screen to active position, inclusive */ - for ( int y = 0; y < fb.cursor_row; y++ ) { - for ( int x = 0; x < fb.width; x++ ) { - fb.rows[ y ].cells[ x ].reset(); - } - } - for ( int x = 0; x <= fb.cursor_col; x++ ) { - if ( x < fb.width ) { - fb.rows[ fb.cursor_row ].cells[ x ].reset(); - } + for ( int y = 0; y < fb.ds.get_cursor_row(); y++ ) { + clearline( &fb, y, 0, fb.ds.get_width() - 1 ); } + clearline( &fb, -1, 0, fb.ds.get_cursor_col() ); } else if ( as.params == "2" ) { /* entire screen */ - for ( int y = 0; y < fb.height; y++ ) { - for ( int x = 0; x < fb.width; x++ ) { - fb.rows[ y ].cells[ x ].reset(); - } + for ( int y = 0; y < fb.ds.get_height(); y++ ) { + clearline( &fb, y, 0, fb.ds.get_width() - 1 ); } } else { /* active position to end of screen, inclusive */ - for ( int x = fb.cursor_col; x < fb.width; x++ ) { - fb.rows[ fb.cursor_row ].cells[ x ].reset(); - } - for ( int y = fb.cursor_row + 1; y < fb.height; y++ ) { - for ( int x = 0; x < fb.width; x++ ) { - fb.rows[ y ].cells[ x ].reset(); - } + clearline( &fb, -1, fb.ds.get_cursor_col(), fb.ds.get_width() - 1 ); + for ( int y = fb.ds.get_cursor_row() + 1; y < fb.ds.get_height(); y++ ) { + clearline( &fb, y, 0, fb.ds.get_width() - 1 ); } } } @@ -57,31 +50,24 @@ void Emulator::CSI_cursormove( void ) switch ( as.dispatch_chars[ 0 ] ) { case 'A': - fb.cursor_row -= num; + fb.ds.move_row( -num, true ); break; case 'B': - fb.cursor_row += num; + fb.ds.move_row( num, true ); break; case 'C': - fb.cursor_col += num; + fb.ds.move_col( num, true ); break; case 'D': - fb.cursor_col -= num; + fb.ds.move_col( -num, true ); break; case 'H': case 'f': int x = as.getparam( 0, 1 ); int y = as.getparam( 1, 1 ); - fb.cursor_row = x - 1; - fb.cursor_col = y - 1; + fb.ds.move_row( x - 1 ); + fb.ds.move_col( y - 1 ); } - - if ( fb.cursor_row < 0 ) fb.cursor_row = 0; - if ( fb.cursor_row >= fb.height ) fb.cursor_row = fb.height - 1; - if ( fb.cursor_col < 0 ) fb.cursor_col = 0; - if ( fb.cursor_col >= fb.width ) fb.cursor_col = fb.width - 1; - - fb.newgrapheme(); } void Emulator::CSI_DA( void ) @@ -91,10 +77,10 @@ void Emulator::CSI_DA( void ) void Emulator::Esc_DECALN( void ) { - for ( int y = 0; y < fb.height; y++ ) { - for ( int x = 0; x < fb.width; x++ ) { - fb.rows[ y ].cells[ x ].reset(); - fb.rows[ y ].cells[ x ].contents.push_back( L'E' ); + for ( int y = 0; y < fb.ds.get_height(); y++ ) { + for ( int x = 0; x < fb.ds.get_width(); x++ ) { + fb.get_cell( y, x )->reset(); + fb.get_cell( y, x )->contents.push_back( L'E' ); } } } diff --git a/terminalframebuffer.cpp b/terminalframebuffer.cpp index 69b1984..82467ed 100644 --- a/terminalframebuffer.cpp +++ b/terminalframebuffer.cpp @@ -8,14 +8,16 @@ Cell::Cell() : overlapping_cell( NULL ), contents(), overlapped_cells(), - fallback( false ) + fallback( false ), + width( 1 ) {} Cell::Cell( const Cell &x ) : overlapping_cell( x.overlapping_cell ), contents( x.contents ), overlapped_cells( x.overlapped_cells ), - fallback( x.fallback ) + fallback( x.fallback ), + width( 1 ) {} Cell & Cell::operator=( const Cell &x ) @@ -36,6 +38,7 @@ void Cell::reset( void ) { contents.clear(); fallback = false; + width = 1; if ( overlapping_cell ) { assert( overlapped_cells.size() == 0 ); @@ -50,11 +53,15 @@ void Cell::reset( void ) } } -Framebuffer::Framebuffer( int s_width, int s_height ) +DrawState::DrawState( int s_width, int s_height ) : width( s_width ), height( s_height ), cursor_col( 0 ), cursor_row( 0 ), combining_char_col( 0 ), combining_char_row( 0 ), - rows( height, Row( width ) ) + next_print_will_wrap( false ) +{} + +Framebuffer::Framebuffer( int s_width, int s_height ) + : rows( s_height, Row( s_width ) ), ds( s_width, s_height ) {} void Framebuffer::scroll( int N ) @@ -63,22 +70,102 @@ void Framebuffer::scroll( int N ) for ( int i = 0; i < N; i++ ) { rows.pop_front(); - rows.push_back( Row( width ) ); - cursor_row--; - combining_char_row--; + rows.push_back( Row( ds.get_width() ) ); + ds.move_row( -1, true ); } } -void Framebuffer::newgrapheme( void ) +void DrawState::new_grapheme( void ) { combining_char_col = cursor_col; combining_char_row = cursor_row; } -void Framebuffer::autoscroll( void ) +void DrawState::snap_cursor_to_border( void ) { - if ( cursor_row >= height ) { /* scroll */ - scroll( cursor_row - height + 1 ); + if ( cursor_row < 0 ) cursor_row = 0; + if ( cursor_row >= height ) cursor_row = height - 1; + if ( cursor_col < 0 ) cursor_col = 0; + if ( cursor_col >= width ) cursor_col = width - 1; +} + +void DrawState::move_row( int N, bool relative ) +{ + if ( relative ) { + cursor_row += N; + } else { + cursor_row = N; + } + + snap_cursor_to_border(); + new_grapheme(); + next_print_will_wrap = false; +} + +void DrawState::move_col( int N, bool relative, bool implicit ) +{ + if ( implicit ) { + new_grapheme(); + } + + if ( relative ) { + cursor_col += N; + } else { + cursor_col = N; + } + + if ( implicit && (cursor_col >= width) ) { + next_print_will_wrap = true; + } + + snap_cursor_to_border(); + if ( !implicit ) { + new_grapheme(); + next_print_will_wrap = false; } } +void Framebuffer::move_rows_autoscroll( int rows ) +{ + if ( ds.get_cursor_row() + rows >= ds.get_height() ) { + scroll( ds.get_height() - ds.get_cursor_row() - rows + 1 ); + } + + ds.move_row( rows, true ); +} + +Cell *Framebuffer::get_cell( void ) +{ + if ( (ds.get_width() == 0) || (ds.get_height() == 0) ) { + return NULL; + } + + return &rows[ ds.get_cursor_row() ].cells[ ds.get_cursor_col() ]; +} + +Cell *Framebuffer::get_cell( int row, int col ) +{ + if ( row == -1 ) row = ds.get_cursor_row(); + if ( col == -1 ) col = ds.get_cursor_col(); + + return &rows[ row ].cells[ col ]; +} + +Cell *Framebuffer::get_combining_cell( void ) +{ + return &rows[ ds.get_combining_char_row() ].cells[ ds.get_combining_char_col() ]; +} + +void Framebuffer::claim_overlap( int row, int col ) +{ + Cell *the_cell = &rows[ row ].cells[ col ]; + + for ( int i = col + 1; i < col + the_cell->width; i++ ) { + if ( i < ds.get_width() ) { + Cell *next_cell = get_cell( row, i ); + next_cell->reset(); + the_cell->overlapped_cells.push_back( next_cell ); + next_cell->overlapping_cell = the_cell; + } + } +} diff --git a/terminalframebuffer.hpp b/terminalframebuffer.hpp index d79679b..f255e64 100644 --- a/terminalframebuffer.hpp +++ b/terminalframebuffer.hpp @@ -13,6 +13,7 @@ namespace Terminal { std::vector contents; std::vector overlapped_cells; char fallback; /* first character is combining character */ + int width; Cell(); @@ -29,19 +30,51 @@ namespace Terminal { Row( size_t s_width ); }; - class Framebuffer { - public: + class DrawState { + private: int width, height; + + void new_grapheme( void ); + void snap_cursor_to_border( void ); + int cursor_col, cursor_row; int combining_char_col, combining_char_row; + public: + bool next_print_will_wrap; + + /* bold, etc. */ + + void move_row( int N, bool relative = false ); + void move_col( int N, bool relative = false, bool implicit = false ); + + int get_cursor_col( void ) { return cursor_col; } + int get_cursor_row( void ) { return cursor_row; } + int get_combining_char_col( void ) { return combining_char_col; } + int get_combining_char_row( void ) { return combining_char_row; } + int get_width( void ) { return width; } + int get_height( void ) { return height; } + + DrawState( int s_width, int s_height ); + }; + + class Framebuffer { + private: std::deque rows; void scroll( int N ); - void autoscroll( void ); - void newgrapheme( void ); + public: Framebuffer( int s_width, int s_height ); + DrawState ds; + + void move_rows_autoscroll( int rows ); + + Cell *get_cell( void ); + Cell *get_cell( int row, int col ); + Cell *get_combining_cell( void ); + + void claim_overlap( int row, int col ); }; }