diff options
Diffstat (limited to 'lib/vio_lines.c')
-rw-r--r-- | lib/vio_lines.c | 585 |
1 files changed, 425 insertions, 160 deletions
diff --git a/lib/vio_lines.c b/lib/vio_lines.c index a9268fd5..031f4539 100644 --- a/lib/vio_lines.c +++ b/lib/vio_lines.c @@ -59,7 +59,7 @@ * * WHAT IT DOES DO: * - * 1) maps bare '\n' to '\r''\n'. + * 1) maps bare '\n' to '\r''\n' -- if required. * * Swallows '\r' immediately before '\n' if present. * @@ -80,66 +80,84 @@ * A width of <= 0 => very large width indeed. * A height of <= 0 => indefinite height * - * Pause is unset. vio_lc_append will collect an indefinite number of lines. - * - * Column and line position set to zero. - * - * Returns: address of vio_line_control + * Returns: address of vio_line_control */ extern vio_line_control -vio_lc_init_new(vio_line_control lc, int width, int height) +vio_lc_init_new(vio_line_control lc, int width, int height, const char* newline) { if (lc == NULL) lc = XCALLOC(MTYPE_VIO_LC, sizeof(struct vio_line_control)) ; else - memset(lc, 0, sizeof(struct vio_line_control)) ; + memset(lc, 0, sizeof(vio_line_control_t)) ; /* Zeroising has set: * - * pause = 0 -- no limit on the number of lines to append - * paused = 0 -- not paused + * width = X -- set below. + * height = X -- set below + * + * counter = X -- set below * - * col = 0 -- at column 0 - * lines = 0 -- no lines collected, yet + * incomplete = false -- no incomplete line in hand + * fragments = NULL -- set below + * here = NULL -- set below * - * iov = all 0 -- empty - * writing = 0 -- not writing + * qiov = NULL -- set below + * + * newline = zeros -- set below */ lc->width = width >= 0 ? width : 0 ; lc->height = height >= 0 ? height : 0 ; + vio_lc_counter_reset(lc) ; + + lc->fragments = qiovec_init_new(NULL) ; + lc->here = qs_new(120) ; + + lc->qiov = qiovec_init_new(NULL) ; + + lc->newline->base = newline ; + lc->newline->len = strlen(newline) ; return lc ; } ; /*------------------------------------------------------------------------------ - * Reset vio_line_control (if any) -- release body and (if required) the + * Reset vio_line_control (if any) -- release body and (if required) free the * structure. * * Returns: address of vio_line_control (if any) -- NULL if structure released + * + * NB: if the line control is not freed, it MUST be reinitialised by + * vio_lc_init_new() before it is used again ! + * + * NB: it is the callers responsibility to release anything which was buffered + * for the line control to look after. + * + * It is also the callers responsibility to release the newline string, + * if required. */ extern vio_line_control vio_lc_reset(vio_line_control lc, free_keep_b free_structure) { if (lc != NULL) { + lc->fragments = qiovec_free(lc->fragments) ; + lc->here = qs_free(lc->here) ; + + lc->qiov = qiovec_free(lc->qiov) ; + if (free_structure) XFREE(MTYPE_VIO_LC, lc) ; /* sets lc = NULL */ else - vio_lc_init_new(lc, lc->width, lc->height) ; - /* re-initialise */ + memset(lc, 0, sizeof(vio_line_control_t)) ; } ; return lc ; } ; /*------------------------------------------------------------------------------ - * Clear given vio_line_control. - * - * Sets: pause = 0 - * paused = 0 - * col = 0 - * writing = 0 + * Clear given vio_line_control -- discard all buffered lines and reset the + * counter. * * NB: it is the callers responsibility to release anything buffered because * it was earlier appended. @@ -150,12 +168,14 @@ vio_lc_clear(vio_line_control lc) if (lc == NULL) return ; - qiovec_clear(&lc->qiov) ; + vio_lc_counter_reset(lc) ; + + lc->incomplete = false ; + + qiovec_clear(lc->fragments) ; + qs_clear(lc->here) ; - lc->pause = 0 ; - lc->paused = 0 ; - lc->col = 0 ; - lc->writing = 0 ; + qiovec_clear(lc->qiov) ; } ; /*------------------------------------------------------------------------------ @@ -164,196 +184,404 @@ vio_lc_clear(vio_line_control lc) * A width of <= 0 => very large width indeed. * A height of <= 0 => indefinite height * - * Pause is adjusted if it is not zero, and may become zero and set paused. + * This may happen at almost any time when a Telnet terminal is resized. + * From line control perspective, can happen between calls of vio_lc_append(), + * vio_lc_flush() and vio_lc_write_nb(). + * + * If happens while the line control has stuff buffered, then it is too late to + * change anything, unless was an incomplete line. + * + * Tries to sort out the counter... but is is not possible to do this perfectly. */ extern void vio_lc_set_window(vio_line_control lc, int width, int height) { - unsigned old_height ; + unsigned depth ; - old_height = lc->height ; + depth = lc->height ; lc->width = width >= 0 ? width : 0 ; lc->height = height >= 0 ? height : 0 ; - if (lc->pause != 0) - { - if (lc->height > old_height) - lc->pause += lc->height - old_height ; - else - { - if (lc->pause >= (old_height - lc->height)) - lc->pause = 0 ; - else - lc->pause -= old_height - lc->height ; - } ; - lc->paused = (lc->pause == 0) ; - } ; -} ; + /* If counter already exhausted, or just set indefinite height, need do + * nothing more. + */ + if ((lc->counter <= 0) || (lc->height == 0)) + return ; -/*============================================================================== - * Appending and writing - */ + /* Counter not already exhausted, and is setting a definite height. + * + * If no height was set before, start from scratch, now. + */ + if (depth == 0) + return vio_lc_counter_reset(lc) ; -/*------------------------------------------------------------------------------ - * Sets pause to the current height and clear paused. - */ -extern void -vio_lc_set_pause(vio_line_control lc) -{ - lc->pause = lc->height ; - lc->paused = 0 ; + /* Had a definite height and setting a new one. + * + * Now calculate the depth, which is how far down the old height have + * got to. (The counter should be less than the old height, but if it + * isn't we end up with a huge depth here...) + */ + depth -= lc->counter ; + + /* If the new height is > depth set the counter to be the new height - depth. + * + * Otherwise, set the counter to 0, so is immediately exhausted. + * + * Cannot solve the problem of what to do if the width has changed ! + */ + lc->counter = (lc->height > depth) ? lc->height - depth : 0 ; } ; -/*------------------------------------------------------------------------------ - * Put newline (if required) and account for it +/*============================================================================== + * Appending and writing */ -static inline void -vio_lc_newline(vio_line_control lc, bool required) -{ - if (required) - qiovec_push(&lc->qiov, "\r\n", 2) ; - lc->col = 0 ; - lc->line += 1 ; - if (lc->pause != 0) - { - lc->pause -= 1 ; - lc->paused = (lc->pause == 0) ; - } ; -} ; +static uint vio_lc_trim(vio_line_control lc, qiov_item item, const char* e) ; +static void vio_lc_append_line(vio_line_control lc, qiov_item item) ; /*------------------------------------------------------------------------------ * Append a lump of output to the given line control's buffers. * - * Breaks the output into lines which are no longer than the lc->width. + * Breaks the output into lines which are no longer than the lc->width. If + * that is zero (unset) we use a very large width indeed. + * + * Breaks the output into a number of screen lines, limited by the line counter. + * + * Effect of line counter: + * + * * if definite height, will output only as many screen lines as it can, + * including none at all. + * + * * if indefinite height, when the line counter is reset it is set to some + * limit on the number of screen lines to buffer in the line control. + * + * Note that this is not exact, in particular will *not* stop in the + * middle of a complete line, just because the limit of screen lines has + * been exceeded. + * + * * in any case, the counter may reach exactly 0 if the number of screen + * lines required is exactly the number of screen lines allowed. + * + * Trims trailing whitespace from each line (but not from screen lines), before + * breaking up into screen lines. + * + * NB: this means that does not output anything until gets a '\n'. + * + * See vio_lc_flush(). * * Maps '\n' to '\r''\n'. * - * Discards '\r' if found before '\n', and possibly at other times. + * Discards '\r' amongst trailing whitespace, but not otherwise. * * If lc->width == 0, use a very large width indeed. * - * If lc->pause == 0, append an indefinite number of lines + * Returns: number of bytes able to append to the line control. * - * NB: the buffer presented MUST be retained until the contents of the - * line control's buffers have been written. + * NB: the buffer presented MUST be retained until the contents of the line + * control's buffers have been written -- see vio_lc_write_nb(). * - * Returns: number of bytes able to append + * NB: the line control may buffer stuff "in hand", before it reaches the + * output iovec, either because: + * + * a) the current line is incomplete -- nothing will be output until + * a '\n' is appended, or the line control is flushed. + * + * b) the line counter was exhausted while outputting a line -- this will + * be output on the next call of vio_lc_append(), or when the line + * control is flushed. + * + * So even when the buffers that feed the line control are empty, + * a call of vio_lc_append() may push out some lines buffered in + * the line control. + * + * NB: all output from the line control ends with a newline. + * + * Incomplete lines are held in the line control until they are completed, + * or until the line control is flushed. When the line control is flushed, + * unless the incomplete line is all whitespace, a newline is appended. */ extern size_t vio_lc_append(vio_line_control lc, const void* buf, size_t len) { - const char* p ; - const char* end ; - - unsigned width ; - unsigned pause ; - - /* Prepare local width and pause */ - if (lc->width > 0) - width = lc->width ; - else - width = UINT_MAX ; - - if (lc->pause > 0) - pause = 0 ; - else - pause = 1 ; + qiov_item_t item ; + const char* bp ; + const char* be ; - lc->paused = 0 ; + /* If we have a line which was being output, but was interrupted part + * way through by line counter expiry... try to empty that out, first. + * + * This may be prevented by the line counter (partly or completely), or may + * exhaust it, which will be noticed, shortly, below. + */ + if (!qiovec_empty(lc->fragments) && !lc->incomplete) + { + qiovec_shift(lc->fragments, item) ; /* want first fragment */ + vio_lc_append_line(lc, item) ; + } ; /* Append: stop when run out of data or run out of lines */ - end = (const char*)buf + len ; - p = buf ; + be = (const char*)buf + len ; + bp = buf ; - while ((p < end) && (lc->pause != pause)) + /* If is counter exhausted, stop appending now. + * + * In vio_lc_append_line() updates the counter, and may set it -ve. + */ + while ((bp < be) && (lc->counter > 0)) { - const char* e ; - bool nl ; - int nlx ; + item->base = bp ; /* start of current fragment */ - nlx = 0 ; /* no line ending chars yet */ + /* scan for '\n' -- note that we look for a complete line, + * before worrying about the line */ + bp = memchr(bp, '\n', (be - bp)) ; - /* scan for '\n'. */ - e = memchr(p, '\n', (end - p)) ; - nl = (e != NULL) ; - if (nl) - ++nlx ; /* account for the '\n' */ - else - e = end ; /* use all there is */ + if (bp == NULL) + { + /* We do not have a '\n' -- rats */ + item->len = be - item->base ; + qiovec_push(lc->fragments, item) ; + + lc->incomplete = true ; /* have incomplete line in hand */ + + bp = be ; + break ; /* done */ + } ; - /* peel off trailing '\r'. + /* We have a '\n' -- hurrah ! * - * NB: if have not got a '\n', then this may discard a bare - * '\r' -- but bare '\r' are undefined in any case. + * Have a complete line ready to be added to the qiov -- the + * current fragment and any buffered ones. + * + * Trim off any trailing whitespace and establish the length of the + * last fragment of the current (complete) line. If the current + * fragment is all whitespace, works its way up any in hand fragments. + */ + lc->incomplete = false ; /* definitely not */ + + item->len = vio_lc_trim(lc, item, bp) ; + + ++bp ; /* step past the '\n' */ + + /* If have fragments in hand, then want to have the first fragment + * as the current fragment (currently the current fragment is the + * last fragment). */ - if ((e > p) && (*(e - 1) == '\r')) + if (!qiovec_empty(lc->fragments)) { - --e ; /* strip the '\r' */ - ++nlx ; /* but account for it */ - } + qiovec_push(lc->fragments, item) ; + qiovec_shift(lc->fragments, item) ; + } ; - /* have p..e characters and possibly nl to add to the output. + /* We are now ready to break the current line up into width + * sections, if required -- to the extend allowed by the counter. * - * Note that if enters the while, (e - p) > 0. So there is at least one - * character to add. This avoids generating a spurious line ending if - * the width has been reduced, and the next thing output is a line end. + * Have trimmed off any trailing whitespace, including back across + * any fragments. So: + * + * item = first fragment of the line */ - while ((p < e) && (lc->pause != pause)) + vio_lc_append_line(lc, item) ; + } ; + + /* Exhausted the available data or the line count */ + + return (bp - (const char*)buf) ; /* what have taken */ +} ; + +/*------------------------------------------------------------------------------ + * Flush anything which the line control has buffered. + * + * There are two possible cases: + * + * 1) had collected a complete line in vio_lc_append(), but when putting to + * the qiovec was stopped by the line counter. + * + * So have one or more screen lines buffered up, ready to go. + * + * 2) had collected an incomplete line in vio_lc_append(). + * + * That will now be flushed as if a newline had been appended, except that + * if the result would be a newline *only*, then all the whitespace is + * discarded and nothing is output. + * + * If there is nothing to flush, or an incomplete line turns out to be + * entirely whitespace, return false. + * + * Otherwise we have something to flush, and will attempt to do so. + * + * Effect of line counter: + * + * * if definite height, will output only as many screen lines as it can, + * including none at all. + * + * * if indefinite height, will output as many screen lines as it takes, + * whatever the state of the line counter. + * + * * in any case, the counter may reach exactly 0 if the number of screen + * lines required is exactly the number of screen lines allowed. + * + * Returns: true <=> have something to flush (but counter may be exhausted) + * false <=> nothing to be flushed + */ +extern bool +vio_lc_flush(vio_line_control lc) +{ + qiov_item_t item ; + + if (qiovec_count(lc->fragments) == 0) + return false ; + + /* We have something in hand. + * + * If was an incomplete line, now is the time to trim off any trailing + * whitespace -- more or less as if there had been a '\n' at this point. + */ + if (lc->incomplete) + { + lc->incomplete = false ; + + qiovec_pop(lc->fragments, item) ; + + item->len = vio_lc_trim(lc, item, item->base + item->len) ; + + if (item->len == 0) + { + assert(qiovec_empty(lc->fragments)) ; + return false ; /* it was all whitespace which has all + been discarded. */ + } ; + + qiovec_push(lc->fragments, item) ; + } ; + + /* Have something in hand, so now attempt to output it. */ + qiovec_shift(lc->fragments, item) ; + vio_lc_append_line(lc, item) ; + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * Trim trailing whitespace from given fragment -- ' ', '\t' and '\r'. + * + * If required, pops fragments until finds some non-whitespace, or runs out + * of fragments. + * + * Returns: resulting length. + */ +static uint +vio_lc_trim(vio_line_control lc, qiov_item item, const char* e) +{ + const char* p ; + + while (1) + { + p = item->base ; + + while (e > p) { - const char* t ; - unsigned col ; + char ch ; + + ch = *(e - 1) ; + if ( (ch != '\r') && (ch != ' ') && (ch != '\t') ) + return e - p ; /* <<< found non-whitespace <<< exit */ + + --e ; + } ; - col = lc->col + (e - p) ; /* NB: e > p */ + qiovec_pop(lc->fragments, item) ; /* pops a NULL if empty */ - if (col > width) + if (item->len == 0) + return 0 ; + + e = item->base + item->len ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Append line to the output qiov -- breaking it up into width sections, + * inserting newlines and looking out for and updating the line counter. + * + * If definite height, may stop immediately, without outputting anything. + * If indefinite height, will output the current line, whatever the state of + * the line counter. + * + * Note that do not trim whitespace from screen lines. + * + * At this point all trailing whitespace will have been removed from the + * original line. + * + * Requires that have previously trimmed off any trailing whitespace, including + * back across any fragments. So: + * + * item = first fragment of the line -- NOT in lc->fragments. + * + * It is possible for the first fragment to be empty (empty line !). + */ +static void +vio_lc_append_line(vio_line_control lc, qiov_item item) +{ + bool done = false ; + + while ((lc->counter > 0) || (lc->height == 0)) + { + uint take ; + + /* Take fragments or parts thereof until we have run out of output + * (which sets done == true) or reach the width. + */ + take = (lc->width > 0) ? lc->width : UINT_MAX ; + while (1) + { + if (item->len <= take) { - /* can use only part of what there is */ - if (width > lc->col) - t = p + (width - lc->col) ; - /* take to edge of screen */ + /* Use entire fragment, and step to next (if any) + * + * Note that qiovec_push() ignores zero length items, so if + * this is an empty line, will push no fragments and will stop + * here. + */ + qiovec_push(lc->qiov, item) ; + + if (qiovec_empty(lc->fragments)) + { + done = true ; /* signal all done */ + break ; + } else - t = p ; - assert(t < e) ; /* if not need to deal with nl */ + { + take -= item->len ; + qiovec_shift(lc->fragments, item) ; + } } else { - /* can use all of what there is */ - if (nlx == 2) /* if have crlf, use it */ - { - e += nlx ; /* use the crlf that's there */ - nlx = 0 ; /* used it */ - } ; + /* Use leading part of fragment, and reduce same */ + qiovec_push_this(lc->qiov, item->base, take) ; + item->base += take ; + item->len -= take ; - t = e ; /* take it all */ + break ; } ; - - assert(t >= p) ; - if (t != p) - qiovec_push(&lc->qiov, p, (t - p)) ; - - /* advance. If not taken all the line, need a crlf */ - p = t ; - - if (p < e) - vio_lc_newline(lc, 1) ; } ; - /* If taken all of line, deal with any outstanding nl and nlx */ - if (p == e) - { - if (nl) - vio_lc_newline(lc, (nlx != 0)) ; + qiovec_push(lc->qiov, lc->newline) ; - p += nlx ; /* step past '\r' or '\n' */ - } ; - } ; + /* Count down & exit if all done. */ + --lc->counter ; - /* Exhausted the available data or the line count */ - assert(p <= end) ; + if (done) + return ; + } ; - return (p - (const char*)buf) ; /* what have taken */ + /* Counter exhausted, but we are not done with the current line. + * + * unshift the current item onto the (front of) the fragments qiovec. + * (We know we can do this straightforwardly, because either the qiovec is + * empty, or what was the first item has previously been shifted off.) + */ + qiovec_unshift(lc->fragments, item) ; } ; /*------------------------------------------------------------------------------ @@ -368,15 +596,52 @@ vio_lc_append(vio_line_control lc, const void* buf, size_t len) * -1 => failed -- see errno * * Sets lc->writing if write does not complete + * + * NB: when says "all done" (or failed) the caller may release all buffered + * material that has been accepted by vio_lc_append() -- and not before. */ extern int vio_lc_write_nb(int fd, vio_line_control lc) { int ret ; - ret = qiovec_write_nb(fd, &lc->qiov) ; + ret = qiovec_write_nb(fd, lc->qiov) ; + + if (ret <= 0) + { + /* About to promise that all buffered material previously accepted by + * vio_lc_append() may now be released. + * + * Unfortunately, if we have any line fragments in hand, have to buffer + * those locally, now. + * + * NB: if have a particularly long original line, or a particularly + * narrow or short screen, it is possible to pass through here + * more than once for the same original line. + * + * In this obscure case, the line will already be buffered in + * lc->here. Happily qs_clear() does not disturb the body of the + * qstring, and qs_append_str_n will append from within its own + * body ! + */ + if (!qiovec_empty(lc->fragments)) + { + qs_set_len_nn(lc->here, 0) ; /* ready to append stuff */ + do + { + qiov_item_t item ; + + qiovec_shift(lc->fragments, item) ; + + qs_append_str_n(lc->here, item->base, item->len) ; + } + while (!qiovec_empty(lc->fragments)) ; - lc->writing = (ret > 0) ; + /* One fragment in hand, collected from all previous fragments + */ + qiovec_push_this(lc->fragments, qs_char(lc->here), qs_len(lc->here)) ; + } ; + } ; return ret ; } ; |