1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
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.
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.
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/>.
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
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
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.
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.
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.
72 #include "term-internal.h"
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)
82 /* real storage behind "term_char_t" in case it's not packed */
83 typedef struct term_character {
85 uint32_t codepoints[];
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.
93 static inline term_char_t char_pack(uint32_t v1, uint32_t v2, uint32_t v3) {
94 uint64_t packed, u1, u2, u3;
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;
105 return TERM_CHAR_INIT(packed);
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))
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).
119 static inline uint8_t char_unpack(term_char_t packed, uint32_t *out_v1, uint32_t *out_v2, uint32_t *out_v3) {
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;
133 return (v1 > CHAR_UCS4_MAX) ? 0 :
134 ((v2 > CHAR_UCS4_MAX) ? 1 :
135 ((v3 > CHAR_UCS4_MAX) ? 2 :
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;
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);
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.
156 static term_character *char_alloc(uint8_t n) {
160 r = posix_memalign((void**)&c,
161 MAX(sizeof(void*), (size_t)2),
162 sizeof(*c) + sizeof(*c->codepoints) * (n + 1));
167 c->codepoints[n] = 0;
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.
176 static inline void char_free(term_char_t ch) {
177 if (term_char_is_allocated(ch))
178 free(char_to_ptr(ch));
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.
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;
194 /* ignore invalid UCS-4 */
195 if (append_ucs4 > CHAR_UCS4_MAX)
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]);
206 return char_pack1(append_ucs4);
211 return char_pack2(buf[0], append_ucs4);
216 return char_pack3(buf[0], buf[1], append_ucs4);
222 /* already fully packed, we need to allocate a new one */
225 /* already an allocated type, we need to allocate a new one */
226 c = char_to_ptr(base);
231 /* bail out if soft-limit is reached */
235 /* allocate new char */
236 c = char_alloc(n + 1);
240 memcpy(c->codepoints, t, sizeof(*t) * n);
241 c->codepoints[n] = append_ucs4;
243 return char_from_ptr(c);
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
251 * This frees all resources in @previous and re-initializes it to @append_ucs4.
252 * The new char is returned.
254 * Usually, this is used like this:
255 * obj->ch = term_char_set(obj->ch, ucs4);
257 * Returns: The previous character reset to @append_ucs4.
259 term_char_t term_char_set(term_char_t previous, uint32_t append_ucs4) {
261 return char_build(TERM_CHAR_NULL, append_ucs4);
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
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.
273 * Usually, this is used like this:
274 * obj->ch = term_char_merge(obj->ch, ucs4);
276 * Returns: The new merged character.
278 term_char_t term_char_merge(term_char_t base, uint32_t append_ucs4) {
281 ch = char_build(base, append_ucs4);
282 if (!term_char_same(ch, base))
283 term_char_free(base);
289 * term_char_dup() - Duplicate character
290 * @ch: character to duplicate
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.
298 * Returns: The duplicated term-character.
300 term_char_t term_char_dup(term_char_t ch) {
301 term_character *c, *newc;
303 if (!term_char_is_allocated(ch))
307 newc = char_alloc(c->n);
309 return char_pack1(CHAR_UCS4_REPLACEMENT);
311 memcpy(newc->codepoints, c->codepoints, sizeof(*c->codepoints) * c->n);
312 return char_from_ptr(newc);
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
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.
325 * Returns: Newly allocated character with @append_ucs4 appended to @base.
327 term_char_t term_char_dup_append(term_char_t base, uint32_t append_ucs4) {
330 ch = char_build(base, append_ucs4);
331 if (term_char_same(ch, base))
332 ch = term_char_dup(base);
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
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
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.
352 * This never returns NULL. Even if the size is 0, this points to a buffer of at
353 * least a zero-terminator.
355 * Returns: The UCS-4 string-representation of @ch, and its size in @s.
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;
366 cache = static_b.buf;
368 if (term_char_is_null(ch)) {
371 } else if (term_char_is_allocated(ch)) {
374 cache = c->codepoints;
376 len = char_unpack(ch, &cache[0], &cache[1], &cache[2]);
387 * term_char_lookup_width() - Lookup cell-width of a character
388 * @ch: character to return cell-width for
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
394 * Returns: 0 for unprintable characters, >0 for everything else.
396 unsigned int term_char_lookup_width(term_char_t ch) {
404 str = term_char_resolve(ch, &len, &b);
406 for (i = 0; i < len; ++i) {
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.
417 assert_cc(sizeof(wchar_t) >= 4);
418 r = mk_wcwidth((wchar_t)str[i]);
419 if (r > 0 && (unsigned int)r > max)
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
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.
438 * It is safe (and supported!) to use:
441 * term_cell_init(c, TERM_CHAR_NULL, NULL, TERM_AGE_NULL);
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.
446 static void term_cell_init(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) {
450 cell->cwidth = cwidth;
454 memcpy(&cell->attr, attr, sizeof(*attr));
460 * term_cell_destroy() - Destroy previously initialized cell
461 * @cell: cell to destroy or NULL
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.
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
470 * If @cell is NULL, this is a no-op.
472 static void term_cell_destroy(term_cell *cell) {
476 term_char_free(cell->ch);
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
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
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.
495 * The cell must have been initialized properly before calling this. See
498 static void term_cell_set(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) {
501 if (!term_char_same(ch, cell->ch)) {
502 term_char_free(cell->ch);
506 cell->cwidth = cwidth;
510 memcpy(&cell->attr, attr, sizeof(*attr));
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
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.
525 * The cell must have been initialized properly before calling this. See
528 static void term_cell_append(term_cell *cell, uint32_t ucs4, term_age_t age) {
531 cell->ch = term_char_merge(cell->ch, ucs4);
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
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.
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);
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
557 * This is the same as term_cell_destroy() but destroys an array of cells.
559 static void term_cell_destroy_n(term_cell *cells, unsigned int n) {
560 for ( ; n > 0; --n, ++cells)
561 term_cell_destroy(cells);
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
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.
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);
583 * term_line_new() - Allocate a new line
584 * @out: place to store pointer to new line
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.
590 * The line has to be freed via term_line_free() once it's no longer needed.
592 * Returns: 0 on success, negative error code on failure.
594 int term_line_new(term_line **out) {
595 _term_line_free_ term_line *line = NULL;
597 assert_return(out, -EINVAL);
599 line = new0(term_line, 1);
609 * term_line_free() - Free a line
610 * @line: line to free or NULL
612 * This frees a line that was previously allocated via term_line_free(). All its
613 * cells are released, too.
615 * If @line is NULL, this is a no-op.
617 term_line *term_line_free(term_line *line) {
621 term_cell_destroy_n(line->cells, line->n_cells);
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
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
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
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.
649 * Returns: 0 on success, negative error code on failure.
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;
655 assert_return(line, -EINVAL);
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,
665 /* allocate new cells if required */
667 if (width > line->n_cells) {
668 t = realloc_multiply(line->cells, sizeof(*t), width);
673 memzero(t + line->n_cells,
674 sizeof(*t) * (width - line->n_cells));
676 term_cell_init_n(t + line->n_cells,
677 width - line->n_cells,
682 line->n_cells = width;
685 line->fill = MIN(line->fill, protect_width);
691 * term_line_set_width() - Change width of a line
692 * @line: line to modify
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.
699 * This does not modify any cells. Use term_line_reserve() or term_line_erase()
700 * to clear any newly added cells.
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
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.
711 void term_line_set_width(term_line *line, unsigned int width) {
714 if (width > line->n_cells)
715 width = line->n_cells;
718 line->fill = MIN(line->fill, width);
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
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.
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.
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;
742 if (from >= line->width)
744 if (from + num < from || from + num > line->width)
745 num = line->width - from;
749 move = line->width - from - num;
750 rem = MIN(num, move);
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.
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
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.
768 /* destroy cells that are knocked off on the right */
769 term_cell_destroy_n(line->cells + line->width - rem, rem);
771 /* move remaining bulk of cells */
772 memmove(line->cells + from + num,
774 sizeof(*line->cells) * move);
776 /* invalidate cells */
777 for (i = 0; i < move; ++i)
778 line->cells[from + num + i].age = age;
780 /* initialize fresh head-cell */
781 term_cell_init(line->cells + from,
787 /* initialize fresh tail-cells */
788 term_cell_init_n(line->cells + from + 1,
793 /* adjust fill-state */
794 DISABLE_WARNING_SHADOW;
795 line->fill = MIN(line->width,
796 MAX(line->fill + num,
800 /* modify head-cell */
801 term_cell_set(line->cells + from,
807 /* reset tail-cells */
808 term_cell_clear_n(line->cells + from + 1,
813 /* adjust fill-state */
814 line->fill = line->width;
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
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.
831 * @ch and @attr are set on this cell.
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).
836 * This function is the low-level handler of normal writes to a terminal.
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) {
843 if (pos_x >= line->width)
846 len = MAX(1U, cwidth);
847 if (pos_x + len < pos_x || pos_x + len > line->width)
848 len = line->width - pos_x;
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);
857 /* modify head-cell */
858 term_cell_set(line->cells + pos_x, ch, cwidth, attr, age);
860 /* reset tail-cells */
861 term_cell_clear_n(line->cells + pos_x + 1,
866 /* adjust fill-state */
867 DISABLE_WARNING_SHADOW;
868 line->fill = MIN(line->width,
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
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.
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);
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
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.
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;
909 if (from >= line->width)
911 if (from + num < from || from + num > line->width)
912 num = line->width - from;
916 /* destroy and move as many upfront as possible */
917 move = line->width - from - num;
918 rem = MIN(num, move);
920 /* destroy to be removed cells */
921 term_cell_destroy_n(line->cells + from, rem);
923 /* move tail upfront */
924 memmove(line->cells + from,
925 line->cells + from + num,
926 sizeof(*line->cells) * move);
928 /* invalidate copied cells */
929 for (i = 0; i < move; ++i)
930 line->cells[from + i].age = age;
932 /* initialize tail that was moved away */
933 term_cell_init_n(line->cells + line->width - rem,
938 /* reset remaining cells in case the move was too small */
940 term_cell_clear_n(line->cells + from + move,
946 term_cell_clear_n(line->cells + from,
952 /* adjust fill-state */
953 if (from + num < line->fill)
955 else if (from < line->fill)
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
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.
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.
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.
985 void term_line_append_combchar(term_line *line, unsigned int pos_x, uint32_t ucs4, term_age_t age) {
988 if (pos_x >= line->width)
991 /* Unused cell? Skip appending any combining chars then. */
992 if (term_char_is_null(line->cells[pos_x].ch))
995 term_cell_append(line->cells + pos_x, ucs4, age);
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
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.
1011 * If @keep_protected is true, protected cells will not be erased.
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) {
1015 unsigned int i, last_protected;
1019 if (from >= line->width)
1021 if (from + num < from || from + num > line->width)
1022 num = line->width - from;
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;
1037 term_cell_set(cell, TERM_CHAR_NULL, 0, attr, age);
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
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);
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
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().
1061 void term_line_reset(term_line *line, const term_attr *attr, term_age_t age) {
1064 return term_line_erase(line, 0, line->width, attr, age, 0);
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
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
1077 void term_line_link(term_line *line, term_line **first, term_line **last) {
1081 assert(!line->lines_prev);
1082 assert(!line->lines_next);
1084 line->lines_prev = NULL;
1085 line->lines_next = *first;
1087 (*first)->lines_prev = line;
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
1099 * Same as term_line_link() but links the line at the tail.
1101 void term_line_link_tail(term_line *line, term_line **first, term_line **last) {
1105 assert(!line->lines_prev);
1106 assert(!line->lines_next);
1108 line->lines_next = NULL;
1109 line->lines_prev = *last;
1111 (*last)->lines_next = line;
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
1123 * This unlinks a previously linked line. See TERM_LINE_UNLINK() for an easier to
1126 void term_line_unlink(term_line *line, term_line **first, term_line **last) {
1131 if (line->lines_prev)
1132 line->lines_prev->lines_next = line->lines_next;
1134 *first = line->lines_next;
1135 if (line->lines_next)
1136 line->lines_next->lines_prev = line->lines_prev;
1138 *last = line->lines_prev;
1140 line->lines_prev = NULL;
1141 line->lines_next = NULL;