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