Abstract cursor movement

This commit is contained in:
Keith Winstein
2011-01-31 02:14:43 -05:00
parent 5e0cc8c2b8
commit ec328ecdee
4 changed files with 233 additions and 138 deletions
+65 -76
View File
@@ -45,24 +45,18 @@ void Emulator::execute( Parser::Execute *act )
switch ( act->ch ) { switch ( act->ch ) {
case 0x0a: /* LF */ case 0x0a: /* LF */
fb.cursor_row++; fb.move_rows_autoscroll( 1 );
fb.autoscroll();
fb.newgrapheme();
act->handled = true; act->handled = true;
break; break;
case 0x0d: /* CR */ case 0x0d: /* CR */
fb.cursor_col = 0; fb.ds.move_col( 0 );
fb.newgrapheme();
act->handled = true; act->handled = true;
break; break;
case 0x08: /* BS */ case 0x08: /* BS */
if ( fb.cursor_col > 0 ) { fb.ds.move_col( -1, true );
fb.cursor_col--; act->handled = true;
fb.newgrapheme(); /* this is not xterm's behavior */
act->handled = true;
}
break; break;
} }
} }
@@ -71,56 +65,47 @@ void Emulator::print( Parser::Print *act )
{ {
assert( act->char_present ); 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; return;
} }
assert( fb.cursor_row < fb.height ); /* must be on screen */ Cell *combining_cell = fb.get_combining_cell();
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;
switch ( chwidth ) { switch ( chwidth ) {
case 1: /* normal character */ case 1: /* normal character */
case 2: /* wide character */ case 2: /* wide character */
if ( fb.cursor_col >= fb.width ) { /* wrap */ if ( fb.ds.next_print_will_wrap ) {
fb.cursor_col = 0; fb.ds.move_col( 0 );
fb.cursor_row++; fb.move_rows_autoscroll( 1 );
} }
fb.autoscroll(); this_cell = fb.get_cell();
fb.newgrapheme();
this_cell = &fb.rows[ fb.cursor_row ].cells[ fb.cursor_col ];
this_cell->reset(); this_cell->reset();
this_cell->contents.push_back( act->ch ); this_cell->contents.push_back( act->ch );
this_cell->width = chwidth;
if ( (fb.cursor_col < fb.width - 1) && (chwidth == 2) ) { fb.claim_overlap( fb.ds.get_cursor_row(), fb.ds.get_cursor_col() );
Cell *next_cell = &fb.rows[ fb.cursor_row ].cells[ fb.cursor_col + 1 ];
this_cell->overlapped_cells.push_back( next_cell ); fb.ds.move_col( chwidth, true, true );
next_cell->overlapping_cell = this_cell;
}
fb.cursor_col += chwidth;
act->handled = true; act->handled = true;
break; break;
case 0: /* combining character */ 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 */ /* cell starts with combining character */
fb.rows[ fb.combining_char_row ].cells[ fb.combining_char_col ].fallback = true; assert( this_cell == combining_cell );
assert( fb.cursor_col == fb.combining_char_col ); assert( combining_cell->width == 1 );
assert( fb.cursor_row == fb.combining_char_row ); combining_cell->fallback = true;
assert( fb.cursor_col < fb.width ); fb.ds.move_col( 1, true, true );
fb.cursor_col++;
/* a combining character should never be able to wrap us */
} }
if ( fb.rows[ fb.combining_char_row ].cells[ fb.combining_char_col ].contents.size() < 16 ) { /* seems like a reasonable limit on combining character */ if ( combining_cell->contents.size() < 16 ) { /* seems like a reasonable limit on combining characters */
fb.rows[ fb.combining_char_row ].cells[ fb.combining_char_col ].contents.push_back( act->ch ); combining_cell->contents.push_back( act->ch );
} }
act->handled = true; act->handled = true;
break; 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<wchar_t>::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 ) void Emulator::CSI_dispatch( Parser::CSI_Dispatch *act )
{ {
/* add final char to dispatch key */ /* add final char to dispatch key */
@@ -212,3 +161,43 @@ void Emulator::Esc_dispatch( Parser::Esc_Dispatch *act )
act->handled = true; 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<wchar_t>::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() );
}
+33 -47
View File
@@ -4,49 +4,42 @@
using namespace Terminal; 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 ) 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 */ if ( as.params == "1" ) { /* start of screen to active position, inclusive */
for ( int x = 0; x <= fb.cursor_col; x++ ) { start = 0;
if ( x < fb.width ) { end = fb.ds.get_cursor_col();
fb.rows[ fb.cursor_row ].cells[ x ].reset();
}
}
} else if ( as.params == "2" ) { /* all of line */ } else if ( as.params == "2" ) { /* all of line */
fb.rows[ fb.cursor_row ] = Row( fb.width ); start = 0;
} 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();
}
} }
clearline( &fb, -1, start, end );
} }
void Emulator::CSI_ED( void ) { void Emulator::CSI_ED( void ) {
if ( as.params == "1" ) { /* start of screen to active position, inclusive */ if ( as.params == "1" ) { /* start of screen to active position, inclusive */
for ( int y = 0; y < fb.cursor_row; y++ ) { for ( int y = 0; y < fb.ds.get_cursor_row(); y++ ) {
for ( int x = 0; x < fb.width; x++ ) { clearline( &fb, y, 0, fb.ds.get_width() - 1 );
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();
}
} }
clearline( &fb, -1, 0, fb.ds.get_cursor_col() );
} else if ( as.params == "2" ) { /* entire screen */ } else if ( as.params == "2" ) { /* entire screen */
for ( int y = 0; y < fb.height; y++ ) { for ( int y = 0; y < fb.ds.get_height(); y++ ) {
for ( int x = 0; x < fb.width; x++ ) { clearline( &fb, y, 0, fb.ds.get_width() - 1 );
fb.rows[ y ].cells[ x ].reset();
}
} }
} else { /* active position to end of screen, inclusive */ } else { /* active position to end of screen, inclusive */
for ( int x = fb.cursor_col; x < fb.width; x++ ) { clearline( &fb, -1, fb.ds.get_cursor_col(), fb.ds.get_width() - 1 );
fb.rows[ fb.cursor_row ].cells[ x ].reset(); for ( int y = fb.ds.get_cursor_row() + 1; y < fb.ds.get_height(); y++ ) {
} clearline( &fb, y, 0, fb.ds.get_width() - 1 );
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();
}
} }
} }
} }
@@ -57,31 +50,24 @@ void Emulator::CSI_cursormove( void )
switch ( as.dispatch_chars[ 0 ] ) { switch ( as.dispatch_chars[ 0 ] ) {
case 'A': case 'A':
fb.cursor_row -= num; fb.ds.move_row( -num, true );
break; break;
case 'B': case 'B':
fb.cursor_row += num; fb.ds.move_row( num, true );
break; break;
case 'C': case 'C':
fb.cursor_col += num; fb.ds.move_col( num, true );
break; break;
case 'D': case 'D':
fb.cursor_col -= num; fb.ds.move_col( -num, true );
break; break;
case 'H': case 'H':
case 'f': case 'f':
int x = as.getparam( 0, 1 ); int x = as.getparam( 0, 1 );
int y = as.getparam( 1, 1 ); int y = as.getparam( 1, 1 );
fb.cursor_row = x - 1; fb.ds.move_row( x - 1 );
fb.cursor_col = y - 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 ) void Emulator::CSI_DA( void )
@@ -91,10 +77,10 @@ void Emulator::CSI_DA( void )
void Emulator::Esc_DECALN( void ) void Emulator::Esc_DECALN( void )
{ {
for ( int y = 0; y < fb.height; y++ ) { for ( int y = 0; y < fb.ds.get_height(); y++ ) {
for ( int x = 0; x < fb.width; x++ ) { for ( int x = 0; x < fb.ds.get_width(); x++ ) {
fb.rows[ y ].cells[ x ].reset(); fb.get_cell( y, x )->reset();
fb.rows[ y ].cells[ x ].contents.push_back( L'E' ); fb.get_cell( y, x )->contents.push_back( L'E' );
} }
} }
} }
+98 -11
View File
@@ -8,14 +8,16 @@ Cell::Cell()
: overlapping_cell( NULL ), : overlapping_cell( NULL ),
contents(), contents(),
overlapped_cells(), overlapped_cells(),
fallback( false ) fallback( false ),
width( 1 )
{} {}
Cell::Cell( const Cell &x ) Cell::Cell( const Cell &x )
: overlapping_cell( x.overlapping_cell ), : overlapping_cell( x.overlapping_cell ),
contents( x.contents ), contents( x.contents ),
overlapped_cells( x.overlapped_cells ), overlapped_cells( x.overlapped_cells ),
fallback( x.fallback ) fallback( x.fallback ),
width( 1 )
{} {}
Cell & Cell::operator=( const Cell &x ) Cell & Cell::operator=( const Cell &x )
@@ -36,6 +38,7 @@ void Cell::reset( void )
{ {
contents.clear(); contents.clear();
fallback = false; fallback = false;
width = 1;
if ( overlapping_cell ) { if ( overlapping_cell ) {
assert( overlapped_cells.size() == 0 ); 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 ), : width( s_width ), height( s_height ),
cursor_col( 0 ), cursor_row( 0 ), cursor_col( 0 ), cursor_row( 0 ),
combining_char_col( 0 ), combining_char_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 ) void Framebuffer::scroll( int N )
@@ -63,22 +70,102 @@ void Framebuffer::scroll( int N )
for ( int i = 0; i < N; i++ ) { for ( int i = 0; i < N; i++ ) {
rows.pop_front(); rows.pop_front();
rows.push_back( Row( width ) ); rows.push_back( Row( ds.get_width() ) );
cursor_row--; ds.move_row( -1, true );
combining_char_row--;
} }
} }
void Framebuffer::newgrapheme( void ) void DrawState::new_grapheme( void )
{ {
combining_char_col = cursor_col; combining_char_col = cursor_col;
combining_char_row = cursor_row; combining_char_row = cursor_row;
} }
void Framebuffer::autoscroll( void ) void DrawState::snap_cursor_to_border( void )
{ {
if ( cursor_row >= height ) { /* scroll */ if ( cursor_row < 0 ) cursor_row = 0;
scroll( cursor_row - height + 1 ); 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;
}
}
}
+37 -4
View File
@@ -13,6 +13,7 @@ namespace Terminal {
std::vector<wchar_t> contents; std::vector<wchar_t> contents;
std::vector<Cell *> overlapped_cells; std::vector<Cell *> overlapped_cells;
char fallback; /* first character is combining character */ char fallback; /* first character is combining character */
int width;
Cell(); Cell();
@@ -29,19 +30,51 @@ namespace Terminal {
Row( size_t s_width ); Row( size_t s_width );
}; };
class Framebuffer { class DrawState {
public: private:
int width, height; int width, height;
void new_grapheme( void );
void snap_cursor_to_border( void );
int cursor_col, cursor_row; int cursor_col, cursor_row;
int combining_char_col, combining_char_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<Row> rows; std::deque<Row> rows;
void scroll( int N ); void scroll( int N );
void autoscroll( void );
void newgrapheme( void );
public:
Framebuffer( int s_width, int s_height ); 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 );
}; };
} }