Coverage Report

Created: 2024-10-29 06:49

/src/mosh/src/terminal/terminalframebuffer.cc
Line
Count
Source (jump to first uncovered line)
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 <cassert>
34
#include <cstdio>
35
#include <cstdlib>
36
37
#include "src/terminal/terminalframebuffer.h"
38
39
using namespace Terminal;
40
41
Cell::Cell( color_type background_color )
42
238
  : contents(), renditions( background_color ), wide( false ), fallback( false ), wrap( false )
43
238
{}
44
45
void Cell::reset( color_type background_color )
46
0
{
47
0
  contents.clear();
48
0
  renditions = Renditions( background_color );
49
0
  wide = false;
50
0
  fallback = false;
51
0
  wrap = false;
52
0
}
53
54
void DrawState::reinitialize_tabs( unsigned int start )
55
238
{
56
238
  assert( default_tabs );
57
19.2k
  for ( unsigned int i = start; i < tabs.size(); i++ ) {
58
19.0k
    tabs[i] = ( ( i % 8 ) == 0 );
59
19.0k
  }
60
238
}
61
62
DrawState::DrawState( int s_width, int s_height )
63
238
  : width( s_width ), height( s_height ), cursor_col( 0 ), cursor_row( 0 ), combining_char_col( 0 ),
64
238
    combining_char_row( 0 ), default_tabs( true ), tabs( s_width ), scrolling_region_top_row( 0 ),
65
238
    scrolling_region_bottom_row( height - 1 ), renditions( 0 ), save(), next_print_will_wrap( false ),
66
238
    origin_mode( false ), auto_wrap_mode( true ), insert_mode( false ), cursor_visible( true ),
67
238
    reverse_video( false ), bracketed_paste( false ), mouse_reporting_mode( MOUSE_REPORTING_NONE ),
68
238
    mouse_focus_event( false ), mouse_alternate_scroll( false ), mouse_encoding_mode( MOUSE_ENCODING_DEFAULT ),
69
238
    application_mode_cursor_keys( false )
70
238
{
71
238
  reinitialize_tabs( 0 );
72
238
}
73
74
Framebuffer::Framebuffer( int s_width, int s_height )
75
238
  : rows(), icon_name(), window_title(), clipboard(), bell_count( 0 ), title_initialized( false ),
76
238
    ds( s_width, s_height )
77
238
{
78
238
  assert( s_height > 0 );
79
238
  assert( s_width > 0 );
80
238
  const size_t w = s_width;
81
238
  const color_type c = 0;
82
238
  rows = rows_type( s_height, row_pointer( std::make_shared<Row>( w, c ) ) );
83
238
}
84
85
Framebuffer::Framebuffer( const Framebuffer& other )
86
0
  : rows( other.rows ), icon_name( other.icon_name ), window_title( other.window_title ),
87
0
    clipboard( other.clipboard ), bell_count( other.bell_count ), title_initialized( other.title_initialized ),
88
0
    ds( other.ds )
