chiark / gitweb /
terminal: parse ID_SEAT not only for parents but the device itself
[elogind.git] / src / libsystemd-terminal / term-page.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 /*
23  * Terminal Page/Line/Cell/Char Handling
24  * This file implements page handling of a terminal. It is split into pages,
25  * lines, cells and characters. Each object is independent of the next upper
26  * object.
27  *
28  * The Terminal layer keeps each line of a terminal separate and dynamically
29  * allocated. This allows us to move lines from main-screen to history-buffers
30  * very fast. Same is true for scrolling, top/bottom borders and other buffer
31  * operations.
32  *
33  * While lines are dynamically allocated, cells are not. This would be a waste
34  * of memory and causes heavy fragmentation. Furthermore, cells are moved much
35  * less frequently than lines so the performance-penalty is pretty small.
36  * However, to support combining-characters, we have to initialize and cleanup
37  * cells properly and cannot just release the underlying memory. Therefore,
38  * cells are treated as proper objects despite being allocated in arrays.
39  *
40  * Each cell has a set of attributes and a stored character. This is usually a
41  * single Unicode character stored as 32bit UCS-4 char. However, we need to
42  * support Unicode combining-characters, therefore this gets more complicated.
43  * Characters themselves are represented by a "term_char_t" object. It
44  * should be treated as a normal integer and passed by value. The
45  * surrounding struct is just to hide the internals. A term-char can contain a
46  * base character together with up to 2 combining-chars in a single integer.
47  * Only if you need more combining-chars (very unlikely!) a term-char is a
48  * pointer to an allocated storage. This requires you to always free term-char
49  * objects once no longer used (even though this is a no-op most of the time).
50  * Furthermore, term-char objects are not ref-counted so you must duplicate them
51  * in case you want to store it somewhere and retain a copy yourself. By
52  * convention, all functions that take a term-char object will not duplicate
53  * it but implicitly take ownership of the passed value. It's up to the caller
54  * to duplicate it beforehand, in case it wants to retain a copy.
55  *
56  * If it turns out, that more than 2 comb-chars become common in specific
57  * languages, we can try to optimize this. One idea is to ref-count allocated
58  * characters and store them in a hash-table (like gnome's libvte3 does). This
59  * way we will never have two allocated chars for the same content. Or we can
60  * simply put two uint64_t into a "term_char_t". This will slow down operations
61  * on systems that don't need that many comb-chars, but avoid the dynamic
62  * allocations on others.
63  * Anyhow, until we have proper benchmarks, we will keep the current code. It
64  * seems to compete very well with other solutions so far.
65  *
66  * The page-layer is a one-dimensional array of lines. Considering that each
67  * line is a one-dimensional array of cells, the page layer provides the
68  * two-dimensional cell-page required for terminals. The page itself only
69  * operates on lines. All cell-related operations are forwarded to the correct
70  * line.
71  * A page does not contain any cursor tracking. It only provides the raw
72  * operations to shuffle lines and modify the page.
73  */
74
75 #include <stdbool.h>
76 #include <stdint.h>
77 #include <stdlib.h>
78 #include <wchar.h>
79 #include "macro.h"
80 #include "term-internal.h"
81 #include "util.h"
82
83 /* maximum UCS-4 character */
84 #define CHAR_UCS4_MAX (0x10ffff)
85 /* mask for valid UCS-4 characters (21bit) */
86 #define CHAR_UCS4_MASK (0x1fffff)
87 /* UCS-4 replacement character */
88 #define CHAR_UCS4_REPLACEMENT (0xfffd)
89
90 /* real storage behind "term_char_t" in case it's not packed */
91 typedef struct term_character {
92         uint8_t n;
93         uint32_t codepoints[];
94 } term_character;
95
96 /*
97  * char_pack() takes 3 UCS-4 values and packs them into a term_char_t object.
98  * Note that UCS-4 chars only take 21 bits, so we still have the LSB as marker.
99  * We set it to 1 so others can distinguish it from pointers.
100  */
101 static inline term_char_t char_pack(uint32_t v1, uint32_t v2, uint32_t v3) {
102         uint64_t packed, u1, u2, u3;
103
104         u1 = v1;
105         u2 = v2;
106         u3 = v3;
107
108         packed = 0x01;
109         packed |= (u1 & (uint64_t)CHAR_UCS4_MASK) << 43;
110         packed |= (u2 & (uint64_t)CHAR_UCS4_MASK) << 22;
111         packed |= (u3 & (uint64_t)CHAR_UCS4_MASK) <<  1;
112
113         return TERM_CHAR_INIT(packed);
114 }
115
116 #define char_pack1(_v1) char_pack2((_v1), CHAR_UCS4_MAX + 1)
117 #define char_pack2(_v1, _v2) char_pack3((_v1), (_v2), CHAR_UCS4_MAX + 1)
118 #define char_pack3(_v1, _v2, _v3) char_pack((_v1), (_v2), (_v3))
119
120 /*
121  * char_unpack() is the inverse of char_pack(). It extracts the 3 stored UCS-4
122  * characters and returns them. Note that this does not validate the passed
123  * term_char_t. That's the responsibility of the caller.
124  * This returns the number of characters actually packed. This obviously is a
125  * number between 0 and 3 (inclusive).
126  */
127 static inline uint8_t char_unpack(term_char_t packed, uint32_t *out_v1, uint32_t *out_v2, uint32_t *out_v3) {
128         uint32_t v1, v2, v3;
129
130         v1 = (packed._value >> 43) & (uint64_t)CHAR_UCS4_MASK;
131         v2 = (packed._value >> 22) & (uint64_t)CHAR_UCS4_MASK;
132         v3 = (packed._value >>  1) & (uint64_t)CHAR_UCS4_MASK;
133
134         if (out_v1)
135                 *out_v1 = v1;
136         if (out_v2)
137                 *out_v2 = v2;
138         if (out_v3)
139                 *out_v3 = v3;
140
141         return (v1 > CHAR_UCS4_MAX) ? 0 :
142               ((v2 > CHAR_UCS4_MAX) ? 1 :
143               ((v3 > CHAR_UCS4_MAX) ? 2 :
144                                       3));
145 }
146
147 /* cast a term_char_t to a term_character* */
148 static inline term_character *char_to_ptr(term_char_t ch) {
149         return (term_character*)(unsigned long)ch._value;
150 }
151
152 /* cast a term_character* to a term_char_t */
153 static inline term_char_t char_from_ptr(term_character *c) {
154         return TERM_CHAR_INIT((unsigned long)c);
155 }
156
157 /*
158  * char_alloc() allocates a properly aligned term_character object and returns
159  * a pointer to it. NULL is returned on allocation errors. The object will have
160  * enough room for @n following UCS-4 chars.
161  * Note that we allocate (n+1) characters and set the last one to 0 in case
162  * anyone prints this string for debugging.
163  */
164 static term_character *char_alloc(uint8_t n) {
165         term_character *c;
166         int r;
167
168         r = posix_memalign((void**)&c,
169                            MAX(sizeof(void*), (size_t)2),
170                            sizeof(*c) + sizeof(*c->codepoints) * (n + 1));
171         if (r)
172                 return NULL;
173
174         c->n = n;
175         c->codepoints[n] = 0;
176
177         return c;
178 }
179
180 /*
181  * char_free() frees the memory allocated via char_alloc(). It is safe to call
182  * this on any term_char_t, only allocated characters are freed.
183  */
184 static inline void char_free(term_char_t ch) {
185         if (term_char_is_allocated(ch))
186                 free(char_to_ptr(ch));
187 }
188
189 /*
190  * This appends @append_ucs4 to the existing character @base and returns
191  * it as a new character. In case that's not possible, @base is returned. The
192  * caller can use term_char_same() to test whether the returned character was
193  * freshly allocated or not.
194  */
195 static term_char_t char_build(term_char_t base, uint32_t append_ucs4) {
196         /* soft-limit for combining-chars; hard-limit is currently 255 */
197         const size_t climit = 64;
198         term_character *c;
199         uint32_t buf[3], *t;
200         uint8_t n;
201
202         /* ignore invalid UCS-4 */
203         if (append_ucs4 > CHAR_UCS4_MAX)
204                 return base;
205
206         if (term_char_is_null(base)) {
207                 return char_pack1(append_ucs4);
208         } else if (!term_char_is_allocated(base)) {
209                 /* unpack and try extending the packed character */
210                 n = char_unpack(base, &buf[0], &buf[1], &buf[2]);
211
212                 switch (n) {
213                 case 0:
214                         return char_pack1(append_ucs4);
215                 case 1:
216                         if (climit < 2)
217                                 return base;
218
219                         return char_pack2(buf[0], append_ucs4);
220                 case 2:
221                         if (climit < 3)
222                                 return base;
223
224                         return char_pack3(buf[0], buf[1], append_ucs4);
225                 default:
226                         /* fallthrough */
227                         break;
228                 }
229
230                 /* already fully packed, we need to allocate a new one */
231                 t = buf;
232         } else {
233                 /* already an allocated type, we need to allocate a new one */
234                 c = char_to_ptr(base);
235                 t = c->codepoints;
236                 n = c->n;
237         }
238
239         /* bail out if soft-limit is reached */
240         if (n >= climit)
241                 return base;
242
243         /* allocate new char */
244         c = char_alloc(n + 1);
245         if (!c)
246                 return base;
247
248         memcpy(c->codepoints, t, sizeof(*t) * n);
249         c->codepoints[n] = append_ucs4;
250
251         return char_from_ptr(c);
252 }
253
254 /**
255  * term_char_set() - Reset character to a single UCS-4 character
256  * @previous: term-char to reset
257  * @append_ucs4: UCS-4 char to set
258  *
259  * This frees all resources in @previous and re-initializes it to @append_ucs4.
260  * The new char is returned.
261  *
262  * Usually, this is used like this:
263  *   obj->ch = term_char_set(obj->ch, ucs4);
264  *
265  * Returns: The previous character reset to @append_ucs4.
266  */
267 term_char_t term_char_set(term_char_t previous, uint32_t append_ucs4) {
268         char_free(previous);
269         return char_build(TERM_CHAR_NULL, append_ucs4);
270 }
271
272 /**
273  * term_char_merge() - Merge UCS-4 char at the end of an existing char
274  * @base: existing term-char
275  * @append_ucs4: UCS-4 character to append
276  *
277  * This appends @append_ucs4 to @base and returns the result. @base is
278  * invalidated by this function and must no longer be used. The returned value
279  * replaces the old one.
280  *
281  * Usually, this is used like this:
282  *   obj->ch = term_char_merge(obj->ch, ucs4);
283  *
284  * Returns: The new merged character.
285  */
286 term_char_t term_char_merge(term_char_t base, uint32_t append_ucs4) {
287         term_char_t ch;
288
289         ch = char_build(base, append_ucs4);
290         if (!term_char_same(ch, base))
291                 term_char_free(base);
292
293         return ch;
294 }
295
296 /**
297  * term_char_dup() - Duplicate character
298  * @ch: character to duplicate
299  *
300  * This duplicates a term-character. In case the character is not allocated,
301  * nothing is done. Otherwise, the underlying memory is copied and returned. You
302  * need to call term_char_free() on the returned character to release it again.
303  * On allocation errors, a replacement character is returned. Therefore, the
304  * caller can safely assume that this function always succeeds.
305  *
306  * Returns: The duplicated term-character.
307  */
308 term_char_t term_char_dup(term_char_t ch) {
309         term_character *c, *newc;
310
311         if (!term_char_is_allocated(ch))
312                 return ch;
313
314         c = char_to_ptr(ch);
315         newc = char_alloc(c->n);
316         if (!newc)
317                 return char_pack1(CHAR_UCS4_REPLACEMENT);
318
319         memcpy(newc->codepoints, c->codepoints, sizeof(*c->codepoints) * c->n);
320         return char_from_ptr(newc);
321 }
322
323 /**
324  * term_char_dup_append() - Duplicate tsm-char with UCS-4 character appended
325  * @base: existing term-char
326  * @append_ucs4: UCS-4 character to append
327  *
328  * This is similar to term_char_merge(), but it returns a separately allocated
329  * character. That is, @base will stay valid after this returns and is not
330  * touched. In case the append-operation fails, @base is duplicated and
331  * returned. That is, the returned char is always independent of @base.
332  *
333  * Returns: Newly allocated character with @append_ucs4 appended to @base.
334  */
335 term_char_t term_char_dup_append(term_char_t base, uint32_t append_ucs4) {
336         term_char_t ch;
337
338         ch = char_build(base, append_ucs4);
339         if (term_char_same(ch, base))
340                 ch = term_char_dup(base);
341
342         return ch;
343 }
344
345 /**
346  * term_char_resolve() - Retrieve the UCS-4 string for a term-char
347  * @ch: character to resolve
348  * @s: storage for size of string or NULL
349  * @b: storage for string or NULL
350  *
351  * This takes a term-character and returns the UCS-4 string associated with it.
352  * In case @ch is not allocated, the string is stored in @b (in case @b is NULL
353  * static storage is used). Otherwise, a pointer to the allocated storage is
354  * returned.
355  *
356  * The returned string is only valid as long as @ch and @b are valid. The string
357  * is zero-terminated and can safely be printed via long-character printf().
358  * The length of the string excluding the zero-character is returned in @s.
359  *
360  * This never returns NULL. Even if the size is 0, this points to a buffer of at
361  * least a zero-terminator.
362  *
363  * Returns: The UCS-4 string-representation of @ch, and its size in @s.
364  */
365 const uint32_t *term_char_resolve(term_char_t ch, size_t *s, term_charbuf_t *b) {
366         static term_charbuf_t static_b;
367         term_character *c;
368         uint32_t *cache;
369         size_t len;
370
371         if (b)
372                 cache = b->buf;
373         else
374                 cache = static_b.buf;
375
376         if (term_char_is_null(ch)) {
377                 len = 0;
378                 cache[0] = 0;
379         } else if (term_char_is_allocated(ch)) {
380                 c = char_to_ptr(ch);
381                 len = c->n;
382                 cache = c->codepoints;
383         } else {
384                 len = char_unpack(ch, &cache[0], &cache[1], &cache[2]);
385                 cache[len] = 0;
386         }
387
388         if (s)
389                 *s = len;
390
391         return cache;
392 }
393
394 /**
395  * term_char_lookup_width() - Lookup cell-width of a character
396  * @ch: character to return cell-width for
397  *
398  * This is an equivalent of wcwidth() for term_char_t. It can deal directly
399  * with UCS-4 and combining-characters and avoids the mess that is wchar_t and
400  * locale handling.
401  *
402  * Returns: 0 for unprintable characters, >0 for everything else.
403  */
404 unsigned int term_char_lookup_width(term_char_t ch) {
405         term_charbuf_t b;
406         const uint32_t *str;
407         unsigned int max;
408         size_t i, len;
409         int r;
410
411         max = 0;
412         str = term_char_resolve(ch, &len, &b);
413
414         for (i = 0; i < len; ++i) {
415                 /*
416                  * Oh god, C99 locale handling strikes again: wcwidth() expects
417                  * wchar_t, but there is no way for us to know the
418                  * internal encoding of wchar_t. Moreover, it is nearly
419                  * impossible to convert UCS-4 into wchar_t (except for iconv,
420                  * which is way too much overhead).
421                  * Therefore, we use our own copy of wcwidth(). Lets just hope
422                  * that glibc will one day export it's internal UCS-4 and UTF-8
423                  * helpers for direct use.
424                  */
425                 assert_cc(sizeof(wchar_t) >= 4);
426                 r = mk_wcwidth((wchar_t)str[i]);
427                 if (r > 0 && (unsigned int)r > max)
428                         max = r;
429         }
430
431         return max;
432 }
433
434 /**
435  * term_cell_init() - Initialize a new cell
436  * @cell: cell to initialize
437  * @ch: character to set on the cell or TERM_CHAR_NULL
438  * @cwidth: character width of @ch
439  * @attr: attributes to set on the cell or NULL
440  * @age: age to set on the cell or TERM_AGE_NULL
441  *
442  * This initializes a new cell. The backing-memory of the cell must be allocated
443  * by the caller beforehand. The caller is responsible to destroy the cell via
444  * term_cell_destroy() before freeing the backing-memory.
445  *
446  * It is safe (and supported!) to use:
447  *   zero(*c);
448  * instead of:
449  *   term_cell_init(c, TERM_CHAR_NULL, NULL, TERM_AGE_NULL);
450  *
451  * Note that this call takes ownership of @ch. If you want to use it yourself
452  * after this call, you need to duplicate it before calling this.
453  */
454 static void term_cell_init(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) {
455         assert(cell);
456
457         cell->ch = ch;
458         cell->cwidth = cwidth;
459         cell->age = age;
460
461         if (attr)
462                 memcpy(&cell->attr, attr, sizeof(*attr));
463         else
464                 zero(cell->attr);
465 }
466
467 /**
468  * term_cell_destroy() - Destroy previously initialized cell
469  * @cell: cell to destroy or NULL
470  *
471  * This releases all resources associated with a cell. The backing memory is
472  * kept as-is. It's the responsibility of the caller to manage it.
473  *
474  * You must not call any other cell operations on this cell after this call
475  * returns. You must re-initialize the cell via term_cell_init() before you can
476  * use it again.
477  *
478  * If @cell is NULL, this is a no-op.
479  */
480 static void term_cell_destroy(term_cell *cell) {
481         if (!cell)
482                 return;
483
484         term_char_free(cell->ch);
485 }
486
487 /**
488  * term_cell_set() - Change contents of a cell
489  * @cell: cell to modify
490  * @ch: character to set on the cell or cell->ch
491  * @cwidth: character width of @ch or cell->cwidth
492  * @attr: attributes to set on the cell or NULL
493  * @age: age to set on the cell or cell->age
494  *
495  * This changes the contents of a cell. It can be used to change the character,
496  * attributes and age. To keep the current character, pass cell->ch as @ch. To
497  * reset the current attributes, pass NULL. To keep the current age, pass
498  * cell->age.
499  *
500  * This call takes ownership of @ch. You need to duplicate it first, in case you
501  * want to use it for your own purposes after this call.
502  *
503  * The cell must have been initialized properly before calling this. See
504  * term_cell_init().
505  */
506 static void term_cell_set(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) {
507         assert(cell);
508
509         if (!term_char_same(ch, cell->ch)) {
510                 term_char_free(cell->ch);
511                 cell->ch = ch;
512         }
513
514         cell->cwidth = cwidth;
515         cell->age = age;
516
517         if (attr)
518                 memcpy(&cell->attr, attr, sizeof(*attr));
519         else
520                 zero(cell->attr);
521 }
522
523 /**
524  * term_cell_append() - Append a combining-char to a cell
525  * @cell: cell to modify
526  * @ucs4: UCS-4 character to append to the cell
527  * @age: new age to set on the cell or cell->age
528  *
529  * This appends a combining-character to a cell. No validation of the UCS-4
530  * character is done, so this can be used to append any character. Additionally,
531  * this can update the age of the cell.
532  *
533  * The cell must have been initialized properly before calling this. See
534  * term_cell_init().
535  */
536 static void term_cell_append(term_cell *cell, uint32_t ucs4, term_age_t age) {
537         assert(cell);
538
539         cell->ch = term_char_merge(cell->ch, ucs4);
540         cell->age = age;
541 }
542
543 /**
544  * term_cell_init_n() - Initialize an array of cells
545  * @cells: pointer to an array of cells to initialize
546  * @n: number of cells
547  * @attr: attributes to set on all cells or NULL
548  * @age: age to set on all cells
549  *
550  * This is the same as term_cell_init() but initializes an array of cells.
551  * Furthermore, this always sets the character to TERM_CHAR_NULL.
552  * If you want to set a specific characters on all cells, you need to hard-code
553  * this loop and duplicate the character for each cell.
554  */
555 static void term_cell_init_n(term_cell *cells, unsigned int n, const term_attr *attr, term_age_t age) {
556         for ( ; n > 0; --n, ++cells)
557                 term_cell_init(cells, TERM_CHAR_NULL, 0, attr, age);
558 }
559
560 /**
561  * term_cell_destroy_n() - Destroy an array of cells
562  * @cells: pointer to an array of cells to destroy
563  * @n: number of cells
564  *
565  * This is the same as term_cell_destroy() but destroys an array of cells.
566  */
567 static void term_cell_destroy_n(term_cell *cells, unsigned int n) {
568         for ( ; n > 0; --n, ++cells)
569                 term_cell_destroy(cells);
570 }
571
572 /**
573  * term_cell_clear_n() - Clear contents of an array of cells
574  * @cells: pointer to an array of cells to modify
575  * @n: number of cells
576  * @attr: attributes to set on all cells or NULL
577  * @age: age to set on all cells
578  *
579  * This is the same as term_cell_set() but operates on an array of cells. Note
580  * that all characters are always set to TERM_CHAR_NULL, unlike term_cell_set()
581  * which takes the character as argument.
582  * If you want to set a specific characters on all cells, you need to hard-code
583  * this loop and duplicate the character for each cell.
584  */
585 static void term_cell_clear_n(term_cell *cells, unsigned int n, const term_attr *attr, term_age_t age) {
586         for ( ; n > 0; --n, ++cells)
587                 term_cell_set(cells, TERM_CHAR_NULL, 0, attr, age);
588 }
589
590 /**
591  * term_line_new() - Allocate a new line
592  * @out: place to store pointer to new line
593  *
594  * This allocates and initialized a new line. The line is unlinked and
595  * independent of any page. It can be used for any purpose. The initial
596  * cell-count is set to 0.
597  *
598  * The line has to be freed via term_line_free() once it's no longer needed.
599  *
600  * Returns: 0 on success, negative error code on failure.
601  */
602 int term_line_new(term_line **out) {
603         _term_line_free_ term_line *line = NULL;
604
605         assert_return(out, -EINVAL);
606
607         line = new0(term_line, 1);
608         if (!line)
609                 return -ENOMEM;
610
611         *out = line;
612         line = NULL;
613         return 0;
614 }
615
616 /**
617  * term_line_free() - Free a line
618  * @line: line to free or NULL
619  *
620  * This frees a line that was previously allocated via term_line_free(). All its
621  * cells are released, too.
622  *
623  * If @line is NULL, this is a no-op.
624  */
625 term_line *term_line_free(term_line *line) {
626         if (!line)
627                 return NULL;
628
629         term_cell_destroy_n(line->cells, line->n_cells);
630         free(line->cells);
631         free(line);
632
633         return NULL;
634 }
635
636 /**
637  * term_line_reserve() - Pre-allocate cells for a line
638  * @line: line to pre-allocate cells for
639  * @width: numbers of cells the line shall have pre-allocated
640  * @attr: attribute for all allocated cells or NULL
641  * @age: current age for all modifications
642  * @protect_width: width to protect from erasure
643  *
644  * This pre-allocates cells for this line. Please note that @width is the number
645  * of cells the line is guaranteed to have allocated after this call returns.
646  * It's not the number of cells that are added, neither is it the new width of
647  * the line.
648  *
649  * This function never frees memory. That is, reducing the line-width will
650  * always succeed, same is true for increasing the width to a previously set
651  * width.
652  *
653  * @attr and @age are used to initialize new cells. Additionally, any
654  * existing cell outside of the protected area specified by @protect_width are
655  * cleared and reset with @attr and @age.
656  *
657  * Returns: 0 on success, negative error code on failure.
658  */
659 int term_line_reserve(term_line *line, unsigned int width, const term_attr *attr, term_age_t age, unsigned int protect_width) {
660         unsigned int min_width;
661         term_cell *t;
662
663         assert_return(line, -EINVAL);
664
665         /* reset existing cells if required */
666         min_width = MIN(line->n_cells, width);
667         if (min_width > protect_width)
668                 term_cell_clear_n(line->cells + protect_width,
669                                   min_width - protect_width,
670                                   attr,
671                                   age);
672
673         /* allocate new cells if required */
674
675         if (width > line->n_cells) {
676                 t = realloc_multiply(line->cells, sizeof(*t), width);
677                 if (!t)
678                         return -ENOMEM;
679
680                 if (!attr && !age)
681                         memzero(t + line->n_cells,
682                                 sizeof(*t) * (width - line->n_cells));
683                 else
684                         term_cell_init_n(t + line->n_cells,
685                                          width - line->n_cells,
686                                          attr,
687                                          age);
688
689                 line->cells = t;
690                 line->n_cells = width;
691         }
692
693         line->fill = MIN(line->fill, protect_width);
694
695         return 0;
696 }
697
698 /**
699  * term_line_set_width() - Change width of a line
700  * @line: line to modify
701  * @width: new width
702  *
703  * This changes the actual width of a line. It is the caller's responsibility
704  * to use term_line_reserve() to make sure enough space is allocated. If @width
705  * is greater than the allocated size, it is cropped.
706  *
707  * This does not modify any cells. Use term_line_reserve() or term_line_erase()
708  * to clear any newly added cells.
709  *
710  * NOTE: The fill state is cropped at line->width. Therefore, if you increase
711  *       the line-width afterwards, but there is a multi-cell character at the
712  *       end of the line that got cropped, then the fill-state will _not_ be
713  *       adjusted.
714  *       This means, the fill-state always includes the cells up to the start
715  *       of the right-most character, but it might or might not cover it until
716  *       its end. This should be totally fine, though. You should never access
717  *       multi-cell tails directly, anyway.
718  */
719 void term_line_set_width(term_line *line, unsigned int width) {
720         assert(line);
721
722         if (width > line->n_cells)
723                 width = line->n_cells;
724
725         line->width = width;
726         line->fill = MIN(line->fill, width);
727 }
728
729 /**
730  * line_insert() - Insert characters and move existing cells to the right
731  * @from: position to insert cells at
732  * @num: number of cells to insert
733  * @head_char: character that is set on the first cell
734  * @head_cwidth: character-length of @head_char
735  * @attr: attribute for all inserted cells or NULL
736  * @age: current age for all modifications
737  *
738  * The INSERT operation (or writes with INSERT_MODE) writes data at a specific
739  * position on a line and shifts the existing cells to the right. Cells that are
740  * moved beyond the right hand border are discarded.
741  *
742  * This helper contains the actual INSERT implementation which is independent of
743  * the data written. It works on cells, not on characters. The first cell is set
744  * to @head_char, all others are reset to TERM_CHAR_NULL. See each caller for a
745  * more detailed description.
746  */
747 static inline void line_insert(term_line *line, unsigned int from, unsigned int num, term_char_t head_char, unsigned int head_cwidth, const term_attr *attr, term_age_t age) {
748         unsigned int i, rem, move;
749
750         if (from >= line->width)
751                 return;
752         if (from + num < from || from + num > line->width)
753                 num = line->width - from;
754         if (!num)
755                 return;
756
757         move = line->width - from - num;
758         rem = MIN(num, move);
759
760         if (rem > 0) {
761                 /*
762                  * Make room for @num cells; shift cells to the right if
763                  * required. @rem is the number of remaining cells that we will
764                  * knock off on the right and overwrite during the right shift.
765                  *
766                  * For INSERT_MODE, @num/@rem are usually 1 or 2, @move is 50%
767                  * of the line on average. Therefore, the actual move is quite
768                  * heavy and we can safely invalidate cells manually instead of
769                  * the whole line.
770                  * However, for INSERT operations, any parameters are
771                  * possible. But we cannot place any assumption on its usage
772                  * across applications, so we just handle it the same as
773                  * INSERT_MODE and do per-cell invalidation.
774                  */
775
776                 /* destroy cells that are knocked off on the right */
777                 term_cell_destroy_n(line->cells + line->width - rem, rem);
778
779                 /* move remaining bulk of cells */
780                 memmove(line->cells + from + num,
781                         line->cells + from,
782                         sizeof(*line->cells) * move);
783
784                 /* invalidate cells */
785                 for (i = 0; i < move; ++i)
786                         line->cells[from + num + i].age = age;
787
788                 /* initialize fresh head-cell */
789                 term_cell_init(line->cells + from,
790                                head_char,
791                                head_cwidth,
792                                attr,
793                                age);
794
795                 /* initialize fresh tail-cells */
796                 term_cell_init_n(line->cells + from + 1,
797                                  num - 1,
798                                  attr,
799                                  age);
800
801                 /* adjust fill-state */
802                 DISABLE_WARNING_SHADOW;
803                 line->fill = MIN(line->width,
804                                  MAX(line->fill + num,
805                                      from + num));
806                 REENABLE_WARNING;
807         } else {
808                 /* modify head-cell */
809                 term_cell_set(line->cells + from,
810                               head_char,
811                               head_cwidth,
812                               attr,
813                               age);
814
815                 /* reset tail-cells */
816                 term_cell_clear_n(line->cells + from + 1,
817                                   num - 1,
818                                   attr,
819                                   age);
820
821                 /* adjust fill-state */
822                 line->fill = line->width;
823         }
824 }
825
826 /**
827  * term_line_write() - Write to a single, specific cell
828  * @line: line to write to
829  * @pos_x: x-position of cell in @line to write to
830  * @ch: character to write to the cell
831  * @cwidth: character width of @ch
832  * @attr: attributes to set on the cell or NULL
833  * @age: current age for all modifications
834  * @insert_mode: true if INSERT-MODE is enabled
835  *
836  * This writes to a specific cell in a line. The cell is addressed by its
837  * X-position @pos_x. If that cell does not exist, this is a no-op.
838  *
839  * @ch and @attr are set on this cell.
840  *
841  * If @insert_mode is true, this inserts the character instead of overwriting
842  * existing data (existing data is now moved to the right before writing).
843  *
844  * This function is the low-level handler of normal writes to a terminal.
845  */
846 void term_line_write(term_line *line, unsigned int pos_x, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age, bool insert_mode) {
847         unsigned int len;
848
849         assert(line);
850
851         if (pos_x >= line->width)
852                 return;
853
854         len = MAX(1U, cwidth);
855         if (pos_x + len < pos_x || pos_x + len > line->width)
856                 len = line->width - pos_x;
857         if (!len)
858                 return;
859
860         if (insert_mode) {
861                 /* Use line_insert() to insert the character-head and fill
862                  * the remains with NULLs. */
863                 line_insert(line, pos_x, len, ch, cwidth, attr, age);
864         } else {
865                 /* modify head-cell */
866                 term_cell_set(line->cells + pos_x, ch, cwidth, attr, age);
867
868                 /* reset tail-cells */
869                 term_cell_clear_n(line->cells + pos_x + 1,
870                                   len - 1,
871                                   attr,
872                                   age);
873
874                 /* adjust fill-state */
875                 DISABLE_WARNING_SHADOW;
876                 line->fill = MIN(line->width,
877                                  MAX(line->fill,
878                                      pos_x + len));
879                 REENABLE_WARNING;
880         }
881 }
882
883 /**
884  * term_line_insert() - Insert empty cells
885  * @line: line to insert empty cells into
886  * @from: x-position where to insert cells
887  * @num: number of cells to insert
888  * @attr: attributes to set on the cells or NULL
889  * @age: current age for all modifications
890  *
891  * This inserts @num empty cells at position @from in line @line. All existing
892  * cells to the right are shifted to make room for the new cells. Cells that get
893  * pushed beyond the right hand border are discarded.
894  */
895 void term_line_insert(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age) {
896         /* use line_insert() to insert @num empty cells */
897         return line_insert(line, from, num, TERM_CHAR_NULL, 0, attr, age);
898 }
899
900 /**
901  * term_line_delete() - Delete cells from line
902  * @line: line to delete cells from
903  * @from: position to delete cells at
904  * @num: number of cells to delete
905  * @attr: attributes to set on any new cells
906  * @age: current age for all modifications
907  *
908  * Delete cells from a line. All cells to the right of the deleted cells are
909  * shifted to the left to fill the empty space. New cells appearing on the right
910  * hand border are cleared and initialized with @attr.
911  */
912 void term_line_delete(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age) {
913         unsigned int rem, move, i;
914
915         assert(line);
916
917         if (from >= line->width)
918                 return;
919         if (from + num < from || from + num > line->width)
920                 num = line->width - from;
921         if (!num)
922                 return;
923
924         /* destroy and move as many upfront as possible */
925         move = line->width - from - num;
926         rem = MIN(num, move);
927         if (rem > 0) {
928                 /* destroy to be removed cells */
929                 term_cell_destroy_n(line->cells + from, rem);
930
931                 /* move tail upfront */
932                 memmove(line->cells + from,
933                         line->cells + from + num,
934                         sizeof(*line->cells) * move);
935
936                 /* invalidate copied cells */
937                 for (i = 0; i < move; ++i)
938                         line->cells[from + i].age = age;
939
940                 /* initialize tail that was moved away */
941                 term_cell_init_n(line->cells + line->width - rem,
942                                  rem,
943                                  attr,
944                                  age);
945
946                 /* reset remaining cells in case the move was too small */
947                 if (num > move)
948                         term_cell_clear_n(line->cells + from + move,
949                                           num - move,
950                                           attr,
951                                           age);
952         } else {
953                 /* reset cells */
954                 term_cell_clear_n(line->cells + from,
955                                   num,
956                                   attr,
957                                   age);
958         }
959
960         /* adjust fill-state */
961         if (from + num < line->fill)
962                 line->fill -= num;
963         else if (from < line->fill)
964                 line->fill = from;
965 }
966
967 /**
968  * term_line_append_combchar() - Append combining char to existing cell
969  * @line: line to modify
970  * @pos_x: position of cell to append combining char to
971  * @ucs4: combining character to append
972  * @age: current age for all modifications
973  *
974  * Unicode allows trailing combining characters, which belong to the
975  * char in front of them. The caller is responsible of detecting
976  * combining characters and calling term_line_append_combchar() instead of
977  * term_line_write(). This simply appends the char to the correct cell then.
978  * If the cell is not in the visible area, this call is skipped.
979  *
980  * Note that control-sequences are not 100% compatible with combining
981  * characters as they require delayed parsing. However, we must handle
982  * control-sequences immediately. Therefore, there might be trailing
983  * combining chars that should be discarded by the parser.
984  * However, to prevent programming errors, we're also being pedantic
985  * here and discard weirdly placed combining chars. This prevents
986  * situations were invalid content is parsed into the terminal and you
987  * might end up with cells containing only combining chars.
988  *
989  * Long story short: To get combining-characters working with old-fashioned
990  * terminal-emulation, we parse them exclusively for direct cell-writes. Other
991  * combining-characters are usually simply discarded and ignored.
992  */
993 void term_line_append_combchar(term_line *line, unsigned int pos_x, uint32_t ucs4, term_age_t age) {
994         assert(line);
995
996         if (pos_x >= line->width)
997                 return;
998
999         /* Unused cell? Skip appending any combining chars then. */
1000         if (term_char_is_null(line->cells[pos_x].ch))
1001                 return;
1002
1003         term_cell_append(line->cells + pos_x, ucs4, age);
1004 }
1005
1006 /**
1007  * term_line_erase() - Erase parts of a line
1008  * @line: line to modify
1009  * @from: position to start the erase
1010  * @num: number of cells to erase
1011  * @attr: attributes to initialize erased cells with
1012  * @age: current age for all modifications
1013  * @keep_protected: true if protected cells should be kept
1014  *
1015  * This is the standard erase operation. It clears all cells in the targeted
1016  * area and re-initializes them. Cells to the right are not shifted left, you
1017  * must use DELETE to achieve that. Cells outside the visible area are skipped.
1018  *
1019  * If @keep_protected is true, protected cells will not be erased.
1020  */
1021 void term_line_erase(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age, bool keep_protected) {
1022         term_cell *cell;
1023         unsigned int i, last_protected;
1024
1025         assert(line);
1026
1027         if (from >= line->width)
1028                 return;
1029         if (from + num < from || from + num > line->width)
1030                 num = line->width - from;
1031         if (!num)
1032                 return;
1033
1034         last_protected = 0;
1035         for (i = 0; i < num; ++i) {
1036                 cell = line->cells + from + i;
1037                 if (keep_protected && cell->attr.protect) {
1038                         /* only count protected-cells inside the fill-region */
1039                         if (from + i < line->fill)
1040                                 last_protected = from + i;
1041
1042                         continue;
1043                 }
1044
1045                 term_cell_set(cell, TERM_CHAR_NULL, 0, attr, age);
1046         }
1047
1048         /* Adjust fill-state. This is a bit tricks, we can only adjust it in
1049          * case the erase-region starts inside the fill-region and ends at the
1050          * tail or beyond the fill-region. Otherwise, the current fill-state
1051          * stays as it was.
1052          * Furthermore, we must account for protected cells. The loop above
1053          * ensures that protected-cells are only accounted for if they're
1054          * inside the fill-region. */
1055         if (from < line->fill && from + num >= line->fill)
1056                 line->fill = MAX(from, last_protected);
1057 }
1058
1059 /**
1060  * term_line_reset() - Reset a line
1061  * @line: line to reset
1062  * @attr: attributes to initialize all cells with
1063  * @age: current age for all modifications
1064  *
1065  * This resets all visible cells of a line and sets their attributes and ages
1066  * to @attr and @age. This is equivalent to erasing a whole line via
1067  * term_line_erase().
1068  */
1069 void term_line_reset(term_line *line, const term_attr *attr, term_age_t age) {
1070         assert(line);
1071
1072         return term_line_erase(line, 0, line->width, attr, age, 0);
1073 }
1074
1075 /**
1076  * term_line_link() - Link line in front of a list
1077  * @line: line to link
1078  * @first: member pointing to first entry
1079  * @last: member pointing to last entry
1080  *
1081  * This links a line into a list of lines. The line is inserted at the front and
1082  * must not be linked, yet. See the TERM_LINE_LINK() macro for an easier usage of
1083  * this.
1084  */
1085 void term_line_link(term_line *line, term_line **first, term_line **last) {
1086         assert(line);
1087         assert(first);
1088         assert(last);
1089         assert(!line->lines_prev);
1090         assert(!line->lines_next);
1091
1092         line->lines_prev = NULL;
1093         line->lines_next = *first;
1094         if (*first)
1095                 (*first)->lines_prev = line;
1096         else
1097                 *last = line;
1098         *first = line;
1099 }
1100
1101 /**
1102  * term_line_link_tail() - Link line at tail of a list
1103  * @line: line to link
1104  * @first: member pointing to first entry
1105  * @last: member pointing to last entry
1106  *
1107  * Same as term_line_link() but links the line at the tail.
1108  */
1109 void term_line_link_tail(term_line *line, term_line **first, term_line **last) {
1110         assert(line);
1111         assert(first);
1112         assert(last);
1113         assert(!line->lines_prev);
1114         assert(!line->lines_next);
1115
1116         line->lines_next = NULL;
1117         line->lines_prev = *last;
1118         if (*last)
1119                 (*last)->lines_next = line;
1120         else
1121                 *first = line;
1122         *last = line;
1123 }
1124
1125 /**
1126  * term_line_unlink() - Unlink line from a list
1127  * @line: line to unlink
1128  * @first: member pointing to first entry
1129  * @last: member pointing to last entry
1130  *
1131  * This unlinks a previously linked line. See TERM_LINE_UNLINK() for an easier to
1132  * use macro.
1133  */
1134 void term_line_unlink(term_line *line, term_line **first, term_line **last) {
1135         assert(line);
1136         assert(first);
1137         assert(last);
1138
1139         if (line->lines_prev)
1140                 line->lines_prev->lines_next = line->lines_next;
1141         else
1142                 *first = line->lines_next;
1143         if (line->lines_next)
1144                 line->lines_next->lines_prev = line->lines_prev;
1145         else
1146                 *last = line->lines_prev;
1147
1148         line->lines_prev = NULL;
1149         line->lines_next = NULL;
1150 }
1151
1152 /**
1153  * term_page_new() - Allocate new page
1154  * @out: storage for pointer to new page
1155  *
1156  * Allocate a new page. The initial dimensions are 0/0.
1157  *
1158  * Returns: 0 on success, negative error code on failure.
1159  */
1160 int term_page_new(term_page **out) {
1161         _term_page_free_ term_page *page = NULL;
1162
1163         assert_return(out, -EINVAL);
1164
1165         page = new0(term_page, 1);
1166         if (!page)
1167                 return -ENOMEM;
1168
1169         *out = page;
1170         page = NULL;
1171         return 0;
1172 }
1173
1174 /**
1175  * term_page_free() - Free page
1176  * @page: page to free or NULL
1177  *
1178  * Free a previously allocated page and all associated data. If @page is NULL,
1179  * this is a no-op.
1180  *
1181  * Returns: NULL
1182  */
1183 term_page *term_page_free(term_page *page) {
1184         unsigned int i;
1185
1186         if (!page)
1187                 return NULL;
1188
1189         for (i = 0; i < page->n_lines; ++i)
1190                 term_line_free(page->lines[i]);
1191
1192         free(page->line_cache);
1193         free(page->lines);
1194         free(page);
1195
1196         return NULL;
1197 }
1198
1199 /**
1200  * term_page_get_cell() - Return pointer to requested cell
1201  * @page: page to operate on
1202  * @x: x-position of cell
1203  * @y: y-position of cell
1204  *
1205  * This returns a pointer to the cell at position @x/@y. You're free to modify
1206  * this cell as much as you like. However, once you call any other function on
1207  * the page, you must drop the pointer to the cell.
1208  *
1209  * Returns: Pointer to the cell or NULL if out of the visible area.
1210  */
1211 term_cell *term_page_get_cell(term_page *page, unsigned int x, unsigned int y) {
1212         assert_return(page, NULL);
1213
1214         if (x >= page->width)
1215                 return NULL;
1216         if (y >= page->height)
1217                 return NULL;
1218
1219         return &page->lines[y]->cells[x];
1220 }
1221
1222 /**
1223  * page_scroll_up() - Scroll up
1224  * @page: page to operate on
1225  * @new_width: width to use for any new line moved into the visible area
1226  * @num: number of lines to scroll up
1227  * @attr: attributes to set on new lines
1228  * @age: age to use for all modifications
1229  * @history: history to use for old lines or NULL
1230  *
1231  * This scrolls the scroll-region by @num lines. New lines are cleared and reset
1232  * with the given attributes. Old lines are moved into the history if non-NULL.
1233  * If a new line is allocated, moved from the history buffer or moved from
1234  * outside the visible region into the visible region, this call makes sure it
1235  * has at least @width cells allocated. If a possible memory-allocation fails,
1236  * the previous line is reused. This has the side effect, that it will not be
1237  * linked into the history buffer.
1238  *
1239  * If the scroll-region is empty, this is a no-op.
1240  */
1241 static void page_scroll_up(term_page *page, unsigned int new_width, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
1242         term_line *line, **cache;
1243         unsigned int i;
1244         int r;
1245
1246         assert(page);
1247
1248         if (num > page->scroll_num)
1249                 num = page->scroll_num;
1250         if (num < 1)
1251                 return;
1252
1253         /* Better safe than sorry: avoid under-allocating lines, even when
1254          * resizing. */
1255         new_width = MAX(new_width, page->width);
1256
1257         cache = page->line_cache;
1258
1259         /* Try moving lines into history and allocate new lines for each moved
1260          * line. In case allocation fails, or if we have no history, reuse the
1261          * line.
1262          * We keep the lines in the line-cache so we can safely move the
1263          * remaining lines around. */
1264         for (i = 0; i < num; ++i) {
1265                 line = page->lines[page->scroll_idx + i];
1266
1267                 r = -EAGAIN;
1268                 if (history) {
1269                         r = term_line_new(&cache[i]);
1270                         if (r >= 0) {
1271                                 r = term_line_reserve(cache[i],
1272                                                       new_width,
1273                                                       attr,
1274                                                       age,
1275                                                       0);
1276                                 if (r < 0)
1277                                         term_line_free(cache[i]);
1278                                 else
1279                                         term_line_set_width(cache[i], page->width);
1280                         }
1281                 }
1282
1283                 if (r >= 0) {
1284                         term_history_push(history, line);
1285                 } else {
1286                         cache[i] = line;
1287                         term_line_reset(line, attr, age);
1288                 }
1289         }
1290
1291         if (num < page->scroll_num) {
1292                 memmove(page->lines + page->scroll_idx,
1293                         page->lines + page->scroll_idx + num,
1294                         sizeof(*page->lines) * (page->scroll_num - num));
1295
1296                 /* update age of moved lines */
1297                 for (i = 0; i < page->scroll_num - num; ++i)
1298                         page->lines[page->scroll_idx + i]->age = age;
1299         }
1300
1301         /* copy remaining lines from cache; age is already updated */
1302         memcpy(page->lines + page->scroll_idx + page->scroll_num - num,
1303                cache,
1304                sizeof(*cache) * num);
1305
1306         /* update fill */
1307         page->scroll_fill -= MIN(page->scroll_fill, num);
1308 }
1309
1310 /**
1311  * page_scroll_down() - Scroll down
1312  * @page: page to operate on
1313  * @new_width: width to use for any new line moved into the visible area
1314  * @num: number of lines to scroll down
1315  * @attr: attributes to set on new lines
1316  * @age: age to use for all modifications
1317  * @history: history to use for new lines or NULL
1318  *
1319  * This scrolls the scroll-region by @num lines. New lines are retrieved from
1320  * the history or cleared if the history is empty or NULL.
1321  *
1322  * Usually, scroll-down implies that new lines are cleared. Therefore, you're
1323  * highly encouraged to set @history to NULL. However, if you resize a terminal,
1324  * you might want to include history-lines in the new area. In that case, you
1325  * should set @history to non-NULL.
1326  *
1327  * If a new line is allocated, moved from the history buffer or moved from
1328  * outside the visible region into the visible region, this call makes sure it
1329  * has at least @width cells allocated. If a possible memory-allocation fails,
1330  * the previous line is reused. This will have the side-effect that lines from
1331  * the history will not get visible on-screen but kept in history.
1332  *
1333  * If the scroll-region is empty, this is a no-op.
1334  */
1335 static void page_scroll_down(term_page *page, unsigned int new_width, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
1336         term_line *line, **cache, *t;
1337         unsigned int i, last_idx;
1338
1339         assert(page);
1340
1341         if (num > page->scroll_num)
1342                 num = page->scroll_num;
1343         if (num < 1)
1344                 return;
1345
1346         /* Better safe than sorry: avoid under-allocating lines, even when
1347          * resizing. */
1348         new_width = MAX(new_width, page->width);
1349
1350         cache = page->line_cache;
1351         last_idx = page->scroll_idx + page->scroll_num - 1;
1352
1353         /* Try pulling out lines from history; if history is empty or if no
1354          * history is given, we reuse the to-be-removed lines. Otherwise, those
1355          * lines are released. */
1356         for (i = 0; i < num; ++i) {
1357                 line = page->lines[last_idx - i];
1358
1359                 t = NULL;
1360                 if (history)
1361                         t = term_history_pop(history, new_width, attr, age);
1362
1363                 if (t) {
1364                         cache[num - 1 - i] = t;
1365                         term_line_free(line);
1366                 } else {
1367                         cache[num - 1 - i] = line;
1368                         term_line_reset(line, attr, age);
1369                 }
1370         }
1371
1372         if (num < page->scroll_num) {
1373                 memmove(page->lines + page->scroll_idx + num,
1374                         page->lines + page->scroll_idx,
1375                         sizeof(*page->lines) * (page->scroll_num - num));
1376
1377                 /* update age of moved lines */
1378                 for (i = 0; i < page->scroll_num - num; ++i)
1379                         page->lines[page->scroll_idx + num + i]->age = age;
1380         }
1381
1382         /* copy remaining lines from cache; age is already updated */
1383         memcpy(page->lines + page->scroll_idx,
1384                cache,
1385                sizeof(*cache) * num);
1386
1387         /* update fill; but only if there's already content in it */
1388         if (page->scroll_fill > 0)
1389                 page->scroll_fill = MIN(page->scroll_num,
1390                                         page->scroll_fill + num);
1391 }
1392
1393 /**
1394  * page_reserve() - Reserve page area
1395  * @page: page to modify
1396  * @cols: required columns (width)
1397  * @rows: required rows (height)
1398  * @attr: attributes for newly allocated cells
1399  * @age: age to set on any modified cells
1400  *
1401  * This allocates the required amount of lines and cells to guarantee that the
1402  * page has at least the demanded dimensions of @cols x @rows. Note that this
1403  * never shrinks the page-memory. We keep cells allocated for performance
1404  * reasons.
1405  *
1406  * Additionally to allocating lines, this also clears any newly added cells so
1407  * you can safely change the size afterwards without clearing new cells.
1408  *
1409  * Note that you must be careful what operations you call on the page between
1410  * page_reserve() and updating page->width/height. Any newly allocated line (or
1411  * shifted line) might not meet your new width/height expectations.
1412  *
1413  * Returns: 0 on success, negative error code on failure.
1414  */
1415 int term_page_reserve(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age) {
1416         _term_line_free_ term_line *line = NULL;
1417         unsigned int i, min_lines;
1418         term_line **t;
1419         int r;
1420
1421         assert_return(page, -EINVAL);
1422
1423         /*
1424          * First make sure the first MIN(page->n_lines, rows) lines have at
1425          * least the required width of @cols. This does not modify any visible
1426          * cells in the existing @page->width x @page->height area, therefore,
1427          * we can safely bail out afterwards in case anything else fails.
1428          * Note that lines in between page->height and page->n_lines might be
1429          * shorter than page->width. Hence, we need to resize them all, but we
1430          * can skip some of them for better performance.
1431          */
1432         min_lines = MIN(page->n_lines, rows);
1433         for (i = 0; i < min_lines; ++i) {
1434                 /* lines below page->height have at least page->width cells */
1435                 if (cols < page->width && i < page->height)
1436                         continue;
1437
1438                 r = term_line_reserve(page->lines[i],
1439                                       cols,
1440                                       attr,
1441                                       age,
1442                                       (i < page->height) ? page->width : 0);
1443                 if (r < 0)
1444                         return r;
1445         }
1446
1447         /*
1448          * We now know the first @min_lines lines have at least width @cols and
1449          * are prepared for resizing. We now only have to allocate any
1450          * additional lines below @min_lines in case @rows is greater than
1451          * page->n_lines.
1452          */
1453         if (rows > page->n_lines) {
1454                 t = realloc_multiply(page->lines, sizeof(*t), rows);
1455                 if (!t)
1456                         return -ENOMEM;
1457                 page->lines = t;
1458
1459                 t = realloc_multiply(page->line_cache, sizeof(*t), rows);
1460                 if (!t)
1461                         return -ENOMEM;
1462                 page->line_cache = t;
1463
1464                 while (page->n_lines < rows) {
1465                         r = term_line_new(&line);
1466                         if (r < 0)
1467                                 return r;
1468
1469                         r = term_line_reserve(line, cols, attr, age, 0);
1470                         if (r < 0)
1471                                 return r;
1472
1473                         page->lines[page->n_lines++] = line;
1474                         line = NULL;
1475                 }
1476         }
1477
1478         return 0;
1479 }
1480
1481 /**
1482  * term_page_resize() - Resize page
1483  * @page: page to modify
1484  * @cols: number of columns (width)
1485  * @rows: number of rows (height)
1486  * @attr: attributes for newly allocated cells
1487  * @age: age to set on any modified cells
1488  * @history: history buffer to use for new/old lines or NULL
1489  *
1490  * This changes the visible dimensions of a page. You must have called
1491  * term_page_reserve() beforehand, otherwise, this will fail.
1492  *
1493  * Returns: 0 on success, negative error code on failure.
1494  */
1495 void term_page_resize(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age, term_history *history) {
1496         unsigned int i, num, empty, max, old_height;
1497         term_line *line;
1498
1499         assert(page);
1500         assert(page->n_lines >= rows);
1501
1502         old_height = page->height;
1503
1504         if (rows < old_height) {
1505                 /*
1506                  * If we decrease the terminal-height, we emulate a scroll-up.
1507                  * This way, existing data from the scroll-area is moved into
1508                  * the history, making space at the bottom to reduce the screen
1509                  * height. In case the scroll-fill indicates empty lines, we
1510                  * reduce the amount of scrolled lines.
1511                  * Once scrolled, we have to move the lower margin from below
1512                  * the scroll area up so it is preserved.
1513                  */
1514
1515                 /* move lines to history if scroll region is filled */
1516                 num = old_height - rows;
1517                 empty = page->scroll_num - page->scroll_fill;
1518                 if (num > empty)
1519                         page_scroll_up(page,
1520                                        cols,
1521                                        num - empty,
1522                                        attr,
1523                                        age,
1524                                        history);
1525
1526                 /* move lower margin up; drop its lines if not enough space */
1527                 num = LESS_BY(old_height, page->scroll_idx + page->scroll_num);
1528                 max = LESS_BY(rows, page->scroll_idx);
1529                 num = MIN(num, max);
1530                 if (num > 0) {
1531                         unsigned int top, bottom;
1532
1533                         top = rows - num;
1534                         bottom = page->scroll_idx + page->scroll_num;
1535
1536                         /* might overlap; must run topdown, not bottomup */
1537                         for (i = 0; i < num; ++i) {
1538                                 line = page->lines[top + i];
1539                                 page->lines[top + i] = page->lines[bottom + i];
1540                                 page->lines[bottom + i] = line;
1541                         }
1542                 }
1543
1544                 /* update vertical extents */
1545                 page->height = rows;
1546                 page->scroll_idx = MIN(page->scroll_idx, rows);
1547                 page->scroll_num -= MIN(page->scroll_num, old_height - rows);
1548                 /* fill is already up-to-date or 0 due to scroll-up */
1549         } else if (rows > old_height) {
1550                 /*
1551                  * If we increase the terminal-height, we emulate a scroll-down
1552                  * and fetch new lines from the history.
1553                  * New lines are always accounted to the scroll-region. Thus we
1554                  * have to preserve the lower margin first, by moving it down.
1555                  */
1556
1557                 /* move lower margin down */
1558                 num = LESS_BY(old_height, page->scroll_idx + page->scroll_num);
1559                 if (num > 0) {
1560                         unsigned int top, bottom;
1561
1562                         top = page->scroll_idx + page->scroll_num;
1563                         bottom = top + (rows - old_height);
1564
1565                         /* might overlap; must run bottomup, not topdown */
1566                         for (i = num; i-- > 0; ) {
1567                                 line = page->lines[top + i];
1568                                 page->lines[top + i] = page->lines[bottom + i];
1569                                 page->lines[bottom + i] = line;
1570                         }
1571                 }
1572
1573                 /* update vertical extents */
1574                 page->height = rows;
1575                 page->scroll_num = MIN(LESS_BY(rows, page->scroll_idx),
1576                                        page->scroll_num + (rows - old_height));
1577
1578                 /* check how many lines can be received from history */
1579                 if (history)
1580                         num = term_history_peek(history,
1581                                                 rows - old_height,
1582                                                 cols,
1583                                                 attr,
1584                                                 age);
1585                 else
1586                         num = 0;
1587
1588                 /* retrieve new lines from history if available */
1589                 if (num > 0)
1590                         page_scroll_down(page,
1591                                          cols,
1592                                          num,
1593                                          attr,
1594                                          age,
1595                                          history);
1596         }
1597
1598         /* set horizontal extents */
1599         page->width = cols;
1600         for (i = 0; i < page->height; ++i)
1601                 term_line_set_width(page->lines[i], cols);
1602 }
1603
1604 /**
1605  * term_page_write() - Write to a single cell
1606  * @page: page to operate on
1607  * @pos_x: x-position of cell to write to
1608  * @pos_y: y-position of cell to write to
1609  * @ch: character to write
1610  * @cwidth: character-width of @ch
1611  * @attr: attributes to set on the cell or NULL
1612  * @age: age to use for all modifications
1613  * @insert_mode: true if INSERT-MODE is enabled
1614  *
1615  * This writes a character to a specific cell. If the cell is beyond bounds,
1616  * this is a no-op. @attr and @age are used to update the cell. @flags can be
1617  * used to alter the behavior of this function.
1618  *
1619  * This is a wrapper around term_line_write().
1620  *
1621  * This call does not wrap around lines. That is, this only operates on a single
1622  * line.
1623  */
1624 void term_page_write(term_page *page, unsigned int pos_x, unsigned int pos_y, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age, bool insert_mode) {
1625         assert(page);
1626
1627         if (pos_y >= page->height)
1628                 return;
1629
1630         term_line_write(page->lines[pos_y], pos_x, ch, cwidth, attr, age, insert_mode);
1631 }
1632
1633 /**
1634  * term_page_insert_cells() - Insert cells into a line
1635  * @page: page to operate on
1636  * @from_x: x-position where to insert new cells
1637  * @from_y: y-position where to insert new cells
1638  * @num: number of cells to insert
1639  * @attr: attributes to set on new cells or NULL
1640  * @age: age to use for all modifications
1641  *
1642  * This inserts new cells into a given line. This is a wrapper around
1643  * term_line_insert().
1644  *
1645  * This call does not wrap around lines. That is, this only operates on a single
1646  * line.
1647  */
1648 void term_page_insert_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age) {
1649         assert(page);
1650
1651         if (from_y >= page->height)
1652                 return;
1653
1654         term_line_insert(page->lines[from_y], from_x, num, attr, age);
1655 }
1656
1657 /**
1658  * term_page_delete_cells() - Delete cells from a line
1659  * @page: page to operate on
1660  * @from_x: x-position where to delete cells
1661  * @from_y: y-position where to delete cells
1662  * @num: number of cells to delete
1663  * @attr: attributes to set on new cells or NULL
1664  * @age: age to use for all modifications
1665  *
1666  * This deletes cells from a given line. This is a wrapper around
1667  * term_line_delete().
1668  *
1669  * This call does not wrap around lines. That is, this only operates on a single
1670  * line.
1671  */
1672 void term_page_delete_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age) {
1673         assert(page);
1674
1675         if (from_y >= page->height)
1676                 return;
1677
1678         term_line_delete(page->lines[from_y], from_x, num, attr, age);
1679 }
1680
1681 /**
1682  * term_page_append_combchar() - Append combining-character to a cell
1683  * @page: page to operate on
1684  * @pos_x: x-position of target cell
1685  * @pos_y: y-position of target cell
1686  * @ucs4: combining character to append
1687  * @age: age to use for all modifications
1688  *
1689  * This appends a combining-character to a specific cell. This is a wrapper
1690  * around term_line_append_combchar().
1691  */
1692 void term_page_append_combchar(term_page *page, unsigned int pos_x, unsigned int pos_y, uint32_t ucs4, term_age_t age) {
1693         assert(page);
1694
1695         if (pos_y >= page->height)
1696                 return;
1697
1698         term_line_append_combchar(page->lines[pos_y], pos_x, ucs4, age);
1699 }
1700
1701 /**
1702  * term_page_erase() - Erase parts of a page
1703  * @page: page to operate on
1704  * @from_x: x-position where to start erasure (inclusive)
1705  * @from_y: y-position where to start erasure (inclusive)
1706  * @to_x: x-position where to stop erasure (inclusive)
1707  * @to_y: y-position where to stop erasure (inclusive)
1708  * @attr: attributes to set on cells
1709  * @age: age to use for all modifications
1710  * @keep_protected: true if protected cells should be kept
1711  *
1712  * This erases all cells starting at @from_x/@from_y up to @to_x/@to_y. Note
1713  * that this wraps around line-boundaries so lines between @from_y and @to_y
1714  * are cleared entirely.
1715  *
1716  * Lines outside the visible area are left untouched.
1717  */
1718 void term_page_erase(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int to_x, unsigned int to_y, const term_attr *attr, term_age_t age, bool keep_protected) {
1719         unsigned int i, from, to;
1720
1721         assert(page);
1722
1723         for (i = from_y; i <= to_y && i < page->height; ++i) {
1724                 from = 0;
1725                 to = page->width;
1726
1727                 if (i == from_y)
1728                         from = from_x;
1729                 if (i == to_y)
1730                         to = to_x;
1731
1732                 term_line_erase(page->lines[i],
1733                                 from,
1734                                 LESS_BY(to, from),
1735                                 attr,
1736                                 age,
1737                                 keep_protected);
1738         }
1739 }
1740
1741 /**
1742  * term_page_reset() - Reset page
1743  * @page: page to modify
1744  * @attr: attributes to set on cells
1745  * @age: age to use for all modifications
1746  *
1747  * This erases the whole visible page. See term_page_erase().
1748  */
1749 void term_page_reset(term_page *page, const term_attr *attr, term_age_t age) {
1750         assert(page);
1751
1752         return term_page_erase(page,
1753                                0, 0,
1754                                page->width - 1, page->height - 1,
1755                                attr,
1756                                age,
1757                                0);
1758 }
1759
1760 /**
1761  * term_page_set_scroll_region() - Set scroll region
1762  * @page: page to operate on
1763  * @idx: start-index of scroll region
1764  * @num: number of lines in scroll region
1765  *
1766  * This sets the scroll region of a page. Whenever an operation needs to scroll
1767  * lines, it scrolls them inside of that region. Lines outside the region are
1768  * left untouched. In case a scroll-operation is targeted outside of this
1769  * region, it will implicitly get a scroll-region of only one line (i.e., no
1770  * scroll region at all).
1771  *
1772  * Note that the scroll-region is clipped to the current page-extents. Growing
1773  * or shrinking the page always accounts new/old lines to the scroll region and
1774  * moves top/bottom margins accordingly so they're preserved.
1775  */
1776 void term_page_set_scroll_region(term_page *page, unsigned int idx, unsigned int num) {
1777         assert(page);
1778
1779         if (page->height < 1) {
1780                 page->scroll_idx = 0;
1781                 page->scroll_num = 0;
1782         } else {
1783                 page->scroll_idx = MIN(idx, page->height - 1);
1784                 page->scroll_num = MIN(num, page->height - page->scroll_idx);
1785         }
1786 }
1787
1788 /**
1789  * term_page_scroll_up() - Scroll up
1790  * @page: page to operate on
1791  * @num: number of lines to scroll up
1792  * @attr: attributes to set on new lines
1793  * @age: age to use for all modifications
1794  * @history: history to use for old lines or NULL
1795  *
1796  * This scrolls the scroll-region by @num lines. New lines are cleared and reset
1797  * with the given attributes. Old lines are moved into the history if non-NULL.
1798  *
1799  * If the scroll-region is empty, this is a no-op.
1800  */
1801 void term_page_scroll_up(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
1802         page_scroll_up(page, page->width, num, attr, age, history);
1803 }
1804
1805 /**
1806  * term_page_scroll_down() - Scroll down
1807  * @page: page to operate on
1808  * @num: number of lines to scroll down
1809  * @attr: attributes to set on new lines
1810  * @age: age to use for all modifications
1811  * @history: history to use for new lines or NULL
1812  *
1813  * This scrolls the scroll-region by @num lines. New lines are retrieved from
1814  * the history or cleared if the history is empty or NULL.
1815  *
1816  * Usually, scroll-down implies that new lines are cleared. Therefore, you're
1817  * highly encouraged to set @history to NULL. However, if you resize a terminal,
1818  * you might want to include history-lines in the new area. In that case, you
1819  * should set @history to non-NULL.
1820  *
1821  * If the scroll-region is empty, this is a no-op.
1822  */
1823 void term_page_scroll_down(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
1824         page_scroll_down(page, page->width, num, attr, age, history);
1825 }
1826
1827 /**
1828  * term_page_insert_lines() - Insert new lines
1829  * @page: page to operate on
1830  * @pos_y: y-position where to insert new lines
1831  * @num: number of lines to insert
1832  * @attr: attributes to set on new lines
1833  * @age: age to use for all modifications
1834  *
1835  * This inserts @num new lines at position @pos_y. If @pos_y is beyond
1836  * boundaries or @num is 0, this is a no-op.
1837  * All lines below @pos_y are moved down to make space for the new lines. Lines
1838  * on the bottom are dropped. Note that this only moves lines above or inside
1839  * the scroll-region. If @pos_y is below the scroll-region, a scroll-region of
1840  * one line is implied (which means the line is simply cleared).
1841  */
1842 void term_page_insert_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age) {
1843         unsigned int scroll_idx, scroll_num;
1844
1845         assert(page);
1846
1847         if (pos_y >= page->height)
1848                 return;
1849         if (num >= page->height)
1850                 num = page->height;
1851
1852         /* remember scroll-region */
1853         scroll_idx = page->scroll_idx;
1854         scroll_num = page->scroll_num;
1855
1856         /* set scroll-region temporarily so we can reuse scroll_down() */
1857         {
1858                 page->scroll_idx = pos_y;
1859                 if (pos_y >= scroll_idx + scroll_num)
1860                         page->scroll_num = 1;
1861                 else if (pos_y >= scroll_idx)
1862                         page->scroll_num -= pos_y - scroll_idx;
1863                 else
1864                         page->scroll_num += scroll_idx - pos_y;
1865
1866                 term_page_scroll_down(page, num, attr, age, NULL);
1867         }
1868
1869         /* reset scroll-region */
1870         page->scroll_idx = scroll_idx;
1871         page->scroll_num = scroll_num;
1872 }
1873
1874 /**
1875  * term_page_delete_lines() - Delete lines
1876  * @page: page to operate on
1877  * @pos_y: y-position where to delete lines
1878  * @num: number of lines to delete
1879  * @attr: attributes to set on new lines
1880  * @age: age to use for all modifications
1881  *
1882  * This deletes @num lines at position @pos_y. If @pos_y is beyond boundaries or
1883  * @num is 0, this is a no-op.
1884  * All lines below @pos_y are moved up into the newly made space. New lines
1885  * on the bottom are clear. Note that this only moves lines above or inside
1886  * the scroll-region. If @pos_y is below the scroll-region, a scroll-region of
1887  * one line is implied (which means the line is simply cleared).
1888  */
1889 void term_page_delete_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age) {
1890         unsigned int scroll_idx, scroll_num;
1891
1892         assert(page);
1893
1894         if (pos_y >= page->height)
1895                 return;
1896         if (num >= page->height)
1897                 num = page->height;
1898
1899         /* remember scroll-region */
1900         scroll_idx = page->scroll_idx;
1901         scroll_num = page->scroll_num;
1902
1903         /* set scroll-region temporarily so we can reuse scroll_up() */
1904         {
1905                 page->scroll_idx = pos_y;
1906                 if (pos_y >= scroll_idx + scroll_num)
1907                         page->scroll_num = 1;
1908                 else if (pos_y > scroll_idx)
1909                         page->scroll_num -= pos_y - scroll_idx;
1910                 else
1911                         page->scroll_num += scroll_idx - pos_y;
1912
1913                 term_page_scroll_up(page, num, attr, age, NULL);
1914         }
1915
1916         /* reset scroll-region */
1917         page->scroll_idx = scroll_idx;
1918         page->scroll_num = scroll_num;
1919 }
1920
1921 /**
1922  * term_history_new() - Create new history object
1923  * @out: storage for pointer to new history
1924  *
1925  * Create a new history object. Histories are used to store scrollback-lines
1926  * from VTE pages. You're highly recommended to set a history-limit on
1927  * history->max_lines and trim it via term_history_trim(), otherwise history
1928  * allocations are unlimited.
1929  *
1930  * Returns: 0 on success, negative error code on failure.
1931  */
1932 int term_history_new(term_history **out) {
1933         _term_history_free_ term_history *history = NULL;
1934
1935         assert_return(out, -EINVAL);
1936
1937         history = new0(term_history, 1);
1938         if (!history)
1939                 return -ENOMEM;
1940
1941         history->max_lines = 4096;
1942
1943         *out = history;
1944         history = NULL;
1945         return 0;
1946 }
1947
1948 /**
1949  * term_history_free() - Free history
1950  * @history: history to free
1951  *
1952  * Clear and free history. You must not access the object afterwards.
1953  *
1954  * Returns: NULL
1955  */
1956 term_history *term_history_free(term_history *history) {
1957         if (!history)
1958                 return NULL;
1959
1960         term_history_clear(history);
1961         free(history);
1962         return NULL;
1963 }
1964
1965 /**
1966  * term_history_clear() - Clear history
1967  * @history: history to clear
1968  *
1969  * Remove all linked lines from a history and reset it to its initial state.
1970  */
1971 void term_history_clear(term_history *history) {
1972         return term_history_trim(history, 0);
1973 }
1974
1975 /**
1976  * term_history_trim() - Trim history
1977  * @history: history to trim
1978  * @max: maximum number of lines to be left in history
1979  *
1980  * This removes lines from the history until it is smaller than @max. Lines are
1981  * removed from the top.
1982  */
1983 void term_history_trim(term_history *history, unsigned int max) {
1984         term_line *line;
1985
1986         if (!history)
1987                 return;
1988
1989         while (history->n_lines > max && (line = history->lines_first)) {
1990                 TERM_LINE_UNLINK(line, history);
1991                 term_line_free(line);
1992                 --history->n_lines;
1993         }
1994 }
1995
1996 /**
1997  * term_history_push() - Push line into history
1998  * @history: history to work on
1999  * @line: line to push into history
2000  *
2001  * This pushes a line into the given history. It is linked at the tail. In case
2002  * the history is limited, the top-most line might be freed.
2003  */
2004 void term_history_push(term_history *history, term_line *line) {
2005         assert(history);
2006         assert(line);
2007
2008         TERM_LINE_LINK_TAIL(line, history);
2009         if (history->max_lines > 0 && history->n_lines >= history->max_lines) {
2010                 line = history->lines_first;
2011                 TERM_LINE_UNLINK(line, history);
2012                 term_line_free(line);
2013         } else {
2014                 ++history->n_lines;
2015         }
2016 }
2017
2018 /**
2019  * term_history_pop() - Retrieve last line from history
2020  * @history: history to work on
2021  * @new_width: width to reserve and set on the line
2022  * @attr: attributes to use for cell reservation
2023  * @age: age to use for cell reservation
2024  *
2025  * This unlinks the last linked line of the history and returns it. This also
2026  * makes sure the line has the given width pre-allocated (see
2027  * term_line_reserve()). If the pre-allocation fails, this returns NULL, so it
2028  * is treated like there's no line in history left. This simplifies
2029  * history-handling on the caller's side in case of allocation errors. No need
2030  * to throw lines away just because the reservation failed. We can keep them in
2031  * history safely, and make them available as scrollback.
2032  *
2033  * Returns: Line from history or NULL
2034  */
2035 term_line *term_history_pop(term_history *history, unsigned int new_width, const term_attr *attr, term_age_t age) {
2036         term_line *line;
2037         int r;
2038
2039         assert_return(history, NULL);
2040
2041         line = history->lines_last;
2042         if (!line)
2043                 return NULL;
2044
2045         r = term_line_reserve(line, new_width, attr, age, line->width);
2046         if (r < 0)
2047                 return NULL;
2048
2049         term_line_set_width(line, new_width);
2050         TERM_LINE_UNLINK(line, history);
2051         --history->n_lines;
2052
2053         return line;
2054 }
2055
2056 /**
2057  * term_history_peek() - Return number of available history-lines
2058  * @history: history to work on
2059  * @max: maximum number of lines to look at
2060  * @reserve_width: width to reserve on the line
2061  * @attr: attributes to use for cell reservation
2062  * @age: age to use for cell reservation
2063  *
2064  * This returns the number of available lines in the history given as @history.
2065  * It returns at most @max. For each line that is looked at, the line is
2066  * verified to have at least @reserve_width cells. Valid cells are preserved,
2067  * new cells are initialized with @attr and @age. In case an allocation fails,
2068  * we bail out and return the number of lines that are valid so far.
2069  *
2070  * Usually, this function should be used before running a loop on
2071  * term_history_pop(). This function guarantees that term_history_pop() (with
2072  * the same arguments) will succeed at least the returned number of times.
2073  *
2074  * Returns: Number of valid lines that can be received via term_history_pop().
2075  */
2076 unsigned int term_history_peek(term_history *history, unsigned int max, unsigned int reserve_width, const term_attr *attr, term_age_t age) {
2077         unsigned int num;
2078         term_line *line;
2079         int r;
2080
2081         assert(history);
2082
2083         num = 0;
2084         line = history->lines_last;
2085
2086         while (num < max && line) {
2087                 r = term_line_reserve(line, reserve_width, attr, age, line->width);
2088                 if (r < 0)
2089                         break;
2090
2091                 ++num;
2092                 line = line->lines_prev;
2093         }
2094
2095         return num;
2096 }