LCOV - code coverage report
Current view: top level - src/terminal - terminaldisplay.cc (source / functions) Hit Total Coverage
Test: mosh-1.3.2 Code Coverage Lines: 216 273 79.1 %
Date: 2022-02-06 20:19:53 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :     Mosh: the mobile shell
       3             :     Copyright 2012 Keith Winstein
       4             : 
       5             :     This program is free software: you can redistribute it and/or modify
       6             :     it under the terms of the GNU General Public License as published by
       7             :     the Free Software Foundation, either version 3 of the License, or
       8             :     (at your option) any later version.
       9             : 
      10             :     This program is distributed in the hope that it will be useful,
      11             :     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :     GNU General Public License for more details.
      14             : 
      15             :     You should have received a copy of the GNU General Public License
      16             :     along with this program.  If not, see <http://www.gnu.org/licenses/>.
      17             : 
      18             :     In addition, as a special exception, the copyright holders give
      19             :     permission to link the code of portions of this program with the
      20             :     OpenSSL library under certain conditions as described in each
      21             :     individual source file, and distribute linked combinations including
      22             :     the two.
      23             : 
      24             :     You must obey the GNU General Public License in all respects for all
      25             :     of the code used other than OpenSSL. If you modify file(s) with this
      26             :     exception, you may extend this exception to your version of the
      27             :     file(s), but you are not obligated to do so. If you do not wish to do
      28             :     so, delete this exception statement from your version. If you delete
      29             :     this exception statement from all source files in the program, then
      30             :     also delete it here.
      31             : */
      32             : 
      33             : #include <stdio.h>
      34             : 
      35             : #include "terminaldisplay.h"
      36             : #include "terminalframebuffer.h"
      37             : 
      38             : using namespace Terminal;
      39             : 
      40             : /* Print a new "frame" to the terminal, using ANSI/ECMA-48 escape codes. */
      41             : 
      42      399034 : static const Renditions & initial_rendition( void )
      43             : {
      44      399034 :   const static Renditions blank = Renditions( 0 );
      45      399034 :   return blank;
      46             : }
      47             : 
      48         448 : std::string Display::open() const
      49             : {
      50        1344 :   return std::string( smcup ? smcup : "" ) + std::string( "\033[?1h" );
      51             : }
      52             : 
      53         448 : std::string Display::close() const
      54             : {
      55        1344 :   return std::string( "\033[?1l\033[0m\033[?25h"
      56             :                       "\033[?1003l\033[?1002l\033[?1001l\033[?1000l"
      57             :                       "\033[?1015l\033[?1006l\033[?1005l" ) +
      58        1792 :     std::string( rmcup ? rmcup : "" );
      59             : }
      60             : 
      61       24199 : std::string Display::new_frame( bool initialized, const Framebuffer &last, const Framebuffer &f ) const
      62             : {
      63       24199 :   FrameState frame( last );
      64             : 
      65       24199 :   char tmp[ 64 ];
      66             : 
      67             :   /* has bell been rung? */
      68       24199 :   if ( f.get_bell_count() != frame.last_frame.get_bell_count() ) {
      69           0 :     frame.append( '\007' );
      70             :   }
      71       24199 :   typedef Terminal::Framebuffer::title_type title_type;
      72             : 
      73             :   /* has icon name or window title changed? */
      74       24199 :   if ( has_title && f.is_title_initialized() &&
      75             :        ( (!initialized)
      76           0 :          || (f.get_icon_name() != frame.last_frame.get_icon_name())
      77           0 :          || (f.get_window_title() != frame.last_frame.get_window_title()) ) ) {
      78             :       /* set icon name and window title */
      79           0 :     if ( f.get_icon_name() == f.get_window_title() ) {
      80             :       /* write combined Icon Name and Window Title */
      81           0 :       frame.append( "\033]0;" );
      82           0 :       const title_type &window_title( f.get_window_title() );
      83           0 :       for ( title_type::const_iterator i = window_title.begin();
      84           0 :             i != window_title.end();
      85           0 :             i++ ) {
      86           0 :         frame.append( *i );
      87             :       }
      88           0 :       frame.append( '\007' );
      89             :       /* ST is more correct, but BEL more widely supported */
      90             :     } else {
      91             :       /* write Icon Name */
      92           0 :       frame.append( "\033]1;" );
      93           0 :       const title_type &icon_name( f.get_icon_name() );
      94           0 :       for ( title_type::const_iterator i = icon_name.begin();
      95           0 :             i != icon_name.end();
      96           0 :             i++ ) {
      97           0 :         frame.append( *i );
      98             :       }
      99           0 :       frame.append( '\007' );
     100             : 
     101           0 :       frame.append( "\033]2;" );
     102           0 :       const title_type &window_title( f.get_window_title() );
     103           0 :       for ( title_type::const_iterator i = window_title.begin();
     104           0 :             i != window_title.end();
     105           0 :             i++ ) {
     106           0 :         frame.append( *i );
     107             :       }
     108           0 :       frame.append( '\007' );
     109             :     }
     110             : 
     111             :   }
     112             : 
     113             :   /* has clipboard changed? */
     114       24199 :   if (f.get_clipboard() != frame.last_frame.get_clipboard()) {
     115           0 :     frame.append( "\033]52;c;" );
     116           0 :     const title_type &clipboard( f.get_clipboard() );
     117           0 :     for ( title_type::const_iterator i = clipboard.begin();
     118           0 :           i != clipboard.end();
     119           0 :           i++ ) {
     120           0 :       frame.append( *i );
     121             :     }
     122           0 :     frame.append( '\007' );
     123             :   }
     124             : 
     125             :   /* has reverse video state changed? */
     126       24199 :   if ( (!initialized)
     127       23751 :        || (f.ds.reverse_video != frame.last_frame.ds.reverse_video) ) {
     128             :     /* set reverse video */
     129         448 :     snprintf( tmp, 64, "\033[?5%c", (f.ds.reverse_video ? 'h' : 'l') );
     130         448 :     frame.append( tmp );
     131             :   }
     132             : 
     133             :   /* has size changed? */
     134       24199 :   if ( (!initialized)
     135       23751 :        || (f.ds.get_width() != frame.last_frame.ds.get_width())
     136       47884 :        || (f.ds.get_height() != frame.last_frame.ds.get_height()) ) {
     137             :     /* reset scrolling region */
     138         580 :     frame.append( "\033[r" );
     139             : 
     140             :     /* clear screen */
     141         580 :     frame.append( "\033[0m\033[H\033[2J" );
     142         580 :     initialized = false;
     143         580 :     frame.cursor_x = frame.cursor_y = 0;
     144         580 :     frame.current_rendition = initial_rendition();
     145             :   } else {
     146       23619 :     frame.cursor_x = frame.last_frame.ds.get_cursor_col();
     147       23619 :     frame.cursor_y = frame.last_frame.ds.get_cursor_row();
     148       23619 :     frame.current_rendition = frame.last_frame.ds.get_renditions();
     149             :   }
     150             : 
     151             :   /* is cursor visibility initialized? */
     152       23619 :   if ( !initialized ) {
     153         580 :     frame.cursor_visible = false;
     154         580 :     frame.append( "\033[?25l" );
     155             :   }
     156             : 
     157       24199 :   int frame_y = 0;
     158       24199 :   Framebuffer::row_pointer blank_row;
     159       24199 :   Framebuffer::rows_type rows( frame.last_frame.get_rows() );
     160             :   /* Extend rows if we've gotten a resize and new is wider than old */
     161       24199 :   if ( frame.last_frame.ds.get_width() < f.ds.get_width() ) {
     162         900 :     for ( Framebuffer::rows_type::iterator p = rows.begin(); p != rows.end(); p++ ) {
     163         864 :       *p = make_shared<Row>( **p );
     164        1728 :       (*p)->cells.resize( f.ds.get_width(), Cell( f.ds.get_background_rendition() ) );
     165             :     }
     166             :   }
     167             :   /* Add rows if we've gotten a resize and new is taller than old */
     168       24199 :   if ( static_cast<int>( rows.size() ) < f.ds.get_height() ) {
     169             :     // get a proper blank row
     170          30 :     const size_t w = f.ds.get_width();
     171          30 :     const color_type c = 0;
     172          30 :     blank_row = make_shared<Row>( w, c );
     173          30 :     rows.resize( f.ds.get_height(), blank_row );
     174             :   }
     175             : 
     176             :   /* shortcut -- has display moved up by a certain number of lines? */
     177       24199 :   if ( initialized ) {
     178      391229 :     int lines_scrolled = 0;
     179      391229 :     int scroll_height = 0;
     180             : 
     181      391229 :     for ( int row = 0; row < f.ds.get_height(); row++ ) {
     182      376608 :       const Row *new_row = f.get_row( 0 );
     183      376608 :       const Row *old_row = &*rows.at( row );
     184      376608 :       if ( ! ( new_row == old_row || *new_row == *old_row ) ) {
     185      367610 :         continue;
     186             :       }
     187             :       /* if row 0, we're looking at ourselves and probably didn't scroll */
     188        8998 :       if ( row == 0 ) {
     189             :         break;
     190             :       }
     191             :       /* found a scroll */
     192       17613 :       lines_scrolled = row;
     193             :       scroll_height = 1;
     194             : 
     195             :       /* how big is the region that was scrolled? */
     196       16131 :       for ( int region_height = 1;
     197       17613 :             lines_scrolled + region_height < f.ds.get_height();
     198             :             region_height++ ) {
     199       33326 :         if ( *f.get_row( region_height )
     200       16663 :              == *rows.at( lines_scrolled + region_height ) ) {
     201       16131 :           scroll_height = region_height + 1;
     202             :         } else {
     203             :           break;
     204             :         }
     205             :       }
     206             : 
     207             :       break;
     208             :     }
     209             : 
     210       16103 :     if ( scroll_height ) {
     211        1482 :       frame_y = scroll_height;
     212             : 
     213        1482 :       if ( lines_scrolled ) {
     214             :         /* Now we need a proper blank row. */
     215        1482 :         if ( blank_row.get() == NULL ) {
     216        1482 :           const size_t w = f.ds.get_width();
     217        1482 :           const color_type c = 0;
     218        1482 :           blank_row = make_shared<Row>( w, c );
     219             :         }
     220        1482 :         frame.update_rendition( initial_rendition(), true );
     221             : 
     222        1482 :         int top_margin = 0;
     223        1482 :         int bottom_margin = top_margin + lines_scrolled + scroll_height - 1;
     224             : 
     225        1482 :         assert( bottom_margin < f.ds.get_height() );
     226             : 
     227             :         /* Common case:  if we're already on the bottom line and we're scrolling the whole
     228             :          * screen, just do a CR and LFs.
     229             :          */
     230        1482 :         if ( scroll_height + lines_scrolled == f.ds.get_height()
     231        1482 :              && frame.cursor_y + 1 == f.ds.get_height() ) {
     232         950 :           frame.append( '\r' );
     233         950 :           frame.append( lines_scrolled, '\n' );
     234         950 :           frame.cursor_x = 0;
     235             :         } else {
     236             :           /* set scrolling region */
     237         532 :           snprintf( tmp, 64, "\033[%d;%dr",
     238             :                     top_margin + 1, bottom_margin + 1);
     239         532 :           frame.append( tmp );
     240             : 
     241             :           /* go to bottom of scrolling region */
     242         532 :           frame.cursor_x = frame.cursor_y = -1;
     243         532 :           frame.append_silent_move( bottom_margin, 0 );
     244             : 
     245             :           /* scroll */
     246         532 :           frame.append( lines_scrolled, '\n' );
     247             : 
     248             :           /* reset scrolling region */
     249         532 :           frame.append( "\033[r" );
     250             :           /* invalidate cursor position after unsetting scrolling region */
     251         532 :           frame.cursor_x = frame.cursor_y = -1;
     252             :         }
     253             : 
     254             :         /* do the move in our local index */
     255       36441 :         for ( int i = top_margin; i <= bottom_margin; i++ ) {
     256       34959 :           if ( i + lines_scrolled <= bottom_margin ) {
     257       17613 :             rows.at( i ) = rows.at( i + lines_scrolled );
     258             :           } else {
     259       52305 :             rows.at( i ) = blank_row;
     260             :           }
     261             :         }
     262             :       }
     263             :     }
     264             :   }
     265             : 
     266             :   /* Now update the display, row by row */
     267             :   bool wrap = false;
     268      585602 :   for ( ; frame_y < f.ds.get_height(); frame_y++ ) {
     269      561403 :     wrap = put_row( initialized, frame, f, frame_y, *rows.at( frame_y ), wrap );
     270             :   }
     271             : 
     272             :   /* has cursor location changed? */
     273       24199 :   if ( (!initialized)
     274       23619 :        || (f.ds.get_cursor_row() != frame.cursor_y)
     275       42837 :        || (f.ds.get_cursor_col() != frame.cursor_x) ) {
     276       11077 :     frame.append_move( f.ds.get_cursor_row(), f.ds.get_cursor_col() );
     277             :   }
     278             : 
     279             :   /* has cursor visibility changed? */
     280       24199 :   if ( (!initialized)
     281       23619 :        || (f.ds.cursor_visible != frame.cursor_visible) ) {
     282       14925 :     if ( f.ds.cursor_visible ) {
     283       14925 :       frame.append( "\033[?25h" );
     284             :     } else {
     285           0 :       frame.append( "\033[?25l" );
     286             :     }
     287             :   }
     288             : 
     289             :   /* have renditions changed? */
     290       24199 :   frame.update_rendition( f.ds.get_renditions(), !initialized );
     291             : 
     292             :   /* has bracketed paste mode changed? */
     293       24199 :   if ( (!initialized)
     294       23619 :        || (f.ds.bracketed_paste != frame.last_frame.ds.bracketed_paste) ) {
     295         600 :     frame.append( f.ds.bracketed_paste ? "\033[?2004h" : "\033[?2004l" );
     296             :   }
     297             : 
     298             :   /* has mouse reporting mode changed? */
     299       24199 :   if ( (!initialized)
     300       23619 :        || (f.ds.mouse_reporting_mode != frame.last_frame.ds.mouse_reporting_mode) ) {
     301         580 :     if (f.ds.mouse_reporting_mode == DrawState::MOUSE_REPORTING_NONE) {
     302         580 :       frame.append("\033[?1003l");
     303         580 :       frame.append("\033[?1002l");
     304         580 :       frame.append("\033[?1001l");
     305         580 :       frame.append("\033[?1000l");
     306             :     } else {
     307           0 :       if (frame.last_frame.ds.mouse_reporting_mode != DrawState::MOUSE_REPORTING_NONE) {
     308           0 :         snprintf(tmp, sizeof(tmp), "\033[?%dl", frame.last_frame.ds.mouse_reporting_mode);
     309           0 :         frame.append(tmp);
     310             :       }
     311           0 :       snprintf(tmp, sizeof(tmp), "\033[?%dh", f.ds.mouse_reporting_mode);
     312           0 :       frame.append(tmp);
     313             :     }
     314             :   }
     315             : 
     316             :   /* has mouse focus mode changed? */
     317       24199 :   if ( (!initialized)
     318       23619 :        || (f.ds.mouse_focus_event != frame.last_frame.ds.mouse_focus_event) ) {
     319         580 :     frame.append( f.ds.mouse_focus_event ? "\033[?1004h" : "\033[?1004l" );
     320             :   }
     321             : 
     322             :   /* has mouse encoding mode changed? */
     323       24199 :   if ( (!initialized)
     324       23619 :        || (f.ds.mouse_encoding_mode != frame.last_frame.ds.mouse_encoding_mode) ) {
     325         580 :     if (f.ds.mouse_encoding_mode == DrawState::MOUSE_ENCODING_DEFAULT) {
     326         580 :       frame.append("\033[?1015l");
     327         580 :       frame.append("\033[?1006l");
     328         580 :       frame.append("\033[?1005l");
     329             :     } else {
     330           0 :       if (frame.last_frame.ds.mouse_encoding_mode != DrawState::MOUSE_ENCODING_DEFAULT) {
     331           0 :         snprintf(tmp, sizeof(tmp), "\033[?%dl", frame.last_frame.ds.mouse_encoding_mode);
     332           0 :         frame.append(tmp);
     333             :       }
     334           0 :       snprintf(tmp, sizeof(tmp), "\033[?%dh", f.ds.mouse_encoding_mode);
     335           0 :       frame.append(tmp);
     336             :     }
     337             :   }
     338             : 
     339       24199 :   return frame.str;
     340       49910 : }
     341             : 
     342      561403 : bool Display::put_row( bool initialized, FrameState &frame, const Framebuffer &f, int frame_y, const Row &old_row, bool wrap ) const
     343             : {
     344      561403 :   char tmp[ 64 ];
     345      561403 :   int frame_x = 0;
     346             : 
     347      561403 :   const Row &row = *f.get_row( frame_y );
     348      561403 :   const Row::cells_type &cells = row.cells;
     349      561403 :   const Row::cells_type &old_cells = old_row.cells;
     350             : 
     351             :   /* If we're forced to write the first column because of wrap, go ahead and do so. */
     352      561403 :   if ( wrap ) {
     353        1522 :     const Cell &cell = cells.at( 0 );
     354        1522 :     frame.update_rendition( cell.get_renditions() );
     355        1522 :     frame.append_cell( cell );
     356        1522 :     frame_x += cell.get_width();
     357        1522 :     frame.cursor_x += cell.get_width();
     358             :   }
     359             : 
     360             :   /* If rows are the same object, we don't need to do anything at all. */
     361      561403 :   if (initialized && &row == &old_row ) {
     362             :     return false;
     363             :   }
     364             : 
     365      385328 :   const bool wrap_this = row.get_wrap();
     366      385328 :   const int row_width = f.ds.get_width();
     367      385328 :   int clear_count = 0;
     368      385328 :   bool wrote_last_cell = false;
     369      385328 :   Renditions blank_renditions = initial_rendition();
     370             : 
     371             :   /* iterate for every cell */
     372    31062206 :   while ( frame_x < row_width ) {
     373             : 
     374    30676878 :     const Cell &cell = cells.at( frame_x );
     375             : 
     376             :     /* Does cell need to be drawn?  Skip all this. */
     377    59698767 :     if ( initialized
     378    30676878 :          && !clear_count
     379    30676878 :          && ( cell == old_cells.at( frame_x ) ) ) {
     380    29021889 :       frame_x += cell.get_width();
     381    29021889 :       continue;
     382             :     }
     383             : 
     384             :     /* Slurp up all the empty cells */
     385     1654989 :     if ( cell.empty() ) {
     386     1067386 :       if ( !clear_count ) {
     387       14331 :         blank_renditions = cell.get_renditions();
     388             :       }
     389     1067386 :       if ( cell.get_renditions() == blank_renditions ) {
     390             :         /* Remember run of blank cells */
     391     1067386 :         clear_count++;
     392     1067386 :         frame_x++;
     393     1067386 :         continue;
     394             :       }
     395             :     }
     396             : 
     397             :     /* Clear or write cells within the row (not to end). */
     398      587603 :     if ( clear_count ) {
     399             :       /* Move to the right position. */
     400           0 :       frame.append_silent_move( frame_y, frame_x - clear_count );
     401           0 :       frame.update_rendition( blank_renditions );
     402           0 :       bool can_use_erase = has_bce || ( frame.current_rendition == initial_rendition() );
     403           0 :       if ( can_use_erase && has_ech && clear_count > 4 ) {
     404           0 :         snprintf( tmp, 64, "\033[%dX", clear_count );
     405           0 :         frame.append( tmp );
     406             :       } else {
     407           0 :         frame.append( clear_count, ' ' );
     408           0 :         frame.cursor_x = frame_x;
     409             :       }
     410           0 :       clear_count = 0;
     411             :       // If the current character is *another* empty cell in a different rendition,
     412             :       // we restart counting and continue here
     413           0 :       if ( cell.empty() ) {
     414           0 :         blank_renditions = cell.get_renditions();
     415           0 :         clear_count = 1;
     416           0 :         frame_x++;
     417           0 :         continue;
     418             :       }
     419             :     }
     420             : 
     421             : 
     422             :     /* Now draw a character cell. */
     423             :     /* Move to the right position. */
     424      587603 :     const int cell_width = cell.get_width();
     425             :     /* If we are about to print the last character in a wrapping row,
     426             :        trash the cursor position to force explicit positioning.  We do
     427             :        this because our input terminal state may have the cursor on
     428             :        the autowrap column ("column 81"), but our output terminal
     429             :        states always snap the cursor to the true last column ("column
     430             :        80"), and we want to be able to apply the diff to either, for
     431             :        verification. */
     432      587603 :     if ( wrap_this && frame_x + cell_width >= row_width ) {
     433        1522 :       frame.cursor_x = frame.cursor_y = -1;
     434             :     }
     435      587603 :     frame.append_silent_move( frame_y, frame_x );
     436      587603 :     frame.update_rendition( cell.get_renditions() );
     437      587603 :     frame.append_cell( cell );
     438      587603 :     frame_x += cell_width;
     439      587603 :     frame.cursor_x += cell_width;
     440      587603 :     if ( frame_x >= row_width ) {
     441        2500 :       wrote_last_cell = true;
     442             :     }
     443             :   }
     444             : 
     445             :   /* End of line. */
     446             : 
     447             :   /* Clear or write empty cells at EOL. */
     448      385328 :   if ( clear_count ) {
     449             :     /* Move to the right position. */
     450       14331 :     frame.append_silent_move( frame_y, frame_x - clear_count );
     451       14331 :     frame.update_rendition( blank_renditions );
     452             : 
     453       14331 :     bool can_use_erase = has_bce || ( frame.current_rendition == initial_rendition() );
     454       14331 :     if ( can_use_erase && !wrap_this ) {
     455       14283 :       frame.append( "\033[K" );
     456             :     } else {
     457          48 :       frame.append( clear_count, ' ' );
     458          48 :       frame.cursor_x = frame_x;
     459          48 :       wrote_last_cell = true;
     460             :     }
     461             :   }
     462             : 
     463      385328 :   if ( ! ( wrote_last_cell
     464        2548 :            && (frame_y < f.ds.get_height() - 1) ) ) {
     465      382782 :     return false;
     466             :   }
     467             :   /* To hint that a word-select should group the end of one line
     468             :      with the beginning of the next, we let the real cursor
     469             :      actually wrap around in cases where it wrapped around for us. */
     470        2546 :   if ( wrap_this ) {
     471             :     /* Update our cursor, and ask for wrap on the next row. */
     472        1522 :     frame.cursor_x = 0;
     473        1522 :     frame.cursor_y++;
     474        1522 :     return true;
     475             :   }
     476             :   /* Resort to CR/LF and update our cursor. */
     477        1024 :   frame.append( "\r\n" );
     478        1024 :   frame.cursor_x = 0;
     479        1024 :   frame.cursor_y++;
     480        1024 :   return false;
     481             : }
     482             : 
     483       24199 : FrameState::FrameState( const Framebuffer &s_last )
     484       24199 :       : str(), cursor_x(0), cursor_y(0), current_rendition( 0 ),
     485       24199 :         cursor_visible( s_last.ds.cursor_visible ),
     486       24199 :         last_frame( s_last )
     487             : {
     488             :   /* Preallocate for better performance.  Make a guess-- doesn't matter for correctness */
     489       24199 :   str.reserve( last_frame.ds.get_width() * last_frame.ds.get_height() * 4 );
     490       24199 : }
     491             : 
     492      602466 : void FrameState::append_silent_move( int y, int x )
     493             : {
     494      602466 :   if ( cursor_x == x && cursor_y == y ) return;
     495             :   /* turn off cursor if necessary before moving cursor */
     496      179045 :   if ( cursor_visible ) {
     497       14345 :     append( "\033[?25l" );
     498       14345 :     cursor_visible = false;
     499             :   }
     500      179045 :   append_move( y, x );
     501             : }
     502             : 
     503      190122 : void FrameState::append_move( int y, int x )
     504             : {
     505      190122 :   const int last_x = cursor_x;
     506      190122 :   const int last_y = cursor_y;
     507      190122 :   cursor_x = x;
     508      190122 :   cursor_y = y;
     509             :   // Only optimize if cursor pos is known
     510      190122 :   if ( last_x != -1 && last_y != -1 ) {
     511             :     // Can we use CR and/or LF?  They're cheap and easier to trace.
     512      187536 :     if ( x == 0 && y - last_y >= 0 && y - last_y < 5 ) {
     513      182289 :       if ( last_x != 0 ) {
     514      164539 :         append( '\r' );
     515             :       }
     516      182289 :       append( y - last_y, '\n' );
     517      364604 :       return;
     518             :     }
     519             :     // Backspaces are good too.
     520        5247 :     if ( y == last_y && x - last_x < 0 && x - last_x > -5 ) {
     521          26 :       append( last_x - x, '\b' );
     522          26 :       return;
     523             :     }
     524             :     // More optimizations are possible.
     525             :   }
     526        7807 :   char tmp[ 64 ];
     527        7807 :   snprintf( tmp, 64, "\033[%d;%dH", y + 1, x + 1 );
     528        7807 :   append( tmp );
     529             : }
     530             : 
     531      629137 : void FrameState::update_rendition(const Renditions &r, bool force) {
     532      629137 :   if ( force || !(current_rendition == r) ) {
     533             :     /* print renditions */
     534       63772 :     append_string( r.sgr() );
     535       31886 :     current_rendition = r;
     536             :   }
     537      629137 : }

Generated by: LCOV version 1.14