From c090d257f27f40a47f8805ecd7838f09e5e02a8f Mon Sep 17 00:00:00 2001 From: John Hood Date: Sun, 15 Dec 2013 18:39:23 -0500 Subject: [PATCH] Rework Terminal::Display to improve performance and treat passed Framebuffers as const. * Refactor put_cell() and parts of new_frame(), and associated state, into put_row(). * Optimize display and line wrap handling code/output. * Make last_frame a const ref, to eliminate a costly copy of the framebuffer on every screen refresh. * In new_frame()'s scroll optimization, don't copy rows, use pointers instead. * Don't check entire frame buffer for scrolling if first line hasn't scrolled. * Add a generation counter on Row objects to allow quicker/better identification of scroll regions * Use at() for bounds-checking on framebuffers, because they can be resized. * Copy and resize scroll buffer on window resize. --- src/terminal/terminaldisplay.cc | 403 ++++++++++++++++------------ src/terminal/terminaldisplay.h | 11 +- src/terminal/terminalframebuffer.cc | 40 ++- src/terminal/terminalframebuffer.h | 31 ++- 4 files changed, 285 insertions(+), 200 deletions(-) diff --git a/src/terminal/terminaldisplay.cc b/src/terminal/terminaldisplay.cc index 736526f..28710ec 100644 --- a/src/terminal/terminaldisplay.cc +++ b/src/terminal/terminaldisplay.cc @@ -123,9 +123,7 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const || (f.ds.get_width() != frame.last_frame.ds.get_width()) || (f.ds.get_height() != frame.last_frame.ds.get_height()) ) { /* reset scrolling region */ - snprintf( tmp, 64, "\033[%d;%dr", - 1, f.ds.get_height() ); - frame.append( tmp ); + frame.append( "\033[r" ); /* clear screen */ frame.append( "\033[0m\033[H\033[2J" ); @@ -138,15 +136,47 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const frame.current_rendition = frame.last_frame.ds.get_renditions(); } - /* shortcut -- has display moved up by a certain number of lines? */ - frame.y = 0; + /* is cursor visibility initialized? */ + if ( !initialized ) { + frame.cursor_visible = false; + frame.append( "\033[?25l" ); + } + int frame_y = 0; + Row blankrow( 0, 0 ); + Framebuffer::rows_p_type rows(frame.last_frame.get_p_rows()); + /* Extend rows if we've gotten a resize and new is wider than old */ + if ( frame.last_frame.ds.get_width() < f.ds.get_width() ) { + for ( Framebuffer::rows_p_type::iterator p = rows.begin(); p != rows.end(); p++ ) { + Row *bigger_row = new Row( **p ); + bigger_row->cells.resize( f.ds.get_width(), Cell( f.ds.get_background_rendition() ) ); + *p = bigger_row; + } + } + /* Add rows if we've gotten a resize and new is taller than old */ + if ( static_cast( rows.size() ) < f.ds.get_height() ) { + size_t orig_size = rows.size(); + // get a proper blank row + blankrow = Row( f.ds.get_width(), 0 ); + rows.resize( f.ds.get_height() ); + for ( Framebuffer::rows_p_type::iterator p = rows.begin() + orig_size; p != rows.end(); p++ ) { + *p = new Row( blankrow ); + } + } + + /* shortcut -- has display moved up by a certain number of lines? */ if ( initialized ) { int lines_scrolled = 0; int scroll_height = 0; - for ( int row = 0; row < frame.last_frame.ds.get_height(); row++ ) { - if ( *(f.get_row( 0 )) == *(frame.last_frame.get_row( row )) ) { + for ( int row = 0; row < f.ds.get_height(); row++ ) { + const Row *new_row = f.get_row( 0 ); + const Row *old_row = rows.at( row ); + if ( new_row == old_row || *new_row == *old_row ) { + /* if row 0, we're looking at ourselves and probably didn't scroll */ + if ( row == 0 ) { + break; + } /* found a scroll */ lines_scrolled = row; scroll_height = 1; @@ -155,8 +185,8 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const for ( int region_height = 1; lines_scrolled + region_height < f.ds.get_height(); region_height++ ) { - if ( *(f.get_row( region_height )) - == *(frame.last_frame.get_row( lines_scrolled + region_height )) ) { + if ( *f.get_row( region_height ) + == *rows.at( lines_scrolled + region_height ) ) { scroll_height = region_height + 1; } else { break; @@ -168,112 +198,73 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const } if ( scroll_height ) { - frame.y = scroll_height; + frame_y = scroll_height; if ( lines_scrolled ) { - if ( !(frame.current_rendition == initial_rendition()) ) { - frame.append( "\033[0m" ); - frame.current_rendition = initial_rendition(); - } + frame.update_rendition( initial_rendition(), true ); int top_margin = 0; int bottom_margin = top_margin + lines_scrolled + scroll_height - 1; assert( bottom_margin < f.ds.get_height() ); - /* set scrolling region */ - snprintf( tmp, 64, "\033[%d;%dr", - top_margin + 1, bottom_margin + 1); - frame.append( tmp ); + /* Common case: if we're already on the bottom line and we're scrolling the whole + * screen, just do a CR and LFs. + */ + if ( (scroll_height + lines_scrolled == f.ds.get_height() ) && frame.cursor_y + 1 == f.ds.get_height() ) { + frame.append( '\r' ); + frame.append( lines_scrolled, '\n' ); + frame.cursor_x = 0; + } else { + /* set scrolling region */ + snprintf( tmp, 64, "\033[%d;%dr", + top_margin + 1, bottom_margin + 1); + frame.append( tmp ); - /* go to bottom of scrolling region */ - frame.append_silent_move( bottom_margin, 0 ); + /* go to bottom of scrolling region */ + frame.cursor_x = frame.cursor_y = -1; + frame.append_silent_move( bottom_margin, 0 ); - /* scroll */ - for ( int i = 0; i < lines_scrolled; i++ ) { - frame.append( '\n' ); + /* scroll */ + frame.append( lines_scrolled, '\n' ); + + /* reset scrolling region */ + frame.append( "\033[r" ); + /* invalidate cursor position after unsetting scrolling region */ + frame.cursor_x = frame.cursor_y = -1; } - /* do the move in memory */ + /* Create a full-width blank row to represent newly-scrolled area */ + blankrow = Row( f.ds.get_width(), 0 ); + + /* do the move in our local index */ for ( int i = top_margin; i <= bottom_margin; i++ ) { if ( i + lines_scrolled <= bottom_margin ) { - *(frame.last_frame.get_mutable_row( i )) = *(frame.last_frame.get_row( i + lines_scrolled )); + rows.at( i ) = rows.at( i + lines_scrolled ); } else { - frame.last_frame.get_mutable_row( i )->reset( 0 ); + rows.at( i ) = &blankrow; } } - - /* reset scrolling region */ - snprintf( tmp, 64, "\033[%d;%dr", - 1, f.ds.get_height() ); - frame.append( tmp ); - - /* invalidate cursor position after unsetting scrolling region */ - frame.cursor_x = frame.cursor_y = -1; } } } - /* iterate for every cell */ - for ( ; frame.y < f.ds.get_height(); frame.y++ ) { - int last_x = 0; - for ( frame.x = 0; - frame.x < f.ds.get_width(); /* let put_cell() handle advance */ ) { - last_x = frame.x; - put_cell( initialized, frame, f ); - } - - /* To hint that a word-select should group the end of one line - with the beginning of the next, we let the real cursor - actually wrap around in cases where it wrapped around for us. */ - - if ( (frame.y < f.ds.get_height() - 1) - && f.get_row( frame.y )->get_wrap() ) { - frame.x = last_x; - - while ( frame.x < f.ds.get_width() ) { - frame.force_next_put = true; - put_cell( initialized, frame, f ); - } - - /* next write will wrap */ - frame.cursor_x = 0; - frame.cursor_y++; - frame.force_next_put = true; - } - - /* Turn off wrap */ - if ( (frame.y < f.ds.get_height() - 1) - && (!f.get_row( frame.y )->get_wrap()) - && (!initialized || frame.last_frame.get_row( frame.y )->get_wrap()) ) { - frame.x = last_x; - if ( initialized ) { - frame.last_frame.reset_cell( frame.last_frame.get_mutable_cell( frame.y, frame.x ) ); - } - - snprintf( tmp, 64, "\033[%d;%dH\033[K", frame.y + 1, frame.x + 1 ); - frame.append( tmp ); - frame.cursor_x = frame.x; - - frame.force_next_put = true; - put_cell( initialized, frame, f ); - } + /* Now update the display, row by row */ + bool wrap = false; + for ( ; frame_y < f.ds.get_height(); frame_y++ ) { + wrap = put_row( initialized, frame, f, frame_y, *rows.at( frame_y ), wrap ); } /* has cursor location changed? */ if ( (!initialized) || (f.ds.get_cursor_row() != frame.cursor_y) || (f.ds.get_cursor_col() != frame.cursor_x) ) { - snprintf( tmp, 64, "\033[%d;%dH", f.ds.get_cursor_row() + 1, - f.ds.get_cursor_col() + 1 ); - frame.append( tmp ); - frame.cursor_x = f.ds.get_cursor_col(); - frame.cursor_y = f.ds.get_cursor_row(); + frame.append_move( f.ds.get_cursor_row(), f.ds.get_cursor_col() ); } /* has cursor visibility changed? */ if ( (!initialized) - || (f.ds.cursor_visible != frame.last_frame.ds.cursor_visible) ) { + || (f.ds.cursor_visible != frame.cursor_visible) ) { if ( f.ds.cursor_visible ) { frame.append( "\033[?25h" ); } else { @@ -282,11 +273,7 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const } /* have renditions changed? */ - if ( (!initialized) - || !(f.ds.get_renditions() == frame.current_rendition) ) { - frame.append( f.ds.get_renditions().sgr() ); - frame.current_rendition = f.ds.get_renditions(); - } + frame.update_rendition( f.ds.get_renditions(), !initialized ); /* has bracketed paste mode changed? */ if ( (!initialized) @@ -338,115 +325,201 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const return frame.str; } -void Display::put_cell( bool initialized, FrameState &frame, const Framebuffer &f ) const +bool Display::put_row( bool initialized, FrameState &frame, const Framebuffer &f, int frame_y, const Row &old_row, bool wrap ) const { char tmp[ 64 ]; + int frame_x = 0; - const Cell *cell = f.get_cell( frame.y, frame.x ); + const Row &row = *f.get_row( frame_y ); + const Row::cells_type &cells = row.cells; + const Row::cells_type &old_cells = old_row.cells; - if ( !frame.force_next_put ) { + /* If we're forced to write the first column because of wrap, go ahead and do so. */ + if ( wrap ) { + const Cell &cell = cells.at( 0 ); + frame.update_rendition( cell.renditions ); + frame.append_cell( cell ); + frame_x += cell.width; + frame.cursor_x += cell.width; + } + + /* If rows are the same object, we don't need to do anything at all. */ + if (initialized && &row == &old_row ) { + return false; + } + + int clear_count = 0; + bool wrote_last_cell = false; + Renditions blank_renditions = initial_rendition(); + + /* iterate for every cell */ + while ( frame_x < f.ds.get_width() ) { + + const Cell &cell = cells.at( frame_x ); + + /* Does cell need to be drawn? Skip all this. */ if ( initialized - && ( *cell == *(frame.last_frame.get_cell( frame.y, frame.x )) ) ) { - frame.x += cell->width; - return; + && !clear_count + && ( cell == old_cells.at( frame_x ) ) ) { + frame_x += cell.width; + continue; } - } - if ( (frame.x != frame.cursor_x) || (frame.y != frame.cursor_y) ) { - frame.append_silent_move( frame.y, frame.x ); - } - - if ( !(frame.current_rendition == cell->renditions) ) { - /* print renditions */ - frame.append( cell->renditions.sgr() ); - frame.current_rendition = cell->renditions; - } - - if ( cell->contents.empty() ) { - /* see how far we can stretch a clear */ - int clear_count = 0; - for ( int col = frame.x; col < f.ds.get_width(); col++ ) { - const Cell *other_cell = f.get_cell( frame.y, col ); - if ( (cell->renditions == other_cell->renditions) - && (other_cell->contents.empty()) ) { + /* Slurp up all the empty cells */ + if ( cell.contents.empty() ) { + if ( !clear_count ) { + blank_renditions = cell.renditions; + } + if ( cell.renditions == blank_renditions ) { + /* Remember run of blank cells */ clear_count++; + frame_x++; + continue; + } + } + + /* Clear or write cells within the row (not to end). */ + if ( clear_count ) { + /* Move to the right position. */ + frame.append_silent_move( frame_y, frame_x - clear_count ); + frame.update_rendition( blank_renditions ); + bool can_use_erase = has_bce || ( frame.current_rendition == initial_rendition() ); + if ( can_use_erase && has_ech ) { + snprintf( tmp, 64, "\033[%dX", clear_count ); + frame.append( tmp ); } else { - break; + frame.append( clear_count, ' ' ); + frame.cursor_x = frame_x; + } + clear_count = 0; + // If the current character is *another* empty cell in a different rendition, + // we restart counting and continue here + if ( cell.contents.empty() ) { + blank_renditions = cell.renditions; + clear_count = 1; + frame_x++; + continue; } } - assert( frame.x + clear_count <= f.ds.get_width() ); - bool can_use_erase = has_bce || (cell->renditions == initial_rendition()); - - if ( frame.force_next_put ) { - frame.append( ' ' ); - frame.cursor_x++; - frame.x++; - frame.force_next_put = false; - return; + /* Now draw a character cell. */ + /* Move to the right position. */ + frame.append_silent_move( frame_y, frame_x ); + frame.update_rendition( cell.renditions ); + frame.append_cell( cell ); + frame_x += cell.width; + frame.cursor_x += cell.width; + if ( frame_x >= f.ds.get_width() ) { + wrote_last_cell = true; } + } - /* can we go to the end of the line? */ - if ( (frame.x + clear_count == f.ds.get_width()) - && can_use_erase ) { + /* End of line. */ + + /* Clear or write empty cells at EOL. */ + if ( clear_count ) { + /* Move to the right position. */ + frame.append_silent_move( frame_y, frame_x - clear_count ); + frame.update_rendition( blank_renditions ); + + bool can_use_erase = !row.get_wrap() && ( has_bce || ( frame.current_rendition == initial_rendition() ) ); + if ( can_use_erase ) { frame.append( "\033[K" ); - frame.x += clear_count; } else { - if ( has_ech && can_use_erase ) { - if ( clear_count == 1 ) { - frame.append( "\033[X" ); - } else { - snprintf( tmp, 64, "\033[%dX", clear_count ); - frame.append( tmp ); - } - frame.x += clear_count; - } else { /* no ECH, so just print a space */ - /* unlike erases, this will use background color irrespective of BCE */ - frame.append( ' ' ); - frame.cursor_x++; - frame.x++; - } + frame.append( clear_count, ' ' ); + frame.cursor_x = frame_x; + wrote_last_cell = true; } + } + if ( wrote_last_cell + && (frame_y < f.ds.get_height() - 1) ) { + /* To hint that a word-select should group the end of one line + with the beginning of the next, we let the real cursor + actually wrap around in cases where it wrapped around for us. */ + if ( row.get_wrap() ) { + /* Update our cursor, and ask for wrap on the next row. */ + frame.cursor_x = 0; + frame.cursor_y++; + return true; + } else { + /* Resort to CR/LF and update our cursor. */ + frame.append( "\r\n" ); + frame.cursor_x = 0; + frame.cursor_y++; + } + } + return false; +} + +FrameState::FrameState( const Framebuffer &s_last ) + : str(), cursor_x(0), cursor_y(0), current_rendition( 0 ), + cursor_visible( s_last.ds.cursor_visible ), + last_frame( s_last ) +{ + /* Preallocate for better performance. Make a guess-- doesn't matter for correctness */ + str.reserve( last_frame.ds.get_width() * last_frame.ds.get_height() * 4 ); +} + +/* Write a character cell */ +void FrameState::append_cell(const Cell & cell) +{ + if ( cell.contents.empty() ) { + append( ' ' ); return; } - /* cells that begin with combining character get combiner attached to no-break space */ - if ( cell->fallback ) { - frame.append( "\xC2\xA0" ); + if ( cell.fallback ) { + append( "\xC2\xA0" ); } - - frame.append( cell->contents ); - - frame.x += cell->width; - frame.cursor_x += cell->width; - - frame.force_next_put = false; + append( cell.contents ); } + void FrameState::append_silent_move( int y, int x ) { - char tmp[ 64 ]; - + if ( cursor_x == x && cursor_y == y ) return; /* turn off cursor if necessary before moving cursor */ - if ( last_frame.ds.cursor_visible ) { + if ( cursor_visible ) { append( "\033[?25l" ); - last_frame.ds.cursor_visible = false; + cursor_visible = false; } + append_move( y, x ); +} - snprintf( tmp, 64, "\033[%d;%dH", y + 1, x + 1 ); - append( tmp ); +void FrameState::append_move( int y, int x ) +{ + do { + // If cursor pos is unknown, of course we can't optimize + if ( cursor_x != -1 && cursor_y != -1 ) { + // Can we use CR and/or LF? They're cheap and easier to trace. + if ( x == 0 && y - cursor_y >= 0 && y - cursor_y < 5 ) { + if ( cursor_x != 0 ) { + append( '\r' ); + } + append( y - cursor_y, '\n' ); + continue; + } + // Backspaces are good too. + if ( y == cursor_y && x - cursor_x < 0 && x - cursor_x > -5 ) { + append( cursor_x - x, '\b' ); + continue; + } + // More optimizations are possible. + } + char tmp[ 64 ]; + snprintf( tmp, 64, "\033[%d;%dH", y + 1, x + 1 ); + append( tmp ); + } while( 0 ); cursor_x = x; cursor_y = y; } -FrameState::FrameState( const Framebuffer &s_last ) - : x(0), y(0), - force_next_put( false ), - str(), cursor_x(0), cursor_y(0), current_rendition( 0 ), - last_frame( s_last ) -{ - /* just a guess-- doesn't matter for correctness */ - str.reserve( last_frame.ds.get_width() * last_frame.ds.get_height() * 4 ); +void FrameState::update_rendition(const Renditions &r, bool force) { + if ( force || !(current_rendition == r) ) { + /* print renditions */ + append_string( r.sgr() ); + current_rendition = r; + } } diff --git a/src/terminal/terminaldisplay.h b/src/terminal/terminaldisplay.h index aab7f4a..9cc7b5e 100644 --- a/src/terminal/terminaldisplay.h +++ b/src/terminal/terminaldisplay.h @@ -39,24 +39,27 @@ namespace Terminal { /* variables used within a new_frame */ class FrameState { public: - int x, y; - bool force_next_put; std::string str; int cursor_x, cursor_y; Renditions current_rendition; + bool cursor_visible; - Framebuffer last_frame; + const Framebuffer &last_frame; FrameState( const Framebuffer &s_last ); void append( const char c ) { str.append( 1, c ); } + void append( const size_t s, const char c ) { str.append( s, c ); } void append( const wchar_t wc ) { Cell::append_to_str( str, wc ); } void append( const char * s ) { str.append( s ); } void append( const Cell::content_type &contents ) { str.append( contents.begin(), contents.end() ); } void append_string( const std::string &append ) { str.append(append); } + void append_cell(const Cell & cell); void append_silent_move( int y, int x ); + void append_move( int y, int x ); + void update_rendition( const Renditions &r, bool force = false ); }; class Display { @@ -76,7 +79,7 @@ namespace Terminal { const char *smcup, *rmcup; /* enter and exit alternate screen mode */ - void put_cell( bool initialized, FrameState &frame, const Framebuffer &f ) const; + bool put_row( bool initialized, FrameState &frame, const Framebuffer &f, int frame_y, const Row &old_row, bool wrap ) const; public: void downgrade( Framebuffer &f ) const { if ( posterize_colors ) { f.posterize(); } } diff --git a/src/terminal/terminalframebuffer.cc b/src/terminal/terminalframebuffer.cc index 84031be..88a54bf 100644 --- a/src/terminal/terminalframebuffer.cc +++ b/src/terminal/terminalframebuffer.cc @@ -42,20 +42,16 @@ Cell::Cell( color_type background_color ) : contents(), renditions( background_color ), width( 1 ), - fallback( false ), - wrap( false ) + fallback( false ) {} -#if 0 Cell::Cell() /* default constructor required by C++11 STL */ : contents(), renditions( 0 ), width( 1 ), - fallback( false ), - wrap( false ) + fallback( false ) { assert( false ); } -#endif void Cell::reset( color_type background_color ) { @@ -63,7 +59,6 @@ void Cell::reset( color_type background_color ) renditions = Renditions( background_color ); width = 1; fallback = false; - wrap = false; } void DrawState::reinitialize_tabs( unsigned int start ) @@ -184,7 +179,7 @@ Cell *Framebuffer::get_combining_cell( void ) return NULL; } /* can happen if a resize came in between */ - return &rows[ ds.get_combining_char_row() ].cells[ ds.get_combining_char_col() ]; + return get_mutable_cell( ds.get_combining_char_row(), ds.get_combining_char_col() ); } void DrawState::set_tab( void ) @@ -324,23 +319,27 @@ void Framebuffer::delete_line( int row, int count ) start = rows.begin() + ds.get_scrolling_region_bottom_row() + 1 - count; rows.insert( start, count, Row( 0, 0 ) ); // then replace with real new rows - start = rows.begin() + ds.get_scrolling_region_bottom_row() + 1 - count; + start = rows.begin() + ds.get_scrolling_region_bottom_row() + 1 - count; for (rows_type::iterator i = start; i < start + count; i++) { *i = newrow(); } } Row::Row( size_t s_width, color_type background_color ) - : cells( s_width, Cell( background_color ) ) + : cells( s_width, Cell( background_color ) ), wrap( false ), gen( get_gen() ) {} -#if 0 Row::Row() /* default constructor required by C++11 STL */ - : cells( 1, Cell() ) + : cells( 1, Cell() ), wrap( false ), gen( get_gen() ) { assert( false ); } -#endif + +uint64_t Row::get_gen() const +{ + static uint64_t gen_counter = 0; + return gen_counter++; +} void Row::insert_cell( int col, color_type background_color ) { @@ -356,12 +355,12 @@ void Row::delete_cell( int col, color_type background_color ) void Framebuffer::insert_cell( int row, int col ) { - rows[ row ].insert_cell( col, ds.get_background_rendition() ); + rows.at( row ).insert_cell( col, ds.get_background_rendition() ); } void Framebuffer::delete_cell( int row, int col ) { - rows[ row ].delete_cell( col, ds.get_background_rendition() ); + rows.at( row ).delete_cell( col, ds.get_background_rendition() ); } void Framebuffer::reset( void ) @@ -402,6 +401,8 @@ void Framebuffer::resize( int s_width, int s_height ) assert( s_width > 0 ); assert( s_height > 0 ); + ds.resize( s_width, s_height ); + rows.resize( s_height, newrow() ); for ( rows_type::iterator i = rows.begin(); @@ -410,8 +411,6 @@ void Framebuffer::resize( int s_width, int s_height ) i->set_wrap( false ); i->cells.resize( s_width, Cell( ds.get_background_rendition() ) ); } - - ds.resize( s_width, s_height ); } void DrawState::resize( int s_width, int s_height ) @@ -604,6 +603,7 @@ void Renditions::posterize( void ) void Row::reset( color_type background_color ) { + gen = get_gen(); for ( cells_type::iterator i = cells.begin(); i != cells.end(); i++ ) { @@ -660,11 +660,5 @@ bool Cell::compare( const Cell &other ) const fprintf( stderr, "renditions differ\n" ); } - if ( wrap != other.wrap ) { - ret = true; - fprintf( stderr, "wrap: %d vs. %d\n", - wrap, other.wrap ); - } - return ret; } diff --git a/src/terminal/terminalframebuffer.h b/src/terminal/terminalframebuffer.h index 5c6dde7..15f6033 100644 --- a/src/terminal/terminalframebuffer.h +++ b/src/terminal/terminalframebuffer.h @@ -89,7 +89,6 @@ namespace Terminal { Renditions renditions; uint8_t width; bool fallback; /* first character is combining character */ - bool wrap; /* if last cell, wrap to next line */ Cell( color_type background_color ); Cell(); /* default constructor required by C++11 STL */ @@ -101,8 +100,7 @@ namespace Terminal { return ( (contents == x.contents) && (fallback == x.fallback) && (width == x.width) - && (renditions == x.renditions) - && (wrap == x.wrap) ); + && (renditions == x.renditions) ); } bool operator!=( const Cell &x ) const { return !operator==( x ); } @@ -149,6 +147,11 @@ namespace Terminal { public: typedef std::vector cells_type; cells_type cells; + bool wrap; /* if last cell, wrap to next line */ + // gen is a generation counter. It can be used to quickly rule + // out the possibility of two rows being identical; this is useful + // in scrolling. + uint64_t gen; Row( size_t s_width, color_type background_color ); Row(); /* default constructor required by C++11 STL */ @@ -160,11 +163,13 @@ namespace Terminal { bool operator==( const Row &x ) const { - return ( cells == x.cells ); + return ( gen == x.gen && cells == x.cells && wrap == x.wrap ); } - bool get_wrap( void ) const { return cells.back().wrap; } - void set_wrap( bool w ) { cells.back().wrap = w; } + bool get_wrap( void ) const { return wrap; } + void set_wrap( bool w ) { wrap = w; } + + uint64_t get_gen() const; }; class SavedCursor { @@ -285,6 +290,7 @@ namespace Terminal { class Framebuffer { public: typedef std::vector title_type; + typedef std::vector rows_p_type; private: typedef std::vector rows_type; @@ -303,11 +309,20 @@ namespace Terminal { void scroll( int N ); void move_rows_autoscroll( int rows ); + rows_p_type get_p_rows() const + { + rows_p_type retval; + for ( size_t i = 0; i < rows.size(); i++ ) { + retval.push_back( &rows.at(i) ); + } + return retval; + } + const Row *get_row( int row ) const { if ( row == -1 ) row = ds.get_cursor_row(); - return &rows[ row ]; + return &rows.at( row ); } inline const Cell *get_cell( int row = -1, int col = -1 ) const @@ -315,7 +330,7 @@ namespace Terminal { if ( row == -1 ) row = ds.get_cursor_row(); if ( col == -1 ) col = ds.get_cursor_col(); - return &rows[ row ].cells[ col ]; + return &rows.at( row ).cells.at( col ); } Row *get_mutable_row( int row )