89
0
{}
90
91
Framebuffer& Framebuffer::operator=( const Framebuffer& other )
92
0
{
93
0
  if ( this != &other ) {
94
0
    rows = other.rows;
95
0
    icon_name = other.icon_name;
96
0
    window_title = other.window_title;
97
0
    clipboard = other.clipboard;
98
0
    bell_count = other.bell_count;
99
0
    title_initialized = other.title_initialized;
100
0
    ds = other.ds;
101
0
  }
102
0
  return *this;
103
0
}
104
105
void Framebuffer::scroll( int N )
106
0
{
107
0
  if ( N >= 0 ) {
108
0
    delete_line( ds.get_scrolling_region_top_row(), N );
109
0
  } else {
110
0
    insert_line( ds.get_scrolling_region_top_row(), -N );
111
0
  }
112
0
}
113
114
void DrawState::new_grapheme( void )
115
0
{
116
0
  combining_char_col = cursor_col;
117
0
  combining_char_row = cursor_row;
118
0
}
119
120
void DrawState::snap_cursor_to_border( void )
121
0
{
122
0
  if ( cursor_row < limit_top() )
123
0
    cursor_row = limit_top();
124
0
  if ( cursor_row > limit_bottom() )
125
0
    cursor_row = limit_bottom();
126
0
  if ( cursor_col < 0 )
127
0
    cursor_col = 0;
128
0
  if ( cursor_col >= width )
129
0
    cursor_col = width - 1;
130
0
}
131
132
void DrawState::move_row( int N, bool relative )
133
0
{
134
0
  if ( relative ) {
135
0
    cursor_row += N;
136
0
  } else {
137
0
    cursor_row = N + limit_top();
138
0
  }
139
140
0
  snap_cursor_to_border();
141
0
  new_grapheme();
142
0
  next_print_will_wrap = false;
143
0
}
144
145
void DrawState::move_col( int N, bool relative, bool implicit )
146
0
{
147
0
  if ( implicit ) {
148
0
    new_grapheme();
149
0
  }
150
151
0
  if ( relative ) {
152
0
    cursor_col += N;
153
0
  } else {
154
0
    cursor_col = N;
155
0
  }
156
157
0
  if ( implicit ) {
158
0
    next_print_will_wrap = ( cursor_col >= width );
159
0
  }
160
161
0
  snap_cursor_to_border();
162
0
  if ( !implicit ) {
163
0
    new_grapheme();
164
0
    next_print_will_wrap = false;
165
0
  }
166
0
}
167
168
void Framebuffer::move_rows_autoscroll( int rows )
169
0
{
170
  /* don't scroll if outside the scrolling region */
171
0
  if ( ( ds.get_cursor_row() < ds.get_scrolling_region_top_row() )
172
0
       || ( ds.get_cursor_row() > ds.get_scrolling_region_bottom_row() ) ) {
173
0
    ds.move_row( rows, true );
174
0
    return;
175
0
  }
176
177
0
  if ( ds.get_cursor_row() + rows > ds.get_scrolling_region_bottom_row() ) {
178
0
    int N = ds.get_cursor_row() + rows - ds.get_scrolling_region_bottom_row();
179
0
    scroll( N );
180
0
    ds.move_row( -N, true );
181
0
  } else if ( ds.get_cursor_row() + rows < ds.get_scrolling_region_top_row() ) {
182
0
    int N = ds.get_cursor_row() + rows - ds.get_scrolling_region_top_row();
183
0
    scroll( N );
184
0
    ds.move_row( -N, true );
185
0
  }
186
187
0
  ds.move_row( rows, true );
188
0
}
189
190
Cell* Framebuffer::get_combining_cell( void )
191
0
{
192
0
  if ( ( ds.get_combining_char_col() < 0 ) || ( ds.get_combining_char_row() < 0 )
193
0
       || ( ds.get_combining_char_col() >= ds.get_width() )
194
0
       || ( ds.get_combining_char_row() >= ds.get_height() ) ) {
195
0
    return NULL;
196
0
  } /* can happen if a resize came in between */
197
198
0
  return get_mutable_cell( ds.get_combining_char_row(), ds.get_combining_char_col() );
199
0
}
200
201
void DrawState::set_tab( void )
202
0
{
203
0
  tabs[cursor_col] = true;
204
0
}
205
206
void DrawState::clear_tab( int col )
207
0
{
208
0
  tabs[col] = false;
209
0
}
210
211
int DrawState::get_next_tab( int count ) const
212
0
{
213
0
  if ( count >= 0 ) {
214
0
    for ( int i = cursor_col + 1; i < width; i++ ) {
215
0
      if ( tabs[i] && --count == 0 ) {
216
0
        return i;
217
0
      }
218
0
    }
219
0
    return -1;
220
0
  }
221
0
  for ( int i = cursor_col - 1; i > 0; i-- ) {
222
0
    if ( tabs[i] && ++count == 0 ) {
223
0
      return i;
224
0
    }
225
0
  }
226
0
  return 0;
227
0
}
228
229
void DrawState::set_scrolling_region( int top, int bottom )
230
0
{
231
0
  if ( height < 1 ) {
232
0
    return;
233
0
  }
234
235
0
  scrolling_region_top_row = top;
236
0
  scrolling_region_bottom_row = bottom;
237
238
0
  if ( scrolling_region_top_row < 0 )
239
0
    scrolling_region_top_row = 0;
240
0
  if ( scrolling_region_bottom_row >= height )
241
0
    scrolling_region_bottom_row = height - 1;
242
243
0
  if ( scrolling_region_bottom_row < scrolling_region_top_row )
244
0
    scrolling_region_bottom_row = scrolling_region_top_row;
245
  /* real rule requires TWO-line scrolling region */
246
247
0
  if ( origin_mode ) {
248
0
    snap_cursor_to_border();
249
0
    new_grapheme();
250
0
  }
251
0
}
252
253
int DrawState::limit_top( void ) const
254
0
{
255
0
  return origin_mode ? scrolling_region_top_row : 0;
256
0
}
257
258
int DrawState::limit_bottom( void ) const
259
0
{
260
0
  return origin_mode ? scrolling_region_bottom_row : height - 1;
261
0
}
262
263
void Framebuffer::apply_renditions_to_cell( Cell* cell )
264
0
{
265
0
  if ( !cell ) {
266
0
    cell = get_mutable_cell();
267
0
  }
268
0
  cell->set_renditions( ds.get_renditions() );
269
0
}
270
271
SavedCursor::SavedCursor()
272
238
  : cursor_col( 0 ), cursor_row( 0 ), renditions( 0 ), auto_wrap_mode( true ), origin_mode( false )
