chiark / gitweb /
bfff3b1719bf5769cd48b471e7b28277f518259e
[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  * sorrounding 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
67 #include <stdbool.h>
68 #include <stdint.h>
69 #include <stdlib.h>
70 #include <wchar.h>
71 #include "macro.h"
72 #include "term-internal.h"
73 #include "util.h"
74
75 /* maximum UCS-4 character */
76 #define CHAR_UCS4_MAX (0x10ffff)
77 /* mask for valid UCS-4 characters (21bit) */
78 #define CHAR_UCS4_MASK (0x1fffff)
79 /* UCS-4 replacement character */
80 #define CHAR_UCS4_REPLACEMENT (0xfffd)
81
82 /* real storage behind "term_char_t" in case it's not packed */
83 typedef struct term_character {
84         uint8_t n;
85         uint32_t codepoints[];
86 } term_character;
87
88 /*
89  * char_pack() takes 3 UCS-4 values and packs them into a term_char_t object.
90  * Note that UCS-4 chars only take 21 bits, so we still have the LSB as marker.
91  * We set it to 1 so others can distinguish it from pointers.
92  */
93 static inline term_char_t char_pack(uint32_t v1, uint32_t v2, uint32_t v3) {
94         uint64_t packed, u1, u2, u3;
95
96         u1 = v1;
97         u2 = v2;
98         u3 = v3;
99
100         packed = 0x01;
101         packed |= (u1 & (uint64_t)CHAR_UCS4_MASK) << 43;
102         packed |= (u2 & (uint64_t)CHAR_UCS4_MASK) << 22;
103         packed |= (u3 & (uint64_t)CHAR_UCS4_MASK) <<  1;
104
105         return TERM_CHAR_INIT(packed);
106 }
107
108 #define char_pack1(_v1) char_pack2((_v1), CHAR_UCS4_MAX + 1)
109 #define char_pack2(_v1, _v2) char_pack3((_v1), (_v2), CHAR_UCS4_MAX + 1)
110 #define char_pack3(_v1, _v2, _v3) char_pack((_v1), (_v2), (_v3))
111
112 /*
113  * char_unpack() is the inverse of char_pack(). It extracts the 3 stored UCS-4
114  * characters and returns them. Note that this does not validate the passed
115  * term_char_t. That's the responsibility of the caller.
116  * This returns the number of characters actually packed. This obviously is a
117  * number between 0 and 3 (inclusive).
118  */
119 static inline uint8_t char_unpack(term_char_t packed, uint32_t *out_v1, uint32_t *out_v2, uint32_t *out_v3) {
120         uint32_t v1, v2, v3;
121
122         v1 = (packed._value >> 43) & (uint64_t)CHAR_UCS4_MASK;
123         v2 = (packed._value >> 22) & (uint64_t)CHAR_UCS4_MASK;
124         v3 = (packed._value >>  1) & (uint64_t)CHAR_UCS4_MASK;
125
126         if (out_v1)
127                 *out_v1 = v1;
128         if (out_v2)
129                 *out_v2 = v2;
130         if (out_v3)
131                 *out_v3 = v3;
132
133         return (v1 > CHAR_UCS4_MAX) ? 0 :
134               ((v2 > CHAR_UCS4_MAX) ? 1 :
135               ((v3 > CHAR_UCS4_MAX) ? 2 :
136                                       3));
137 }
138
139 /* cast a term_char_t to a term_character* */
140 static inline term_character *char_to_ptr(term_char_t ch) {
141         return (term_character*)(unsigned long)ch._value;
142 }
143
144 /* cast a term_character* to a term_char_t */
145 static inline term_char_t char_from_ptr(term_character *c) {
146         return TERM_CHAR_INIT((unsigned long)c);
147 }
148
149 /*
150  * char_alloc() allocates a properly aligned term_character object and returns
151  * a pointer to it. NULL is returned on allocation errors. The object will have
152  * enough room for @n following UCS-4 chars.
153  * Note that we allocate (n+1) characters and set the last one to 0 in case
154  * anyone prints this string for debugging.
155  */
156 static term_character *char_alloc(uint8_t n) {
157         term_character *c;
158         int r;
159
160         r = posix_memalign((void**)&c,
161                            MAX(sizeof(void*), (size_t)2),
162                            sizeof(*c) + sizeof(*c->codepoints) * (n + 1));
163         if (r)
164                 return NULL;
165
166         c->n = n;
167         c->codepoints[n] = 0;
168
169         return c;
170 }
171
172 /*
173  * char_free() frees the memory allocated via char_alloc(). It is safe to call
174  * this on any term_char_t, only allocated characters are freed.
175  */
176 static inline void char_free(term_char_t ch) {
177         if (term_char_is_allocated(ch))
178                 free(char_to_ptr(ch));
179 }
180
181 /*
182  * This appends @append_ucs4 to the existing character @base and returns
183  * it as a new character. In case that's not possible, @base is returned. The
184  * caller can use term_char_same() to test whether the returned character was
185  * freshly allocated or not.
186  */
187 static term_char_t char_build(term_char_t base, uint32_t append_ucs4) {
188         /* soft-limit for combining-chars; hard-limit is currently 255 */
189         const size_t climit = 64;
190         term_character *c;
191         uint32_t buf[3], *t;
192         uint8_t n;
193
194         /* ignore invalid UCS-4 */
195         if (append_ucs4 > CHAR_UCS4_MAX)
196                 return base;
197
198         if (term_char_is_null(base)) {
199                 return char_pack1(append_ucs4);
200         } else if (!term_char_is_allocated(base)) {
201                 /* unpack and try extending the packed character */
202                 n = char_unpack(base, &buf[0], &buf[1], &buf[2]);
203
204                 switch (n) {
205                 case 0:
206                         return char_pack1(append_ucs4);
207                 case 1:
208                         if (climit < 2)
209                                 return base;
210
211                         return char_pack2(buf[0], append_ucs4);
212                 case 2:
213                         if (climit < 3)
214                                 return base;
215
216                         return char_pack3(buf[0], buf[1], append_ucs4);
217                 default:
218                         /* fallthrough */
219                         break;
220                 }
221
222                 /* already fully packed, we need to allocate a new one */
223                 t = buf;
224         } else {
225                 /* already an allocated type, we need to allocate a new one */
226                 c = char_to_ptr(base);
227                 t = c->codepoints;
228                 n = c->n;
229         }
230
231         /* bail out if soft-limit is reached */
232         if (n >= climit)
233                 return base;
234
235         /* allocate new char */
236         c = char_alloc(n + 1);
237         if (!c)
238                 return base;
239
240         memcpy(c->codepoints, t, sizeof(*t) * n);
241         c->codepoints[n] = append_ucs4;
242
243         return char_from_ptr(c);
244 }
245
246 /**
247  * term_char_set() - Reset character to a single UCS-4 character
248  * @previous: term-char to reset
249  * @append_ucs4: UCS-4 char to set
250  *
251  * This frees all resources in @previous and re-initializes it to @append_ucs4.
252  * The new char is returned.
253  *
254  * Usually, this is used like this:
255  *   obj->ch = term_char_set(obj->ch, ucs4);
256  *
257  * Returns: The previous character reset to @append_ucs4.
258  */
259 term_char_t term_char_set(term_char_t previous, uint32_t append_ucs4) {
260         char_free(previous);
261         return char_build(TERM_CHAR_NULL, append_ucs4);
262 }
263
264 /**
265  * term_char_merge() - Merge UCS-4 char at the end of an existing char
266  * @base: existing term-char
267  * @append_ucs4: UCS-4 character to append
268  *
269  * This appends @append_ucs4 to @base and returns the result. @base is
270  * invalidated by this function and must no longer be used. The returned value
271  * replaces the old one.
272  *
273  * Usually, this is used like this:
274  *   obj->ch = term_char_merge(obj->ch, ucs4);
275  *
276  * Returns: The new merged character.
277  */
278 term_char_t term_char_merge(term_char_t base, uint32_t append_ucs4) {
279         term_char_t ch;
280
281         ch = char_build(base, append_ucs4);
282         if (!term_char_same(ch, base))
283                 term_char_free(base);
284
285         return ch;
286 }
287
288 /**
289  * term_char_dup() - Duplicate character
290  * @ch: character to duplicate
291  *
292  * This duplicates a term-character. In case the character is not allocated,
293  * nothing is done. Otherwise, the underlying memory is copied and returned. You
294  * need to call term_char_free() on the returned character to release it again.
295  * On allocation errors, a replacement character is returned. Therefore, the
296  * caller can safely assume that this function always succeeds.
297  *
298  * Returns: The duplicated term-character.
299  */
300 term_char_t term_char_dup(term_char_t ch) {
301         term_character *c, *newc;
302
303         if (!term_char_is_allocated(ch))
304                 return ch;
305
306         c = char_to_ptr(ch);
307         newc = char_alloc(c->n);
308         if (!newc)
309                 return char_pack1(CHAR_UCS4_REPLACEMENT);
310
311         memcpy(newc->codepoints, c->codepoints, sizeof(*c->codepoints) * c->n);
312         return char_from_ptr(newc);
313 }
314
315 /**
316  * term_char_dup_append() - Duplicate tsm-char with UCS-4 character appended
317  * @base: existing term-char
318  * @append_ucs4: UCS-4 character to append
319  *
320  * This is similar to term_char_merge(), but it returns a separately allocated
321  * character. That is, @base will stay valid after this returns and is not
322  * touched. In case the append-operation fails, @base is duplicated and
323  * returned. That is, the returned char is always independent of @base.
324  *
325  * Returns: Newly allocated character with @append_ucs4 appended to @base.
326  */
327 term_char_t term_char_dup_append(term_char_t base, uint32_t append_ucs4) {
328         term_char_t ch;
329
330         ch = char_build(base, append_ucs4);
331         if (term_char_same(ch, base))
332                 ch = term_char_dup(base);
333
334         return ch;
335 }
336
337 /**
338  * term_char_resolve() - Retrieve the UCS-4 string for a term-char
339  * @ch: character to resolve
340  * @s: storage for size of string or NULL
341  * @b: storage for string or NULL
342  *
343  * This takes a term-character and returns the UCS-4 string associated with it.
344  * In case @ch is not allocated, the string is stored in @b (in case @b is NULL
345  * static storage is used). Otherwise, a pointer to the allocated storage is
346  * returned.
347  *
348  * The returned string is only valid as long as @ch and @b are valid. The string
349  * is zero-terminated and can safely be printed via long-character printf().
350  * The length of the string excluding the zero-character is returned in @s.
351  *
352  * This never returns NULL. Even if the size is 0, this points to a buffer of at
353  * least a zero-terminator.
354  *
355  * Returns: The UCS-4 string-representation of @ch, and its size in @s.
356  */
357 const uint32_t *term_char_resolve(term_char_t ch, size_t *s, term_charbuf_t *b) {
358         static term_charbuf_t static_b;
359         term_character *c;
360         uint32_t *cache;
361         size_t len;
362
363         if (b)
364                 cache = b->buf;
365         else
366                 cache = static_b.buf;
367
368         if (term_char_is_null(ch)) {
369                 len = 0;
370                 cache[0] = 0;
371         } else if (term_char_is_allocated(ch)) {
372                 c = char_to_ptr(ch);
373                 len = c->n;
374                 cache = c->codepoints;
375         } else {
376                 len = char_unpack(ch, &cache[0], &cache[1], &cache[2]);
377                 cache[len] = 0;
378         }
379
380         if (s)
381                 *s = len;
382
383         return cache;
384 }
385
386 /**
387  * term_char_lookup_width() - Lookup cell-width of a character
388  * @ch: character to return cell-width for
389  *
390  * This is an equivalent of wcwidth() for term_char_t. It can deal directly
391  * with UCS-4 and combining-characters and avoids the mess that is wchar_t and
392  * locale handling.
393  *
394  * Returns: 0 for unprintable characters, >0 for everything else.
395  */
396 unsigned int term_char_lookup_width(term_char_t ch) {
397         term_charbuf_t b;
398         const uint32_t *str;
399         unsigned int max;
400         size_t i, len;
401         int r;
402
403         max = 0;
404         str = term_char_resolve(ch, &len, &b);
405
406         for (i = 0; i < len; ++i) {
407                 /*
408                  * Oh god, C99 locale handling strikes again: wcwidth() expects
409                  * wchar_t, but there is no way for us to know the
410                  * internal encoding of wchar_t. Moreover, it is nearly
411                  * impossible to convert UCS-4 into wchar_t (except for iconv,
412                  * which is way too much overhead).
413                  * Therefore, we use our own copy of wcwidth(). Lets just hope
414                  * that glibc will one day export it's internal UCS-4 and UTF-8
415                  * helpers for direct use.
416                  */
417                 assert_cc(sizeof(wchar_t) >= 4);
418                 r = mk_wcwidth((wchar_t)str[i]);
419                 if (r > 0 && (unsigned int)r > max)
420                         max = r;
421         }
422
423         return max;
424 }
425
426 /**
427  * term_cell_init() - Initialize a new cell
428  * @cell: cell to initialize
429  * @ch: character to set on the cell or TERM_CHAR_NULL
430  * @cwidth: character width of @ch
431  * @attr: attributes to set on the cell or NULL
432  * @age: age to set on the cell or TERM_AGE_NULL
433  *
434  * This initializes a new cell. The backing-memory of the cell must be allocated
435  * by the caller beforehand. The caller is responsible to destroy the cell via
436  * term_cell_destroy() before freeing the backing-memory.
437  *
438  * It is safe (and supported!) to use:
439  *   zero(*c);
440  * instead of:
441  *   term_cell_init(c, TERM_CHAR_NULL, NULL, TERM_AGE_NULL);
442  *
443  * Note that this call takes ownership of @ch. If you want to use it yourself
444  * after this call, you need to duplicate it before calling this.
445  */
446 static void term_cell_init(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) {
447         assert(cell);
448
449         cell->ch = ch;
450         cell->cwidth = cwidth;
451         cell->age = age;
452
453         if (attr)
454                 memcpy(&cell->attr, attr, sizeof(*attr));
455         else
456                 zero(cell->attr);
457 }
458
459 /**
460  * term_cell_destroy() - Destroy previously initialized cell
461  * @cell: cell to destroy or NULL
462  *
463  * This releases all resources associated with a cell. The backing memory is
464  * kept as-is. It's the responsibility of the caller to manage it.
465  *
466  * You must not call any other cell operations on this cell after this call
467  * returns. You must re-initialize the cell via term_cell_init() before you can
468  * use it again.
469  *
470  * If @cell is NULL, this is a no-op.
471  */
472 static void term_cell_destroy(term_cell *cell) {
473         if (!cell)
474                 return;
475
476         term_char_free(cell->ch);
477 }
478
479 /**
480  * term_cell_set() - Change contents of a cell
481  * @cell: cell to modify
482  * @ch: character to set on the cell or cell->ch
483  * @cwidth: character width of @ch or cell->cwidth
484  * @attr: attributes to set on the cell or NULL
485  * @age: age to set on the cell or cell->age
486  *
487  * This changes the contents of a cell. It can be used to change the character,
488  * attributes and age. To keep the current character, pass cell->ch as @ch. To
489  * reset the current attributes, pass NULL. To keep the current age, pass
490  * cell->age.
491  *
492  * This call takes ownership of @ch. You need to duplicate it first, in case you
493  * want to use it for your own purposes after this call.
494  *
495  * The cell must have been initialized properly before calling this. See
496  * term_cell_init().
497  */
498 static void term_cell_set(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) {
499         assert(cell);
500
501         if (!term_char_same(ch, cell->ch)) {
502                 term_char_free(cell->ch);
503                 cell->ch = ch;
504         }
505
506         cell->cwidth = cwidth;
507         cell->age = age;
508
509         if (attr)
510                 memcpy(&cell->attr, attr, sizeof(*attr));
511         else
512                 zero(cell->attr);
513 }
514
515 /**
516  * term_cell_append() - Append a combining-char to a cell
517  * @cell: cell to modify
518  * @ucs4: UCS-4 character to append to the cell
519  * @age: new age to set on the cell or cell->age
520  *
521  * This appends a combining-character to a cell. No validation of the UCS-4
522  * character is done, so this can be used to append any character. Additionally,
523  * this can update the age of the cell.
524  *
525  * The cell must have been initialized properly before calling this. See
526  * term_cell_init().
527  */
528 static void term_cell_append(term_cell *cell, uint32_t ucs4, term_age_t age) {
529         assert(cell);
530
531         cell->ch = term_char_merge(cell->ch, ucs4);
532         cell->age = age;
533 }
534
535 /**
536  * term_cell_init_n() - Initialize an array of cells
537  * @cells: pointer to an array of cells to initialize
538  * @n: number of cells
539  * @attr: attributes to set on all cells or NULL
540  * @age: age to set on all cells
541  *
542  * This is the same as term_cell_init() but initializes an array of cells.
543  * Furthermore, this always sets the character to TERM_CHAR_NULL.
544  * If you want to set a specific characters on all cells, you need to hard-code
545  * this loop and duplicate the character for each cell.
546  */
547 static void term_cell_init_n(term_cell *cells, unsigned int n, const term_attr *attr, term_age_t age) {
548         for ( ; n > 0; --n, ++cells)
549                 term_cell_init(cells, TERM_CHAR_NULL, 0, attr, age);
550 }
551
552 /**
553  * term_cell_destroy_n() - Destroy an array of cells
554  * @cells: pointer to an array of cells to destroy
555  * @n: number of cells
556  *
557  * This is the same as term_cell_destroy() but destroys an array of cells.
558  */
559 static void term_cell_destroy_n(term_cell *cells, unsigned int n) {
560         for ( ; n > 0; --n, ++cells)
561                 term_cell_destroy(cells);
562 }
563
564 /**
565  * term_cell_clear_n() - Clear contents of an array of cells
566  * @cells: pointer to an array of cells to modify
567  * @n: number of cells
568  * @attr: attributes to set on all cells or NULL
569  * @age: age to set on all cells
570  *
571  * This is the same as term_cell_set() but operates on an array of cells. Note
572  * that all characters are always set to TERM_CHAR_NULL, unlike term_cell_set()
573  * which takes the character as argument.
574  * If you want to set a specific characters on all cells, you need to hard-code
575  * this loop and duplicate the character for each cell.
576  */
577 static void term_cell_clear_n(term_cell *cells, unsigned int n, const term_attr *attr, term_age_t age) {
578         for ( ; n > 0; --n, ++cells)
579                 term_cell_set(cells, TERM_CHAR_NULL, 0, attr, age);
580 }
581
582 /**
583  * term_line_new() - Allocate a new line
584  * @out: place to store pointer to new line
585  *
586  * This allocates and initialized a new line. The line is unlinked and
587  * independent of any page. It can be used for any purpose. The initial
588  * cell-count is set to 0.
589  *
590  * The line has to be freed via term_line_free() once it's no longer needed.
591  *
592  * Returns: 0 on success, negative error code on failure.
593  */
594 int term_line_new(term_line **out) {
595         _term_line_free_ term_line *line = NULL;
596
597         assert_return(out, -EINVAL);
598
599         line = new0(term_line, 1);
600         if (!line)
601                 return -ENOMEM;
602
603         *out = line;
604         line = NULL;
605         return 0;
606 }
607
608 /**
609  * term_line_free() - Free a line
610  * @line: line to free or NULL
611  *
612  * This frees a line that was previously allocated via term_line_free(). All its
613  * cells are released, too.
614  *
615  * If @line is NULL, this is a no-op.
616  */
617 term_line *term_line_free(term_line *line) {
618         if (!line)
619                 return NULL;
620
621         term_cell_destroy_n(line->cells, line->n_cells);
622         free(line->cells);
623         free(line);
624
625         return NULL;
626 }
627
628 /**
629  * term_line_reserve() - Pre-allocate cells for a line
630  * @line: line to pre-allocate cells for
631  * @width: numbers of cells the line shall have pre-allocated
632  * @attr: attribute for all allocated cells or NULL
633  * @age: current age for all modifications
634  * @protect_width: width to protect from erasure
635  *
636  * This pre-allocates cells for this line. Please note that @width is the number
637  * of cells the line is guaranteed to have allocated after this call returns.
638  * It's not the number of cells that are added, neither is it the new width of
639  * the line.
640  *
641  * This function never frees memory. That is, reducing the line-width will
642  * always succeed, same is true for increasing the width to a previously set
643  * width.
644  *
645  * @attr and @age are used to initialize new cells. Additionally, any
646  * existing cell outside of the protected area specified by @protect_width are
647  * cleared and reset with @attr and @age.
648  *
649  * Returns: 0 on success, negative error code on failure.
650  */
651 int term_line_reserve(term_line *line, unsigned int width, const term_attr *attr, term_age_t age, unsigned int protect_width) {
652         unsigned int min_width;
653         term_cell *t;
654
655         assert_return(line, -EINVAL);
656
657         /* reset existing cells if required */
658         min_width = MIN(line->n_cells, width);
659         if (min_width > protect_width)
660                 term_cell_clear_n(line->cells + protect_width,
661                                   min_width - protect_width,
662                                   attr,
663                                   age);
664
665         /* allocate new cells if required */
666
667         if (width > line->n_cells) {
668                 t = realloc_multiply(line->cells, sizeof(*t), width);
669                 if (!t)
670                         return -ENOMEM;
671
672                 if (!attr && !age)
673                         memzero(t + line->n_cells,
674                                 sizeof(*t) * (width - line->n_cells));
675                 else
676                         term_cell_init_n(t + line->n_cells,
677                                          width - line->n_cells,
678                                          attr,
679                                          age);
680
681                 line->cells = t;
682                 line->n_cells = width;
683         }
684
685         line->fill = MIN(line->fill, protect_width);
686
687         return 0;
688 }
689
690 /**
691  * term_line_set_width() - Change width of a line
692  * @line: line to modify
693  * @width: new width
694  *
695  * This changes the actual width of a line. It is the caller's responsibility
696  * to use term_line_reserve() to make sure enough space is allocated. If @width
697  * is greater than the allocated size, it is cropped.
698  *
699  * This does not modify any cells. Use term_line_reserve() or term_line_erase()
700  * to clear any newly added cells.
701  *
702  * NOTE: The fill state is cropped at line->width. Therefore, if you increase
703  *       the line-width afterwards, but there is a multi-cell character at the
704  *       end of the line that got cropped, then the fill-state will _not_ be
705  *       adjusted.
706  *       This means, the fill-state always includes the cells up to the start
707  *       of the right-most character, but it might or might not cover it until
708  *       its end. This should be totally fine, though. You should never access
709  *       multi-cell tails directly, anyway.
710  */
711 void term_line_set_width(term_line *line, unsigned int width) {
712         assert(line);
713
714         if (width > line->n_cells)
715                 width = line->n_cells;
716
717         line->width = width;
718         line->fill = MIN(line->fill, width);
719 }
720
721 /**
722  * line_insert() - Insert characters and move existing cells to the right
723  * @from: position to insert cells at
724  * @num: number of cells to insert
725  * @head_char: character that is set on the first cell
726  * @head_cwidth: character-length of @head_char
727  * @attr: attribute for all inserted cells or NULL
728  * @age: current age for all modifications
729  *
730  * The INSERT operation (or writes with INSERT_MODE) writes data at a specific
731  * position on a line and shifts the existing cells to the right. Cells that are
732  * moved beyond the right hand border are discarded.
733  *
734  * This helper contains the actual INSERT implementation which is independent of
735  * the data written. It works on cells, not on characters. The first cell is set
736  * to @head_char, all others are reset to TERM_CHAR_NULL. See each caller for a
737  * more detailed description.
738  */
739 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) {
740         unsigned int i, rem, move;
741
742         if (from >= line->width)
743                 return;
744         if (from + num < from || from + num > line->width)
745                 num = line->width - from;
746         if (!num)
747                 return;
748
749         move = line->width - from - num;
750         rem = MIN(num, move);
751
752         if (rem > 0) {
753                 /*
754                  * Make room for @num cells; shift cells to the right if
755                  * required. @rem is the number of remaining cells that we will
756                  * knock off on the right and overwrite during the right shift.
757                  *
758                  * For INSERT_MODE, @num/@rem are usually 1 or 2, @move is 50%
759                  * of the line on average. Therefore, the actual move is quite
760                  * heavy and we can safely invalidate cells manually instead of
761                  * the whole line.
762                  * However, for INSERT operations, any parameters are
763                  * possible. But we cannot place any assumption on its usage
764                  * across applications, so we just handle it the same as
765                  * INSERT_MODE and do per-cell invalidation.
766                  */
767
768                 /* destroy cells that are knocked off on the right */
769                 term_cell_destroy_n(line->cells + line->width - rem, rem);
770
771                 /* move remaining bulk of cells */
772                 memmove(line->cells + from + num,
773                         line->cells + from,
774                         sizeof(*line->cells) * move);
775
776                 /* invalidate cells */
777                 for (i = 0; i < move; ++i)
778                         line->cells[from + num + i].age = age;
779
780                 /* initialize fresh head-cell */
781                 term_cell_init(line->cells + from,
782                                head_char,
783                                head_cwidth,
784                                attr,
785                                age);
786
787                 /* initialize fresh tail-cells */
788                 term_cell_init_n(line->cells + from + 1,
789                                  num - 1,
790                                  attr,
791                                  age);
792
793                 /* adjust fill-state */
794                 DISABLE_WARNING_SHADOW;
795                 line->fill = MIN(line->width,
796                                  MAX(line->fill + num,
797                                      from + num));
798                 REENABLE_WARNING;
799         } else {
800                 /* modify head-cell */
801                 term_cell_set(line->cells + from,
802                               head_char,
803                               head_cwidth,
804                               attr,
805                               age);
806
807                 /* reset tail-cells */
808                 term_cell_clear_n(line->cells + from + 1,
809                                   num - 1,
810                                   attr,
811                                   age);
812
813                 /* adjust fill-state */
814                 line->fill = line->width;
815         }
816 }
817
818 /**
819  * term_line_write() - Write to a single, specific cell
820  * @line: line to write to
821  * @pos_x: x-position of cell in @line to write to
822  * @ch: character to write to the cell
823  * @cwidth: character width of @ch
824  * @attr: attributes to set on the cell or NULL
825  * @age: current age for all modifications
826  * @insert_mode: true if INSERT-MODE is enabled
827  *
828  * This writes to a specific cell in a line. The cell is addressed by its
829  * X-position @pos_x. If that cell does not exist, this is a no-op.
830  *
831  * @ch and @attr are set on this cell.
832  *
833  * If @insert_mode is true, this inserts the character instead of overwriting
834  * existing data (existing data is now moved to the right before writing).
835  *
836  * This function is the low-level handler of normal writes to a terminal.
837  */
838 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) {
839         unsigned int len;
840
841         assert(line);
842
843         if (pos_x >= line->width)
844                 return;
845
846         len = MAX(1U, cwidth);
847         if (pos_x + len < pos_x || pos_x + len > line->width)
848                 len = line->width - pos_x;
849         if (!len)
850                 return;
851
852         if (insert_mode) {
853                 /* Use line_insert() to insert the character-head and fill
854                  * the remains with NULLs. */
855                 line_insert(line, pos_x, len, ch, cwidth, attr, age);
856         } else {
857                 /* modify head-cell */
858                 term_cell_set(line->cells + pos_x, ch, cwidth, attr, age);
859
860                 /* reset tail-cells */
861                 term_cell_clear_n(line->cells + pos_x + 1,
862                                   len - 1,
863                                   attr,
864                                   age);
865
866                 /* adjust fill-state */
867                 DISABLE_WARNING_SHADOW;
868                 line->fill = MIN(line->width,
869                                  MAX(line->fill,
870                                      pos_x + len));
871                 REENABLE_WARNING;
872         }
873 }
874
875 /**
876  * term_line_insert() - Insert empty cells
877  * @line: line to insert empty cells into
878  * @from: x-position where to insert cells
879  * @num: number of cells to insert
880  * @attr: attributes to set on the cells or NULL
881  * @age: current age for all modifications
882  *
883  * This inserts @num empty cells at position @from in line @line. All existing
884  * cells to the right are shifted to make room for the new cells. Cells that get
885  * pushed beyond the right hand border are discarded.
886  */
887 void term_line_insert(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age) {
888         /* use line_insert() to insert @num empty cells */
889         return line_insert(line, from, num, TERM_CHAR_NULL, 0, attr, age);
890 }
891
892 /**
893  * term_line_delete() - Delete cells from line
894  * @line: line to delete cells from
895  * @from: position to delete cells at
896  * @num: number of cells to delete
897  * @attr: attributes to set on any new cells
898  * @age: current age for all modifications
899  *
900  * Delete cells from a line. All cells to the right of the deleted cells are
901  * shifted to the left to fill the empty space. New cells appearing on the right
902  * hand border are cleared and initialized with @attr.
903  */
904 void term_line_delete(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age) {
905         unsigned int rem, move, i;
906
907         assert(line);
908
909         if (from >= line->width)
910                 return;
911         if (from + num < from || from + num > line->width)
912                 num = line->width - from;
913         if (!num)
914                 return;
915
916         /* destroy and move as many upfront as possible */
917         move = line->width - from - num;
918         rem = MIN(num, move);
919         if (rem > 0) {
920                 /* destroy to be removed cells */
921                 term_cell_destroy_n(line->cells + from, rem);
922
923                 /* move tail upfront */
924                 memmove(line->cells + from,
925                         line->cells + from + num,
926                         sizeof(*line->cells) * move);
927
928                 /* invalidate copied cells */
929                 for (i = 0; i < move; ++i)
930                         line->cells[from + i].age = age;
931
932                 /* initialize tail that was moved away */
933                 term_cell_init_n(line->cells + line->width - rem,
934                                  rem,
935                                  attr,
936                                  age);
937
938                 /* reset remaining cells in case the move was too small */
939                 if (num > move)
940                         term_cell_clear_n(line->cells + from + move,
941                                           num - move,
942                                           attr,
943                                           age);
944         } else {
945                 /* reset cells */
946                 term_cell_clear_n(line->cells + from,
947                                   num,
948                                   attr,
949                                   age);
950         }
951
952         /* adjust fill-state */
953         if (from + num < line->fill)
954                 line->fill -= num;
955         else if (from < line->fill)
956                 line->fill = from;
957 }
958
959 /**
960  * term_line_append_combchar() - Append combining char to existing cell
961  * @line: line to modify
962  * @pos_x: position of cell to append combining char to
963  * @ucs4: combining character to append
964  * @age: current age for all modifications
965  *
966  * Unicode allows trailing combining characters, which belong to the
967  * char in front of them. The caller is responsible of detecting
968  * combining characters and calling term_line_append_combchar() instead of
969  * term_line_write(). This simply appends the char to the correct cell then.
970  * If the cell is not in the visible area, this call is skipped.
971  *
972  * Note that control-sequences are not 100% compatible with combining
973  * characters as they require delayed parsing. However, we must handle
974  * control-sequences immediately. Therefore, there might be trailing
975  * combining chars that should be discarded by the parser.
976  * However, to prevent programming errors, we're also being pedantic
977  * here and discard weirdly placed combining chars. This prevents
978  * situations were invalid content is parsed into the terminal and you
979  * might end up with cells containing only combining chars.
980  *
981  * Long story short: To get combining-characters working with old-fashioned
982  * terminal-emulation, we parse them exclusively for direct cell-writes. Other
983  * combining-characters are usually simply discarded and ignored.
984  */
985 void term_line_append_combchar(term_line *line, unsigned int pos_x, uint32_t ucs4, term_age_t age) {
986         assert(line);
987
988         if (pos_x >= line->width)
989                 return;
990
991         /* Unused cell? Skip appending any combining chars then. */
992         if (term_char_is_null(line->cells[pos_x].ch))
993                 return;
994
995         term_cell_append(line->cells + pos_x, ucs4, age);
996 }
997
998 /**
999  * term_line_erase() - Erase parts of a line
1000  * @line: line to modify
1001  * @from: position to start the erase
1002  * @num: number of cells to erase
1003  * @attr: attributes to initialize erased cells with
1004  * @age: current age for all modifications
1005  * @keep_protected: true if protected cells should be kept
1006  *
1007  * This is the standard erase operation. It clears all cells in the targetted
1008  * area and re-initializes them. Cells to the right are not shifted left, you
1009  * must use DELETE to achieve that. Cells outside the visible area are skipped.
1010  *
1011  * If @keep_protected is true, protected cells will not be erased.
1012  */
1013 void term_line_erase(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age, bool keep_protected) {
1014         term_cell *cell;
1015         unsigned int i, last_protected;
1016
1017         assert(line);
1018
1019         if (from >= line->width)
1020                 return;
1021         if (from + num < from || from + num > line->width)
1022                 num = line->width - from;
1023         if (!num)
1024                 return;
1025
1026         last_protected = 0;
1027         for (i = 0; i < num; ++i) {
1028                 cell = line->cells + from + i;
1029                 if (keep_protected && cell->attr.protect) {
1030                         /* only count protected-cells inside the fill-region */
1031                         if (from + i < line->fill)
1032                                 last_protected = from + i;
1033
1034                         continue;
1035                 }
1036
1037                 term_cell_set(cell, TERM_CHAR_NULL, 0, attr, age);
1038         }
1039
1040         /* Adjust fill-state. This is a bit tricks, we can only adjust it in
1041          * case the erase-region starts inside the fill-region and ends at the
1042          * tail or beyond the fill-region. Otherwise, the current fill-state
1043          * stays as it was.
1044          * Furthermore, we must account for protected cells. The loop above
1045          * ensures that protected-cells are only accounted for if they're
1046          * inside the fill-region. */
1047         if (from < line->fill && from + num >= line->fill)
1048                 line->fill = MAX(from, last_protected);
1049 }
1050
1051 /**
1052  * term_line_reset() - Reset a line
1053  * @line: line to reset
1054  * @attr: attributes to initialize all cells with
1055  * @age: current age for all modifications
1056  *
1057  * This resets all visible cells of a line and sets their attributes and ages
1058  * to @attr and @age. This is equivalent to erasing a whole line via
1059  * term_line_erase().
1060  */
1061 void term_line_reset(term_line *line, const term_attr *attr, term_age_t age) {
1062         assert(line);
1063
1064         return term_line_erase(line, 0, line->width, attr, age, 0);
1065 }
1066
1067 /**
1068  * term_line_link() - Link line in front of a list
1069  * @line: line to link
1070  * @first: member pointing to first entry
1071  * @last: member pointing to last entry
1072  *
1073  * This links a line into a list of lines. The line is inserted at the front and
1074  * must not be linked, yet. See the TERM_LINE_LINK() macro for an easier usage of
1075  * this.
1076  */
1077 void term_line_link(term_line *line, term_line **first, term_line **last) {
1078         assert(line);
1079         assert(first);
1080         assert(last);
1081         assert(!line->lines_prev);
1082         assert(!line->lines_next);
1083
1084         line->lines_prev = NULL;
1085         line->lines_next = *first;
1086         if (*first)
1087                 (*first)->lines_prev = line;
1088         else
1089                 *last = line;
1090         *first = line;
1091 }
1092
1093 /**
1094  * term_line_link_tail() - Link line at tail of a list
1095  * @line: line to link
1096  * @first: member pointing to first entry
1097  * @last: member pointing to last entry
1098  *
1099  * Same as term_line_link() but links the line at the tail.
1100  */
1101 void term_line_link_tail(term_line *line, term_line **first, term_line **last) {
1102         assert(line);
1103         assert(first);
1104         assert(last);
1105         assert(!line->lines_prev);
1106         assert(!line->lines_next);
1107
1108         line->lines_next = NULL;
1109         line->lines_prev = *last;
1110         if (*last)
1111                 (*last)->lines_next = line;
1112         else
1113                 *first = line;
1114         *last = line;
1115 }
1116
1117 /**
1118  * term_line_unlink() - Unlink line from a list
1119  * @line: line to unlink
1120  * @first: member pointing to first entry
1121  * @last: member pointing to last entry
1122  *
1123  * This unlinks a previously linked line. See TERM_LINE_UNLINK() for an easier to
1124  * use macro.
1125  */
1126 void term_line_unlink(term_line *line, term_line **first, term_line **last) {
1127         assert(line);
1128         assert(first);
1129         assert(last);
1130
1131         if (line->lines_prev)
1132                 line->lines_prev->lines_next = line->lines_next;
1133         else
1134                 *first = line->lines_next;
1135         if (line->lines_next)
1136                 line->lines_next->lines_prev = line->lines_prev;
1137         else
1138                 *last = line->lines_prev;
1139
1140         line->lines_prev = NULL;
1141         line->lines_next = NULL;
1142 }