273
238
{}
274
275
void DrawState::save_cursor( void )
276
0
{
277
0
  save.cursor_col = cursor_col;
278
0
  save.cursor_row = cursor_row;
279
0
  save.renditions = renditions;
280
0
  save.auto_wrap_mode = auto_wrap_mode;
281
0
  save.origin_mode = origin_mode;
282
0
}
283
284
void DrawState::restore_cursor( void )
285
0
{
286
0
  cursor_col = save.cursor_col;
287
0
  cursor_row = save.cursor_row;
288
0
  renditions = save.renditions;
289
0
  auto_wrap_mode = save.auto_wrap_mode;
290
0
  origin_mode = save.origin_mode;
291
292
0
  snap_cursor_to_border(); /* we could have resized in between */
293
0
  new_grapheme();
294
0
}
295
296
void Framebuffer::insert_line( int before_row, int count )
297
0
{
298
0
  if ( ( before_row < ds.get_scrolling_region_top_row() )
299
0
       || ( before_row > ds.get_scrolling_region_bottom_row() + 1 ) ) {
300
0
    return;
301
0
  }
302
303
0
  int scroll = ds.get_scrolling_region_bottom_row() + 1 - before_row;
304
0
  if ( count < scroll ) {
305
0
    scroll = count;
306
0
  }
307
308
0
  if ( scroll == 0 ) {
309
0
    return;
310
0
  }
311
312
  // delete old rows
313
0
  rows_type::iterator start = rows.begin() + ds.get_scrolling_region_bottom_row() + 1 - scroll;
314
0
  rows.erase( start, start + scroll );
315
  // insert new rows
316
0
  start = rows.begin() + before_row;
317
0
  rows.insert( start, scroll, newrow() );
318
0
}
319
320
void Framebuffer::delete_line( int row, int count )
321
0
{
322
0
  if ( ( row < ds.get_scrolling_region_top_row() ) || ( row > ds.get_scrolling_region_bottom_row() ) ) {
323
0
    return;
324
0
  }
325
326
0
  int scroll = ds.get_scrolling_region_bottom_row() + 1 - row;
327
0
  if ( count < scroll ) {
328
0
    scroll = count;
329
0
  }
330
331
0
  if ( scroll == 0 ) {
332
0
    return;
333
0
  }
334
335
  // delete old rows
336
0
  rows_type::iterator start = rows.begin() + row;
337
0
  rows.erase( start, start + scroll );
338
  // insert a block of dummy rows
339
0
  start = rows.begin() + ds.get_scrolling_region_bottom_row() + 1 - scroll;
340
0
  rows.insert( start, scroll, newrow() );
341
0
}
342
343
Row::Row( const size_t s_width, const color_type background_color )
344
238
  : cells( s_width, Cell( background_color ) ), gen( get_gen() )
345
238
{}
346
347
uint64_t Row::get_gen() const
348
238
{
349
238
  static uint64_t gen_counter = 0;
350
238
  return gen_counter++;
351
238
}
352
353
void Row::insert_cell( int col, color_type background_color )
354
0
{
355
0
  cells.insert( cells.begin() + col, Cell( background_color ) );
356
0
  cells.pop_back();
357
0
}
358
359
void Row::delete_cell( int col, color_type background_color )
360
0
{
361
0
  cells.push_back( Cell( background_color ) );
362
0
  cells.erase( cells.begin() + col );
363
0
}
364
365
void Framebuffer::insert_cell( int row, int col )
366
0
{
367
0
  get_mutable_row( row )->insert_cell( col, ds.get_background_rendition() );
368
0
}
369
370
void Framebuffer::delete_cell( int row, int col )
371
0
{
372
0
  get_mutable_row( row )->delete_cell( col, ds.get_background_rendition() );
373
0
}
374
375
void Framebuffer::reset( void )
376
0
{
377
0
  int width = ds.get_width(), height = ds.get_height();
378
0
  ds = DrawState( width, height );
379
0
  rows = rows_type( height, newrow() );
380
0
  window_title.clear();
381
0
  clipboard.clear();
382
  /* do not reset bell_count */
383
0
}
384
385
void Framebuffer::soft_reset( void )
386
0
{
387
0
  ds.insert_mode = false;
388
0
  ds.origin_mode = false;
389
0
  ds.cursor_visible = true; /* per xterm and gnome-terminal */
390
0
  ds.application_mode_cursor_keys = false;
391
0
  ds.set_scrolling_region( 0, ds.get_height() - 1 );
392
0
  ds.add_rendition( 0 );
393
0
  ds.clear_saved_cursor();
394
0
}
395
396
void Framebuffer::resize( int s_width, int s_height )
397
0
{
398
0
  assert( s_width > 0 );
399
0
  assert( s_height > 0 );
400
401
0
  int oldheight = ds.get_height();
402
0
  int oldwidth = ds.get_width();
403
0
  ds.resize( s_width, s_height );
404
405
0
  row_pointer blankrow( newrow() );
406
0
  if ( oldheight != s_height ) {
407
0
    rows.resize( s_height, blankrow );
408
0
  }
409
0
  if ( oldwidth == s_width ) {
410
0
    return;
411
0
  }
412
0
  for ( rows_type::iterator i = rows.begin(); i != rows.end() && *i != blankrow; i++ ) {
413
0
    *i = std::make_shared<Row>( **i );
414
0
    ( *i )->set_wrap( false );
415
0
    ( *i )->cells.resize( s_width, Cell( ds.get_background_rendition() ) );
416
0
  }
417
0
}
418
419
void DrawState::resize( int s_width, int s_height )
420
0
{
421
0
  if ( ( width != s_width ) || ( height != s_height ) ) {
422
    /* reset entire scrolling region on any resize */
423
    /* xterm and rxvt-unicode do this. gnome-terminal only
424
       resets scrolling region if it has to become smaller in resize */
425
0
    scrolling_region_top_row = 0;
426
0
    scrolling_region_bottom_row = s_height - 1;
427
0
  }
428
429
0
  tabs.resize( s_width );
430
0
  if ( default_tabs ) {
431
0
    reinitialize_tabs( width );
432
0
  }
433
434
0
  width = s_width;
435
0
  height = s_height;
436
437
0
  snap_cursor_to_border();
438
439
  /* saved cursor will be snapped to border on restore */
440
441
  /* invalidate combining char cell if necessary */
442
0
  if ( ( combining_char_col >= width ) || ( combining_char_row >= height ) ) {
443
0
    combining_char_col = combining_char_row = -1;
444
0
  }
445
0
}
446
447
Renditions::Renditions( color_type s_background )
448
834
  : foreground_color( 0 ), background_color( s_background ), attributes( 0 )
449
834
{}
450
451
/* This routine cannot be used to set a color beyond the 16-color set. */
452
void Renditions::set_rendition( color_type num )
453
0
{
454
0
  if ( num == 0 ) {
455
0
    clear_attributes();
456
0
    foreground_color = background_color = 0;
457
0
    return;
458
0
  }
459
460
0
  if ( num == 39 ) {
461
0
    foreground_color = 0;
462
0
    return;
463
0
  } else if ( num == 49 ) {
464
0
    background_color = 0;
465
0
    return;
466
0
  }
467
468
0
  if ( ( 30 <= num ) && ( num <= 37 ) ) { /* foreground color in 8-color set */
469
0
    foreground_color = num;
470
0
    return;
471
0
  } else if ( ( 40 <= num ) && ( num <= 47 ) ) { /* background color in 8-color set */
472
0
    background_color = num;
473
0
    return;
474
0
  } else if ( ( 90 <= num ) && ( num <= 97 ) ) { /* foreground color in 16-color set */
475
0
    foreground_color = num - 90 + 38;
476
0
    return;
477
0
  } else if ( ( 100 <= num ) && ( num <= 107 ) ) { /* background color in 16-color set */
478
0
    background_color = num - 100 + 48;
479
0
    return;
480
0
  }
481
482
0
  bool value = num < 9;
483
0
  switch ( num ) {
484
0
    case 1:
485
0
    case 22:
486
0
      set_attribute( bold, value );
487
0
      break;
488
0
    case 3:
489
0
    case 23:
490
0
      set_attribute( italic, value );
491
0
      break;
492
0
    case 4:
493
0
    case 24:
494
0
      set_attribute( underlined, value );
495
0
      break;
496
0
    case 5:
497
0
    case 25:
498
0
      set_attribute( blink, value );
499
0
      break;
500
0
    case 7:
501
0
    case 27:
502
0
      set_attribute( inverse, value );
503
0
      break;
504
0
    case 8:
505
0
    case 28:
506
0
      set_attribute( invisible, value );
507
0
      break;
508
0
    default:
509
0
      break; /* ignore unknown rendition */
510
0
  }
511
0
}
512
513
void Renditions::set_foreground_color( int num )
514
0
{
515
0
  if ( ( 0 <= num ) && ( num <= 255 ) ) {
516
0
    foreground_color = 30 + num;
517
0
  } else if ( is_true_color( num ) ) {
518
0
    foreground_color = num;
519
0
  }
520
0
}
521
522
void Renditions::set_background_color( int num )
523
0
{
524
0
  if ( ( 0 <= num ) && ( num <= 255 ) ) {
525
0
    background_color = 40 + num;
526
0
  } else if ( is_true_color( num ) ) {
527
0
    background_color = num;
528
0
  }
529
0
}
530
531
std::string Renditions::sgr( void ) const
532
0
{
533
0
  std::string ret;
534
0
  char col[64];
535
536
0
  ret.append( "\033[0" );
537
0
  if ( get_attribute( bold ) )
538
0
    ret.append( ";1" );
539
0
  if ( get_attribute( italic ) )
540
0
    ret.append( ";3" );
541
0
  if ( get_attribute( underlined ) )
542
0
    ret.append( ";4" );
543
0
  if ( get_attribute( blink ) )
544
0
    ret.append( ";5" );
545
0
  if ( get_attribute( inverse ) )
546
0
    ret.append( ";7" );
547
0
  if ( get_attribute( invisible ) )
548
0
    ret.append( ";8" );
549
550
0
  if ( foreground_color ) {
551
    // Since foreground_color is a 25-bit field, it is promoted to an int when
552
    // manipulated. (See [conv.prom] in various C++ standards, e.g.,
553
    // https://timsong-cpp.github.io/cppwp/n4659/conv.prom#5.) The correct
554
    // printf format specifier is thus %d.
555
0
    if ( is_true_color( foreground_color ) ) {
556
0
      snprintf( col,
557
0
                sizeof( col ),
558
0
                ";38;2;%d;%d;%d",
559
0
                ( foreground_color >> 16 ) & 0xff,
560
0
                ( foreground_color >> 8 ) & 0xff,
561
0
                foreground_color & 0xff );
562
0
    } else if ( foreground_color > 37 ) { /* use 256-color set */
563
0
      snprintf( col, sizeof( col ), ";38;5;%d", foreground_color - 30 );
564
0
    } else { /* ANSI foreground color */
565
      // Unfortunately, some versions of GCC (notably including GCC 9.3) give
566
      // -Wformat warnings when relying on [conv.prom] to promote
567
      // foreground_color in calls to printf. Explicitly promote it to silence
568
      // the warning.
569
0
      int fg = foreground_color;
570
0
      snprintf( col, sizeof( col ), ";%d", fg );
571
0
    }
572
0
    ret.append( col );
573
0
  }
574
0
  if ( background_color ) {
575
    // See comment above about bit-field promotion; it applies here as well.
576
0
    if ( is_true_color( background_color ) ) {
577
0
      snprintf( col,
578
0
                sizeof( col ),
579
0
                ";48;2;%d;%d;%d",
580
0
                ( background_color >> 16 ) & 0xff,
581
0
                ( background_color >> 8 ) & 0xff,
582
0
                background_color & 0xff );
583
0
    } else if ( background_color > 47 ) { /* use 256-color set */
584
0
      snprintf( col, sizeof( col ), ";48;5;%d", background_color - 40 );
585
0
    } else { /* ANSI background color */
586
      // See comment above about explicit promotion; it applies here as well.
587
0
      int bg = background_color;
588
0
      snprintf( col, sizeof( col ), ";%d", bg );
589
0
    }
590
0
    ret.append( col );
591
0
  }
592
0
  ret.append( "m" );
593
594
0
  return ret;
595
0
}
596
597
void Row::reset( color_type background_color )
598
0
{
599
0
  gen = get_gen();
600
0
  for ( cells_type::iterator i = cells.begin(); i != cells.end(); i++ ) {
601
0
    i->reset( background_color );
602
0
  }
603
0
}
604
605
void Framebuffer::prefix_window_title( const title_type& s )
606
0
{
607
0
  if ( icon_name == window_title ) {
608
    /* preserve equivalence */
609
0
    icon_name.insert( icon_name.begin(), s.begin(), s.end() );
610
0
  }
611
0
  window_title.insert( window_title.begin(), s.begin(), s.end() );
612
0
}
613
614
std::string Cell::debug_contents( void ) const
615
0
{
616
0
  if ( contents.empty() ) {
617
0
    return "'_' ()";
618
0
  }
619
0
  std::string chars( 1, '\'' );
620
0
  print_grapheme( chars );
621
0
  chars.append( "' [" );
622
0
  const char* lazycomma = "";
623
0
  char buf[64];
624
0
  for ( content_type::const_iterator i = contents.begin(); i < contents.end(); i++ ) {
625
626
0
    snprintf( buf, sizeof buf, "%s0x%02x", lazycomma, static_cast<uint8_t>( *i ) );
627
0
    chars.append( buf );
628
0
    lazycomma = ", ";
629
0
  }
630
0
  chars.append( "]" );
631
0
  return chars;
632
0
}
633
634
bool Cell::compare( const Cell& other ) const
635
0
{
636
0
  bool ret = false;
637
638
0
  std::string grapheme, other_grapheme;
639
640
0
  print_grapheme( grapheme );
641
0
  other.print_grapheme( other_grapheme );
642
643
0
  if ( grapheme != other_grapheme ) {
644
0
    ret = true;
645
0
    fprintf( stderr, "Graphemes: '%s' vs. '%s'\n", grapheme.c_str(), other_grapheme.c_str() );
646
0
  }
647
648
0
  if ( !contents_match( other ) ) {
649
    // ret = true;
650
0
    fprintf( stderr,
651
0
             "Contents: %s (%ld) vs. %s (%ld)\n",
652
0
             debug_contents().c_str(),
653
0
             static_cast<long int>( contents.size() ),
654
0
             other.debug_contents().c_str(),
655
0
             static_cast<long int>( other.contents.size() ) );
656
0
  }
657
658
0
  if ( fallback != other.fallback ) {
659
    // ret = true;
660
    // Since fallback is a 1-bit field, it is promoted to an int when
661
    // manipulated. (See [conv.prom] in various C++ standards, e.g.,
662
    // https://timsong-cpp.github.io/cppwp/n4659/conv.prom#5.) The correct
663
    // printf format specifier is thus %d.
664
0
    fprintf( stderr, "fallback: %d vs. %d\n", fallback, other.fallback );
665
0
  }
666
667
0
  if ( wide != other.wide ) {
668
0
    ret = true;
669
    // See comment above about bit-field promotion; it applies here as well.
670
0
    fprintf( stderr, "width: %d vs. %d\n", wide, other.wide );
671
0
  }
672
673
0
  if ( !( renditions == other.renditions ) ) {
674
0
    ret = true;
675
0
    fprintf( stderr, "renditions differ\n" );
676
0
  }
677
678
0
  if ( wrap != other.wrap ) {
679
0
    ret = true;
680
    // See comment above about bit-field promotion; it applies here as well.
681
0
    fprintf( stderr, "wrap: %d vs. %d\n", wrap, other.wrap );
682
0
  }
683
684
0
  return ret;
685
0
}