1229 lines
32 KiB
C
1229 lines
32 KiB
C
/* $NetBSD: refresh.c,v 1.58 2021/09/09 20:24:07 christos Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 1992, 1993
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to Berkeley by
|
|
* Christos Zoulas of Cornell University.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#if !defined(lint) && !defined(SCCSID)
|
|
#if 0
|
|
static char sccsid[] = "@(#)refresh.c 8.1 (Berkeley) 6/4/93";
|
|
#else
|
|
__RCSID("$NetBSD: refresh.c,v 1.58 2021/09/09 20:24:07 christos Exp $");
|
|
#endif
|
|
#endif /* not lint && not SCCSID */
|
|
|
|
/*
|
|
* refresh.c: Lower level screen refreshing functions
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "el.h"
|
|
|
|
static void re_nextline(EditLine *);
|
|
static void re_addc(EditLine *, wint_t);
|
|
static void re_update_line(EditLine *, wchar_t *, wchar_t *, int);
|
|
static void re_insert (EditLine *, wchar_t *, int, int, wchar_t *, int);
|
|
static void re_delete(EditLine *, wchar_t *, int, int, int);
|
|
static void re_fastputc(EditLine *, wint_t);
|
|
static void re_clear_eol(EditLine *, int, int, int);
|
|
static void re__strncopy(wchar_t *, wchar_t *, size_t);
|
|
static void re__copy_and_pad(wchar_t *, const wchar_t *, size_t);
|
|
|
|
#ifdef DEBUG_REFRESH
|
|
static void re_printstr(EditLine *, const char *, wchar_t *, wchar_t *);
|
|
#define __F el->el_errfile
|
|
#define ELRE_ASSERT(a, b, c) do \
|
|
if (/*CONSTCOND*/ a) { \
|
|
(void) fprintf b; \
|
|
c; \
|
|
} \
|
|
while (/*CONSTCOND*/0)
|
|
#define ELRE_DEBUG(a, b) ELRE_ASSERT(a,b,;)
|
|
|
|
/* re_printstr():
|
|
* Print a string on the debugging pty
|
|
*/
|
|
static void
|
|
re_printstr(EditLine *el, const char *str, wchar_t *f, wchar_t *t)
|
|
{
|
|
|
|
ELRE_DEBUG(1, (__F, "%s:\"", str));
|
|
while (f < t)
|
|
ELRE_DEBUG(1, (__F, "%c", *f++ & 0177));
|
|
ELRE_DEBUG(1, (__F, "\"\r\n"));
|
|
}
|
|
#else
|
|
#define ELRE_ASSERT(a, b, c)
|
|
#define ELRE_DEBUG(a, b)
|
|
#endif
|
|
|
|
/* re_nextline():
|
|
* Move to the next line or scroll
|
|
*/
|
|
static void
|
|
re_nextline(EditLine *el)
|
|
{
|
|
el->el_refresh.r_cursor.h = 0; /* reset it. */
|
|
|
|
/*
|
|
* If we would overflow (input is longer than terminal size),
|
|
* emulate scroll by dropping first line and shuffling the rest.
|
|
* We do this via pointer shuffling - it's safe in this case
|
|
* and we avoid memcpy().
|
|
*/
|
|
if (el->el_refresh.r_cursor.v + 1 >= el->el_terminal.t_size.v) {
|
|
int i, lins = el->el_terminal.t_size.v;
|
|
wint_t *firstline = el->el_vdisplay[0];
|
|
|
|
for(i = 1; i < lins; i++)
|
|
el->el_vdisplay[i - 1] = el->el_vdisplay[i];
|
|
|
|
firstline[0] = '\0'; /* empty the string */
|
|
el->el_vdisplay[i - 1] = firstline;
|
|
} else
|
|
el->el_refresh.r_cursor.v++;
|
|
|
|
ELRE_ASSERT(el->el_refresh.r_cursor.v >= el->el_terminal.t_size.v,
|
|
(__F, "\r\nre_putc: overflow! r_cursor.v == %d > %d\r\n",
|
|
el->el_refresh.r_cursor.v, el->el_terminal.t_size.v),
|
|
abort());
|
|
}
|
|
|
|
/* re_addc():
|
|
* Draw c, expanding tabs, control chars etc.
|
|
*/
|
|
static void
|
|
re_addc(EditLine *el, wint_t c)
|
|
{
|
|
switch (ct_chr_class(c)) {
|
|
case CHTYPE_TAB: /* expand the tab */
|
|
for (;;) {
|
|
re_putc(el, ' ', 1);
|
|
if ((el->el_refresh.r_cursor.h & 07) == 0)
|
|
break; /* go until tab stop */
|
|
}
|
|
break;
|
|
case CHTYPE_NL: {
|
|
int oldv = el->el_refresh.r_cursor.v;
|
|
re_putc(el, '\0', 0); /* assure end of line */
|
|
if (oldv == el->el_refresh.r_cursor.v) /* XXX */
|
|
re_nextline(el);
|
|
break;
|
|
}
|
|
case CHTYPE_PRINT:
|
|
re_putc(el, c, 1);
|
|
break;
|
|
default: {
|
|
wchar_t visbuf[VISUAL_WIDTH_MAX];
|
|
ssize_t i, n =
|
|
ct_visual_char(visbuf, VISUAL_WIDTH_MAX, c);
|
|
for (i = 0; n-- > 0; ++i)
|
|
re_putc(el, visbuf[i], 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* re_putliteral():
|
|
* Place the literal string given
|
|
*/
|
|
libedit_private void
|
|
re_putliteral(EditLine *el, const wchar_t *begin, const wchar_t *end)
|
|
{
|
|
coord_t *cur = &el->el_refresh.r_cursor;
|
|
wint_t c;
|
|
int sizeh = el->el_terminal.t_size.h;
|
|
int i, w;
|
|
|
|
c = literal_add(el, begin, end, &w);
|
|
if (c == 0 || w <= 0)
|
|
return;
|
|
el->el_vdisplay[cur->v][cur->h] = c;
|
|
|
|
i = w;
|
|
if (i > sizeh - cur->h) /* avoid overflow */
|
|
i = sizeh - cur->h;
|
|
while (--i > 0)
|
|
el->el_vdisplay[cur->v][cur->h + i] = MB_FILL_CHAR;
|
|
|
|
cur->h += w;
|
|
if (cur->h >= sizeh) {
|
|
/* assure end of line */
|
|
el->el_vdisplay[cur->v][sizeh] = '\0';
|
|
re_nextline(el);
|
|
}
|
|
}
|
|
|
|
/* re_putc():
|
|
* Draw the character given
|
|
*/
|
|
libedit_private void
|
|
re_putc(EditLine *el, wint_t c, int shift)
|
|
{
|
|
coord_t *cur = &el->el_refresh.r_cursor;
|
|
int i, w = wcwidth(c);
|
|
int sizeh = el->el_terminal.t_size.h;
|
|
|
|
ELRE_DEBUG(1, (__F, "printing %5x '%lc'\r\n", c, c));
|
|
if (w == -1)
|
|
w = 0;
|
|
|
|
while (shift && (cur->h + w > sizeh))
|
|
re_putc(el, ' ', 1);
|
|
|
|
el->el_vdisplay[cur->v][cur->h] = c;
|
|
/* assumes !shift is only used for single-column chars */
|
|
i = w;
|
|
while (--i > 0)
|
|
el->el_vdisplay[cur->v][cur->h + i] = MB_FILL_CHAR;
|
|
|
|
if (!shift)
|
|
return;
|
|
|
|
cur->h += w; /* advance to next place */
|
|
if (cur->h >= sizeh) {
|
|
/* assure end of line */
|
|
el->el_vdisplay[cur->v][sizeh] = '\0';
|
|
re_nextline(el);
|
|
}
|
|
}
|
|
|
|
|
|
/* re_refresh():
|
|
* draws the new virtual screen image from the current input
|
|
* line, then goes line-by-line changing the real image to the new
|
|
* virtual image. The routine to re-draw a line can be replaced
|
|
* easily in hopes of a smarter one being placed there.
|
|
*/
|
|
libedit_private void
|
|
re_refresh(EditLine *el)
|
|
{
|
|
int i, rhdiff;
|
|
wchar_t *cp, *st;
|
|
coord_t cur;
|
|
#ifdef notyet
|
|
size_t termsz;
|
|
#endif
|
|
|
|
ELRE_DEBUG(1, (__F, "el->el_line.buffer = :%ls:\r\n",
|
|
el->el_line.buffer));
|
|
|
|
literal_clear(el);
|
|
/* reset the Drawing cursor */
|
|
el->el_refresh.r_cursor.h = 0;
|
|
el->el_refresh.r_cursor.v = 0;
|
|
|
|
terminal_move_to_char(el, 0);
|
|
|
|
/* temporarily draw rprompt to calculate its size */
|
|
prompt_print(el, EL_RPROMPT);
|
|
|
|
/* reset the Drawing cursor */
|
|
el->el_refresh.r_cursor.h = 0;
|
|
el->el_refresh.r_cursor.v = 0;
|
|
|
|
if (el->el_line.cursor >= el->el_line.lastchar) {
|
|
if (el->el_map.current == el->el_map.alt
|
|
&& el->el_line.lastchar != el->el_line.buffer)
|
|
el->el_line.cursor = el->el_line.lastchar - 1;
|
|
else
|
|
el->el_line.cursor = el->el_line.lastchar;
|
|
}
|
|
|
|
cur.h = -1; /* set flag in case I'm not set */
|
|
cur.v = 0;
|
|
|
|
prompt_print(el, EL_PROMPT);
|
|
|
|
/* draw the current input buffer */
|
|
#if notyet
|
|
termsz = el->el_terminal.t_size.h * el->el_terminal.t_size.v;
|
|
if (el->el_line.lastchar - el->el_line.buffer > termsz) {
|
|
/*
|
|
* If line is longer than terminal, process only part
|
|
* of line which would influence display.
|
|
*/
|
|
size_t rem = (el->el_line.lastchar-el->el_line.buffer)%termsz;
|
|
|
|
st = el->el_line.lastchar - rem
|
|
- (termsz - (((rem / el->el_terminal.t_size.v) - 1)
|
|
* el->el_terminal.t_size.v));
|
|
} else
|
|
#endif
|
|
st = el->el_line.buffer;
|
|
|
|
for (cp = st; cp < el->el_line.lastchar; cp++) {
|
|
if (cp == el->el_line.cursor) {
|
|
int w = wcwidth(*cp);
|
|
/* save for later */
|
|
cur.h = el->el_refresh.r_cursor.h;
|
|
cur.v = el->el_refresh.r_cursor.v;
|
|
/* handle being at a linebroken doublewidth char */
|
|
if (w > 1 && el->el_refresh.r_cursor.h + w >
|
|
el->el_terminal.t_size.h) {
|
|
cur.h = 0;
|
|
cur.v++;
|
|
}
|
|
}
|
|
re_addc(el, *cp);
|
|
}
|
|
|
|
if (cur.h == -1) { /* if I haven't been set yet, I'm at the end */
|
|
cur.h = el->el_refresh.r_cursor.h;
|
|
cur.v = el->el_refresh.r_cursor.v;
|
|
}
|
|
rhdiff = el->el_terminal.t_size.h - el->el_refresh.r_cursor.h -
|
|
el->el_rprompt.p_pos.h;
|
|
if (el->el_rprompt.p_pos.h && !el->el_rprompt.p_pos.v &&
|
|
!el->el_refresh.r_cursor.v && rhdiff > 1) {
|
|
/*
|
|
* have a right-hand side prompt that will fit
|
|
* on the end of the first line with at least
|
|
* one character gap to the input buffer.
|
|
*/
|
|
while (--rhdiff > 0) /* pad out with spaces */
|
|
re_putc(el, ' ', 1);
|
|
prompt_print(el, EL_RPROMPT);
|
|
} else {
|
|
el->el_rprompt.p_pos.h = 0; /* flag "not using rprompt" */
|
|
el->el_rprompt.p_pos.v = 0;
|
|
}
|
|
|
|
re_putc(el, '\0', 0); /* make line ended with NUL, no cursor shift */
|
|
|
|
el->el_refresh.r_newcv = el->el_refresh.r_cursor.v;
|
|
|
|
ELRE_DEBUG(1, (__F,
|
|
"term.h=%d vcur.h=%d vcur.v=%d vdisplay[0]=\r\n:%80.80s:\r\n",
|
|
el->el_terminal.t_size.h, el->el_refresh.r_cursor.h,
|
|
el->el_refresh.r_cursor.v, ct_encode_string(el->el_vdisplay[0],
|
|
&el->el_scratch)));
|
|
|
|
ELRE_DEBUG(1, (__F, "updating %d lines.\r\n", el->el_refresh.r_newcv));
|
|
for (i = 0; i <= el->el_refresh.r_newcv; i++) {
|
|
/* NOTE THAT re_update_line MAY CHANGE el_display[i] */
|
|
re_update_line(el, (wchar_t *)el->el_display[i],
|
|
(wchar_t *)el->el_vdisplay[i], i);
|
|
|
|
/*
|
|
* Copy the new line to be the current one, and pad out with
|
|
* spaces to the full width of the terminal so that if we try
|
|
* moving the cursor by writing the character that is at the
|
|
* end of the screen line, it won't be a NUL or some old
|
|
* leftover stuff.
|
|
*/
|
|
re__copy_and_pad((wchar_t *)el->el_display[i],
|
|
(wchar_t *)el->el_vdisplay[i],
|
|
(size_t) el->el_terminal.t_size.h);
|
|
}
|
|
ELRE_DEBUG(1, (__F,
|
|
"\r\nel->el_refresh.r_cursor.v=%d,el->el_refresh.r_oldcv=%d i=%d\r\n",
|
|
el->el_refresh.r_cursor.v, el->el_refresh.r_oldcv, i));
|
|
|
|
if (el->el_refresh.r_oldcv > el->el_refresh.r_newcv)
|
|
for (; i <= el->el_refresh.r_oldcv; i++) {
|
|
terminal_move_to_line(el, i);
|
|
terminal_move_to_char(el, 0);
|
|
/* This wcslen should be safe even with MB_FILL_CHARs */
|
|
terminal_clear_EOL(el,
|
|
(int) wcslen((const wchar_t *)el->el_display[i]));
|
|
#ifdef DEBUG_REFRESH
|
|
terminal_overwrite(el, L"C\b", 2);
|
|
#endif /* DEBUG_REFRESH */
|
|
el->el_display[i][0] = '\0';
|
|
}
|
|
|
|
el->el_refresh.r_oldcv = el->el_refresh.r_newcv; /* set for next time */
|
|
ELRE_DEBUG(1, (__F,
|
|
"\r\ncursor.h = %d, cursor.v = %d, cur.h = %d, cur.v = %d\r\n",
|
|
el->el_refresh.r_cursor.h, el->el_refresh.r_cursor.v,
|
|
cur.h, cur.v));
|
|
terminal_move_to_line(el, cur.v); /* go to where the cursor is */
|
|
terminal_move_to_char(el, cur.h);
|
|
}
|
|
|
|
|
|
/* re_goto_bottom():
|
|
* used to go to last used screen line
|
|
*/
|
|
libedit_private void
|
|
re_goto_bottom(EditLine *el)
|
|
{
|
|
|
|
terminal_move_to_line(el, el->el_refresh.r_oldcv);
|
|
terminal__putc(el, '\n');
|
|
re_clear_display(el);
|
|
terminal__flush(el);
|
|
}
|
|
|
|
|
|
/* re_insert():
|
|
* insert num characters of s into d (in front of the character)
|
|
* at dat, maximum length of d is dlen
|
|
*/
|
|
static void
|
|
/*ARGSUSED*/
|
|
re_insert(EditLine *el __attribute__((__unused__)),
|
|
wchar_t *d, int dat, int dlen, wchar_t *s, int num)
|
|
{
|
|
wchar_t *a, *b;
|
|
|
|
if (num <= 0)
|
|
return;
|
|
if (num > dlen - dat)
|
|
num = dlen - dat;
|
|
|
|
ELRE_DEBUG(1,
|
|
(__F, "re_insert() starting: %d at %d max %d, d == \"%s\"\n",
|
|
num, dat, dlen, ct_encode_string(d, &el->el_scratch)));
|
|
ELRE_DEBUG(1, (__F, "s == \"%s\"\n", ct_encode_string(s,
|
|
&el->el_scratch)));
|
|
|
|
/* open up the space for num chars */
|
|
if (num > 0) {
|
|
b = d + dlen - 1;
|
|
a = b - num;
|
|
while (a >= &d[dat])
|
|
*b-- = *a--;
|
|
d[dlen] = '\0'; /* just in case */
|
|
}
|
|
|
|
ELRE_DEBUG(1, (__F,
|
|
"re_insert() after insert: %d at %d max %d, d == \"%s\"\n",
|
|
num, dat, dlen, ct_encode_string(d, &el->el_scratch)));
|
|
ELRE_DEBUG(1, (__F, "s == \"%s\"\n", ct_encode_string(s,
|
|
&el->el_scratch)));
|
|
|
|
/* copy the characters */
|
|
for (a = d + dat; (a < d + dlen) && (num > 0); num--)
|
|
*a++ = *s++;
|
|
|
|
#ifdef notyet
|
|
/* ct_encode_string() uses a static buffer, so we can't conveniently
|
|
* encode both d & s here */
|
|
ELRE_DEBUG(1,
|
|
(__F, "re_insert() after copy: %d at %d max %d, %s == \"%s\"\n",
|
|
num, dat, dlen, d, s));
|
|
ELRE_DEBUG(1, (__F, "s == \"%s\"\n", s));
|
|
#endif
|
|
}
|
|
|
|
|
|
/* re_delete():
|
|
* delete num characters d at dat, maximum length of d is dlen
|
|
*/
|
|
static void
|
|
/*ARGSUSED*/
|
|
re_delete(EditLine *el __attribute__((__unused__)),
|
|
wchar_t *d, int dat, int dlen, int num)
|
|
{
|
|
wchar_t *a, *b;
|
|
|
|
if (num <= 0)
|
|
return;
|
|
if (dat + num >= dlen) {
|
|
d[dat] = '\0';
|
|
return;
|
|
}
|
|
ELRE_DEBUG(1,
|
|
(__F, "re_delete() starting: %d at %d max %d, d == \"%s\"\n",
|
|
num, dat, dlen, ct_encode_string(d, &el->el_scratch)));
|
|
|
|
/* open up the space for num chars */
|
|
if (num > 0) {
|
|
b = d + dat;
|
|
a = b + num;
|
|
while (a < &d[dlen])
|
|
*b++ = *a++;
|
|
d[dlen] = '\0'; /* just in case */
|
|
}
|
|
ELRE_DEBUG(1,
|
|
(__F, "re_delete() after delete: %d at %d max %d, d == \"%s\"\n",
|
|
num, dat, dlen, ct_encode_string(d, &el->el_scratch)));
|
|
}
|
|
|
|
|
|
/* re__strncopy():
|
|
* Like strncpy without padding.
|
|
*/
|
|
static void
|
|
re__strncopy(wchar_t *a, wchar_t *b, size_t n)
|
|
{
|
|
|
|
while (n-- && *b)
|
|
*a++ = *b++;
|
|
}
|
|
|
|
/* re_clear_eol():
|
|
* Find the number of characters we need to clear till the end of line
|
|
* in order to make sure that we have cleared the previous contents of
|
|
* the line. fx and sx is the number of characters inserted or deleted
|
|
* in the first or second diff, diff is the difference between the
|
|
* number of characters between the new and old line.
|
|
*/
|
|
static void
|
|
re_clear_eol(EditLine *el, int fx, int sx, int diff)
|
|
{
|
|
|
|
ELRE_DEBUG(1, (__F, "re_clear_eol sx %d, fx %d, diff %d\n",
|
|
sx, fx, diff));
|
|
|
|
if (fx < 0)
|
|
fx = -fx;
|
|
if (sx < 0)
|
|
sx = -sx;
|
|
if (fx > diff)
|
|
diff = fx;
|
|
if (sx > diff)
|
|
diff = sx;
|
|
|
|
ELRE_DEBUG(1, (__F, "re_clear_eol %d\n", diff));
|
|
terminal_clear_EOL(el, diff);
|
|
}
|
|
|
|
/*****************************************************************
|
|
re_update_line() is based on finding the middle difference of each line
|
|
on the screen; vis:
|
|
|
|
/old first difference
|
|
/beginning of line | /old last same /old EOL
|
|
v v v v
|
|
old: eddie> Oh, my little gruntle-buggy is to me, as lurgid as
|
|
new: eddie> Oh, my little buggy says to me, as lurgid as
|
|
^ ^ ^ ^
|
|
\beginning of line | \new last same \new end of line
|
|
\new first difference
|
|
|
|
all are character pointers for the sake of speed. Special cases for
|
|
no differences, as well as for end of line additions must be handled.
|
|
**************************************************************** */
|
|
|
|
/* Minimum at which doing an insert it "worth it". This should be about
|
|
* half the "cost" of going into insert mode, inserting a character, and
|
|
* going back out. This should really be calculated from the termcap
|
|
* data... For the moment, a good number for ANSI terminals.
|
|
*/
|
|
#define MIN_END_KEEP 4
|
|
|
|
static void
|
|
re_update_line(EditLine *el, wchar_t *old, wchar_t *new, int i)
|
|
{
|
|
wchar_t *o, *n, *p, c;
|
|
wchar_t *ofd, *ols, *oe, *nfd, *nls, *ne;
|
|
wchar_t *osb, *ose, *nsb, *nse;
|
|
int fx, sx;
|
|
size_t len;
|
|
|
|
/*
|
|
* find first diff
|
|
*/
|
|
for (o = old, n = new; *o && (*o == *n); o++, n++)
|
|
continue;
|
|
ofd = o;
|
|
nfd = n;
|
|
|
|
/*
|
|
* Find the end of both old and new
|
|
*/
|
|
while (*o)
|
|
o++;
|
|
/*
|
|
* Remove any trailing blanks off of the end, being careful not to
|
|
* back up past the beginning.
|
|
*/
|
|
while (ofd < o) {
|
|
if (o[-1] != ' ')
|
|
break;
|
|
o--;
|
|
}
|
|
oe = o;
|
|
*oe = '\0';
|
|
|
|
while (*n)
|
|
n++;
|
|
|
|
/* remove blanks from end of new */
|
|
while (nfd < n) {
|
|
if (n[-1] != ' ')
|
|
break;
|
|
n--;
|
|
}
|
|
ne = n;
|
|
*ne = '\0';
|
|
|
|
/*
|
|
* if no diff, continue to next line of redraw
|
|
*/
|
|
if (*ofd == '\0' && *nfd == '\0') {
|
|
ELRE_DEBUG(1, (__F, "no difference.\r\n"));
|
|
return;
|
|
}
|
|
/*
|
|
* find last same pointer
|
|
*/
|
|
while ((o > ofd) && (n > nfd) && (*--o == *--n))
|
|
continue;
|
|
ols = ++o;
|
|
nls = ++n;
|
|
|
|
/*
|
|
* find same beginning and same end
|
|
*/
|
|
osb = ols;
|
|
nsb = nls;
|
|
ose = ols;
|
|
nse = nls;
|
|
|
|
/*
|
|
* case 1: insert: scan from nfd to nls looking for *ofd
|
|
*/
|
|
if (*ofd) {
|
|
for (c = *ofd, n = nfd; n < nls; n++) {
|
|
if (c == *n) {
|
|
for (o = ofd, p = n;
|
|
p < nls && o < ols && *o == *p;
|
|
o++, p++)
|
|
continue;
|
|
/*
|
|
* if the new match is longer and it's worth
|
|
* keeping, then we take it
|
|
*/
|
|
if (((nse - nsb) < (p - n)) &&
|
|
(2 * (p - n) > n - nfd)) {
|
|
nsb = n;
|
|
nse = p;
|
|
osb = ofd;
|
|
ose = o;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* case 2: delete: scan from ofd to ols looking for *nfd
|
|
*/
|
|
if (*nfd) {
|
|
for (c = *nfd, o = ofd; o < ols; o++) {
|
|
if (c == *o) {
|
|
for (n = nfd, p = o;
|
|
p < ols && n < nls && *p == *n;
|
|
p++, n++)
|
|
continue;
|
|
/*
|
|
* if the new match is longer and it's worth
|
|
* keeping, then we take it
|
|
*/
|
|
if (((ose - osb) < (p - o)) &&
|
|
(2 * (p - o) > o - ofd)) {
|
|
nsb = nfd;
|
|
nse = n;
|
|
osb = o;
|
|
ose = p;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* Pragmatics I: If old trailing whitespace or not enough characters to
|
|
* save to be worth it, then don't save the last same info.
|
|
*/
|
|
if ((oe - ols) < MIN_END_KEEP) {
|
|
ols = oe;
|
|
nls = ne;
|
|
}
|
|
/*
|
|
* Pragmatics II: if the terminal isn't smart enough, make the data
|
|
* dumber so the smart update doesn't try anything fancy
|
|
*/
|
|
|
|
/*
|
|
* fx is the number of characters we need to insert/delete: in the
|
|
* beginning to bring the two same begins together
|
|
*/
|
|
fx = (int)((nsb - nfd) - (osb - ofd));
|
|
/*
|
|
* sx is the number of characters we need to insert/delete: in the
|
|
* end to bring the two same last parts together
|
|
*/
|
|
sx = (int)((nls - nse) - (ols - ose));
|
|
|
|
if (!EL_CAN_INSERT) {
|
|
if (fx > 0) {
|
|
osb = ols;
|
|
ose = ols;
|
|
nsb = nls;
|
|
nse = nls;
|
|
}
|
|
if (sx > 0) {
|
|
ols = oe;
|
|
nls = ne;
|
|
}
|
|
if ((ols - ofd) < (nls - nfd)) {
|
|
ols = oe;
|
|
nls = ne;
|
|
}
|
|
}
|
|
if (!EL_CAN_DELETE) {
|
|
if (fx < 0) {
|
|
osb = ols;
|
|
ose = ols;
|
|
nsb = nls;
|
|
nse = nls;
|
|
}
|
|
if (sx < 0) {
|
|
ols = oe;
|
|
nls = ne;
|
|
}
|
|
if ((ols - ofd) > (nls - nfd)) {
|
|
ols = oe;
|
|
nls = ne;
|
|
}
|
|
}
|
|
/*
|
|
* Pragmatics III: make sure the middle shifted pointers are correct if
|
|
* they don't point to anything (we may have moved ols or nls).
|
|
*/
|
|
/* if the change isn't worth it, don't bother */
|
|
/* was: if (osb == ose) */
|
|
if ((ose - osb) < MIN_END_KEEP) {
|
|
osb = ols;
|
|
ose = ols;
|
|
nsb = nls;
|
|
nse = nls;
|
|
}
|
|
/*
|
|
* Now that we are done with pragmatics we recompute fx, sx
|
|
*/
|
|
fx = (int)((nsb - nfd) - (osb - ofd));
|
|
sx = (int)((nls - nse) - (ols - ose));
|
|
|
|
ELRE_DEBUG(1, (__F, "fx %d, sx %d\n", fx, sx));
|
|
ELRE_DEBUG(1, (__F, "ofd %td, osb %td, ose %td, ols %td, oe %td\n",
|
|
ofd - old, osb - old, ose - old, ols - old, oe - old));
|
|
ELRE_DEBUG(1, (__F, "nfd %td, nsb %td, nse %td, nls %td, ne %td\n",
|
|
nfd - new, nsb - new, nse - new, nls - new, ne - new));
|
|
ELRE_DEBUG(1, (__F,
|
|
"xxx-xxx:\"00000000001111111111222222222233333333334\"\r\n"));
|
|
ELRE_DEBUG(1, (__F,
|
|
"xxx-xxx:\"01234567890123456789012345678901234567890\"\r\n"));
|
|
#ifdef DEBUG_REFRESH
|
|
re_printstr(el, "old- oe", old, oe);
|
|
re_printstr(el, "new- ne", new, ne);
|
|
re_printstr(el, "old-ofd", old, ofd);
|
|
re_printstr(el, "new-nfd", new, nfd);
|
|
re_printstr(el, "ofd-osb", ofd, osb);
|
|
re_printstr(el, "nfd-nsb", nfd, nsb);
|
|
re_printstr(el, "osb-ose", osb, ose);
|
|
re_printstr(el, "nsb-nse", nsb, nse);
|
|
re_printstr(el, "ose-ols", ose, ols);
|
|
re_printstr(el, "nse-nls", nse, nls);
|
|
re_printstr(el, "ols- oe", ols, oe);
|
|
re_printstr(el, "nls- ne", nls, ne);
|
|
#endif /* DEBUG_REFRESH */
|
|
|
|
/*
|
|
* el_cursor.v to this line i MUST be in this routine so that if we
|
|
* don't have to change the line, we don't move to it. el_cursor.h to
|
|
* first diff char
|
|
*/
|
|
terminal_move_to_line(el, i);
|
|
|
|
/*
|
|
* at this point we have something like this:
|
|
*
|
|
* /old /ofd /osb /ose /ols /oe
|
|
* v.....................v v..................v v........v
|
|
* eddie> Oh, my fredded gruntle-buggy is to me, as foo var lurgid as
|
|
* eddie> Oh, my fredded quiux buggy is to me, as gruntle-lurgid as
|
|
* ^.....................^ ^..................^ ^........^
|
|
* \new \nfd \nsb \nse \nls \ne
|
|
*
|
|
* fx is the difference in length between the chars between nfd and
|
|
* nsb, and the chars between ofd and osb, and is thus the number of
|
|
* characters to delete if < 0 (new is shorter than old, as above),
|
|
* or insert (new is longer than short).
|
|
*
|
|
* sx is the same for the second differences.
|
|
*/
|
|
|
|
/*
|
|
* if we have a net insert on the first difference, AND inserting the
|
|
* net amount ((nsb-nfd) - (osb-ofd)) won't push the last useful
|
|
* character (which is ne if nls != ne, otherwise is nse) off the edge
|
|
* of the screen (el->el_terminal.t_size.h) else we do the deletes first
|
|
* so that we keep everything we need to.
|
|
*/
|
|
|
|
/*
|
|
* if the last same is the same like the end, there is no last same
|
|
* part, otherwise we want to keep the last same part set p to the
|
|
* last useful old character
|
|
*/
|
|
p = (ols != oe) ? oe : ose;
|
|
|
|
/*
|
|
* if (There is a diffence in the beginning) && (we need to insert
|
|
* characters) && (the number of characters to insert is less than
|
|
* the term width)
|
|
* We need to do an insert!
|
|
* else if (we need to delete characters)
|
|
* We need to delete characters!
|
|
* else
|
|
* No insert or delete
|
|
*/
|
|
if ((nsb != nfd) && fx > 0 &&
|
|
((p - old) + fx <= el->el_terminal.t_size.h)) {
|
|
ELRE_DEBUG(1,
|
|
(__F, "first diff insert at %td...\r\n", nfd - new));
|
|
/*
|
|
* Move to the first char to insert, where the first diff is.
|
|
*/
|
|
terminal_move_to_char(el, (int)(nfd - new));
|
|
/*
|
|
* Check if we have stuff to keep at end
|
|
*/
|
|
if (nsb != ne) {
|
|
ELRE_DEBUG(1, (__F, "with stuff to keep at end\r\n"));
|
|
/*
|
|
* insert fx chars of new starting at nfd
|
|
*/
|
|
if (fx > 0) {
|
|
ELRE_DEBUG(!EL_CAN_INSERT, (__F,
|
|
"ERROR: cannot insert in early first diff\n"));
|
|
terminal_insertwrite(el, nfd, fx);
|
|
re_insert(el, old, (int)(ofd - old),
|
|
el->el_terminal.t_size.h, nfd, fx);
|
|
}
|
|
/*
|
|
* write (nsb-nfd) - fx chars of new starting at
|
|
* (nfd + fx)
|
|
*/
|
|
len = (size_t) ((nsb - nfd) - fx);
|
|
terminal_overwrite(el, (nfd + fx), len);
|
|
re__strncopy(ofd + fx, nfd + fx, len);
|
|
} else {
|
|
ELRE_DEBUG(1, (__F, "without anything to save\r\n"));
|
|
len = (size_t)(nsb - nfd);
|
|
terminal_overwrite(el, nfd, len);
|
|
re__strncopy(ofd, nfd, len);
|
|
/*
|
|
* Done
|
|
*/
|
|
return;
|
|
}
|
|
} else if (fx < 0) {
|
|
ELRE_DEBUG(1,
|
|
(__F, "first diff delete at %td...\r\n", ofd - old));
|
|
/*
|
|
* move to the first char to delete where the first diff is
|
|
*/
|
|
terminal_move_to_char(el, (int)(ofd - old));
|
|
/*
|
|
* Check if we have stuff to save
|
|
*/
|
|
if (osb != oe) {
|
|
ELRE_DEBUG(1, (__F, "with stuff to save at end\r\n"));
|
|
/*
|
|
* fx is less than zero *always* here but we check
|
|
* for code symmetry
|
|
*/
|
|
if (fx < 0) {
|
|
ELRE_DEBUG(!EL_CAN_DELETE, (__F,
|
|
"ERROR: cannot delete in first diff\n"));
|
|
terminal_deletechars(el, -fx);
|
|
re_delete(el, old, (int)(ofd - old),
|
|
el->el_terminal.t_size.h, -fx);
|
|
}
|
|
/*
|
|
* write (nsb-nfd) chars of new starting at nfd
|
|
*/
|
|
len = (size_t) (nsb - nfd);
|
|
terminal_overwrite(el, nfd, len);
|
|
re__strncopy(ofd, nfd, len);
|
|
|
|
} else {
|
|
ELRE_DEBUG(1, (__F,
|
|
"but with nothing left to save\r\n"));
|
|
/*
|
|
* write (nsb-nfd) chars of new starting at nfd
|
|
*/
|
|
terminal_overwrite(el, nfd, (size_t)(nsb - nfd));
|
|
re_clear_eol(el, fx, sx,
|
|
(int)((oe - old) - (ne - new)));
|
|
/*
|
|
* Done
|
|
*/
|
|
return;
|
|
}
|
|
} else
|
|
fx = 0;
|
|
|
|
if (sx < 0 && (ose - old) + fx < el->el_terminal.t_size.h) {
|
|
ELRE_DEBUG(1, (__F,
|
|
"second diff delete at %td...\r\n", (ose - old) + fx));
|
|
/*
|
|
* Check if we have stuff to delete
|
|
*/
|
|
/*
|
|
* fx is the number of characters inserted (+) or deleted (-)
|
|
*/
|
|
|
|
terminal_move_to_char(el, (int)((ose - old) + fx));
|
|
/*
|
|
* Check if we have stuff to save
|
|
*/
|
|
if (ols != oe) {
|
|
ELRE_DEBUG(1, (__F, "with stuff to save at end\r\n"));
|
|
/*
|
|
* Again a duplicate test.
|
|
*/
|
|
if (sx < 0) {
|
|
ELRE_DEBUG(!EL_CAN_DELETE, (__F,
|
|
"ERROR: cannot delete in second diff\n"));
|
|
terminal_deletechars(el, -sx);
|
|
}
|
|
/*
|
|
* write (nls-nse) chars of new starting at nse
|
|
*/
|
|
terminal_overwrite(el, nse, (size_t)(nls - nse));
|
|
} else {
|
|
ELRE_DEBUG(1, (__F,
|
|
"but with nothing left to save\r\n"));
|
|
terminal_overwrite(el, nse, (size_t)(nls - nse));
|
|
re_clear_eol(el, fx, sx,
|
|
(int)((oe - old) - (ne - new)));
|
|
}
|
|
}
|
|
/*
|
|
* if we have a first insert AND WE HAVEN'T ALREADY DONE IT...
|
|
*/
|
|
if ((nsb != nfd) && (osb - ofd) <= (nsb - nfd) && (fx == 0)) {
|
|
ELRE_DEBUG(1, (__F, "late first diff insert at %td...\r\n",
|
|
nfd - new));
|
|
|
|
terminal_move_to_char(el, (int)(nfd - new));
|
|
/*
|
|
* Check if we have stuff to keep at the end
|
|
*/
|
|
if (nsb != ne) {
|
|
ELRE_DEBUG(1, (__F, "with stuff to keep at end\r\n"));
|
|
/*
|
|
* We have to recalculate fx here because we set it
|
|
* to zero above as a flag saying that we hadn't done
|
|
* an early first insert.
|
|
*/
|
|
fx = (int)((nsb - nfd) - (osb - ofd));
|
|
if (fx > 0) {
|
|
/*
|
|
* insert fx chars of new starting at nfd
|
|
*/
|
|
ELRE_DEBUG(!EL_CAN_INSERT, (__F,
|
|
"ERROR: cannot insert in late first diff\n"));
|
|
terminal_insertwrite(el, nfd, fx);
|
|
re_insert(el, old, (int)(ofd - old),
|
|
el->el_terminal.t_size.h, nfd, fx);
|
|
}
|
|
/*
|
|
* write (nsb-nfd) - fx chars of new starting at
|
|
* (nfd + fx)
|
|
*/
|
|
len = (size_t) ((nsb - nfd) - fx);
|
|
terminal_overwrite(el, (nfd + fx), len);
|
|
re__strncopy(ofd + fx, nfd + fx, len);
|
|
} else {
|
|
ELRE_DEBUG(1, (__F, "without anything to save\r\n"));
|
|
len = (size_t) (nsb - nfd);
|
|
terminal_overwrite(el, nfd, len);
|
|
re__strncopy(ofd, nfd, len);
|
|
}
|
|
}
|
|
/*
|
|
* line is now NEW up to nse
|
|
*/
|
|
if (sx >= 0) {
|
|
ELRE_DEBUG(1, (__F,
|
|
"second diff insert at %d...\r\n", (int)(nse - new)));
|
|
terminal_move_to_char(el, (int)(nse - new));
|
|
if (ols != oe) {
|
|
ELRE_DEBUG(1, (__F, "with stuff to keep at end\r\n"));
|
|
if (sx > 0) {
|
|
/* insert sx chars of new starting at nse */
|
|
ELRE_DEBUG(!EL_CAN_INSERT, (__F,
|
|
"ERROR: cannot insert in second diff\n"));
|
|
terminal_insertwrite(el, nse, sx);
|
|
}
|
|
/*
|
|
* write (nls-nse) - sx chars of new starting at
|
|
* (nse + sx)
|
|
*/
|
|
terminal_overwrite(el, (nse + sx),
|
|
(size_t)((nls - nse) - sx));
|
|
} else {
|
|
ELRE_DEBUG(1, (__F, "without anything to save\r\n"));
|
|
terminal_overwrite(el, nse, (size_t)(nls - nse));
|
|
|
|
/*
|
|
* No need to do a clear-to-end here because we were
|
|
* doing a second insert, so we will have over
|
|
* written all of the old string.
|
|
*/
|
|
}
|
|
}
|
|
ELRE_DEBUG(1, (__F, "done.\r\n"));
|
|
}
|
|
|
|
|
|
/* re__copy_and_pad():
|
|
* Copy string and pad with spaces
|
|
*/
|
|
static void
|
|
re__copy_and_pad(wchar_t *dst, const wchar_t *src, size_t width)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < width; i++) {
|
|
if (*src == '\0')
|
|
break;
|
|
*dst++ = *src++;
|
|
}
|
|
|
|
for (; i < width; i++)
|
|
*dst++ = ' ';
|
|
|
|
*dst = '\0';
|
|
}
|
|
|
|
|
|
/* re_refresh_cursor():
|
|
* Move to the new cursor position
|
|
*/
|
|
libedit_private void
|
|
re_refresh_cursor(EditLine *el)
|
|
{
|
|
wchar_t *cp;
|
|
int h, v, th, w;
|
|
|
|
if (el->el_line.cursor >= el->el_line.lastchar) {
|
|
if (el->el_map.current == el->el_map.alt
|
|
&& el->el_line.lastchar != el->el_line.buffer)
|
|
el->el_line.cursor = el->el_line.lastchar - 1;
|
|
else
|
|
el->el_line.cursor = el->el_line.lastchar;
|
|
}
|
|
|
|
/* first we must find where the cursor is... */
|
|
h = el->el_prompt.p_pos.h;
|
|
v = el->el_prompt.p_pos.v;
|
|
th = el->el_terminal.t_size.h; /* optimize for speed */
|
|
|
|
/* do input buffer to el->el_line.cursor */
|
|
for (cp = el->el_line.buffer; cp < el->el_line.cursor; cp++) {
|
|
switch (ct_chr_class(*cp)) {
|
|
case CHTYPE_NL: /* handle newline in data part too */
|
|
h = 0;
|
|
v++;
|
|
break;
|
|
case CHTYPE_TAB: /* if a tab, to next tab stop */
|
|
while (++h & 07)
|
|
continue;
|
|
break;
|
|
default:
|
|
w = wcwidth(*cp);
|
|
if (w > 1 && h + w > th) { /* won't fit on line */
|
|
h = 0;
|
|
v++;
|
|
}
|
|
h += ct_visual_width(*cp);
|
|
break;
|
|
}
|
|
|
|
if (h >= th) { /* check, extra long tabs picked up here also */
|
|
h -= th;
|
|
v++;
|
|
}
|
|
}
|
|
/* if we have a next character, and it's a doublewidth one, we need to
|
|
* check whether we need to linebreak for it to fit */
|
|
if (cp < el->el_line.lastchar && (w = wcwidth(*cp)) > 1)
|
|
if (h + w > th) {
|
|
h = 0;
|
|
v++;
|
|
}
|
|
|
|
/* now go there */
|
|
terminal_move_to_line(el, v);
|
|
terminal_move_to_char(el, h);
|
|
terminal__flush(el);
|
|
}
|
|
|
|
|
|
/* re_fastputc():
|
|
* Add a character fast.
|
|
*/
|
|
static void
|
|
re_fastputc(EditLine *el, wint_t c)
|
|
{
|
|
wint_t *lastline;
|
|
int w;
|
|
|
|
w = wcwidth(c);
|
|
while (w > 1 && el->el_cursor.h + w > el->el_terminal.t_size.h)
|
|
re_fastputc(el, ' ');
|
|
|
|
terminal__putc(el, c);
|
|
el->el_display[el->el_cursor.v][el->el_cursor.h++] = c;
|
|
while (--w > 0)
|
|
el->el_display[el->el_cursor.v][el->el_cursor.h++]
|
|
= MB_FILL_CHAR;
|
|
|
|
if (el->el_cursor.h >= el->el_terminal.t_size.h) {
|
|
/* if we must overflow */
|
|
el->el_cursor.h = 0;
|
|
|
|
/*
|
|
* If we would overflow (input is longer than terminal size),
|
|
* emulate scroll by dropping first line and shuffling the rest.
|
|
* We do this via pointer shuffling - it's safe in this case
|
|
* and we avoid memcpy().
|
|
*/
|
|
if (el->el_cursor.v + 1 >= el->el_terminal.t_size.v) {
|
|
int i, lins = el->el_terminal.t_size.v;
|
|
|
|
lastline = el->el_display[0];
|
|
for(i = 1; i < lins; i++)
|
|
el->el_display[i - 1] = el->el_display[i];
|
|
|
|
el->el_display[i - 1] = lastline;
|
|
} else {
|
|
el->el_cursor.v++;
|
|
lastline = el->el_display[++el->el_refresh.r_oldcv];
|
|
}
|
|
re__copy_and_pad((wchar_t *)lastline, L"",
|
|
(size_t)el->el_terminal.t_size.h);
|
|
|
|
if (EL_HAS_AUTO_MARGINS) {
|
|
if (EL_HAS_MAGIC_MARGINS) {
|
|
terminal__putc(el, ' ');
|
|
terminal__putc(el, '\b');
|
|
}
|
|
} else {
|
|
terminal__putc(el, '\r');
|
|
terminal__putc(el, '\n');
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* re_fastaddc():
|
|
* we added just one char, handle it fast.
|
|
* Assumes that screen cursor == real cursor
|
|
*/
|
|
libedit_private void
|
|
re_fastaddc(EditLine *el)
|
|
{
|
|
wchar_t c;
|
|
int rhdiff;
|
|
|
|
c = el->el_line.cursor[-1];
|
|
|
|
if (c == '\t' || el->el_line.cursor != el->el_line.lastchar) {
|
|
re_refresh(el); /* too hard to handle */
|
|
return;
|
|
}
|
|
rhdiff = el->el_terminal.t_size.h - el->el_cursor.h -
|
|
el->el_rprompt.p_pos.h;
|
|
if (el->el_rprompt.p_pos.h && rhdiff < 3) {
|
|
re_refresh(el); /* clear out rprompt if less than 1 char gap */
|
|
return;
|
|
} /* else (only do at end of line, no TAB) */
|
|
switch (ct_chr_class(c)) {
|
|
case CHTYPE_TAB: /* already handled, should never happen here */
|
|
break;
|
|
case CHTYPE_NL:
|
|
case CHTYPE_PRINT:
|
|
re_fastputc(el, c);
|
|
break;
|
|
case CHTYPE_ASCIICTL:
|
|
case CHTYPE_NONPRINT: {
|
|
wchar_t visbuf[VISUAL_WIDTH_MAX];
|
|
ssize_t i, n =
|
|
ct_visual_char(visbuf, VISUAL_WIDTH_MAX, c);
|
|
for (i = 0; n-- > 0; ++i)
|
|
re_fastputc(el, visbuf[i]);
|
|
break;
|
|
}
|
|
}
|
|
terminal__flush(el);
|
|
}
|
|
|
|
|
|
/* re_clear_display():
|
|
* clear the screen buffers so that new new prompt starts fresh.
|
|
*/
|
|
libedit_private void
|
|
re_clear_display(EditLine *el)
|
|
{
|
|
int i;
|
|
|
|
el->el_cursor.v = 0;
|
|
el->el_cursor.h = 0;
|
|
for (i = 0; i < el->el_terminal.t_size.v; i++)
|
|
el->el_display[i][0] = '\0';
|
|
el->el_refresh.r_oldcv = 0;
|
|
}
|
|
|
|
|
|
/* re_clear_lines():
|
|
* Make sure all lines are *really* blank
|
|
*/
|
|
libedit_private void
|
|
re_clear_lines(EditLine *el)
|
|
{
|
|
|
|
if (EL_CAN_CEOL) {
|
|
int i;
|
|
for (i = el->el_refresh.r_oldcv; i >= 0; i--) {
|
|
/* for each line on the screen */
|
|
terminal_move_to_line(el, i);
|
|
terminal_move_to_char(el, 0);
|
|
terminal_clear_EOL(el, el->el_terminal.t_size.h);
|
|
}
|
|
} else {
|
|
terminal_move_to_line(el, el->el_refresh.r_oldcv);
|
|
/* go to last line */
|
|
terminal__putc(el, '\r'); /* go to BOL */
|
|
terminal__putc(el, '\n'); /* go to new line */
|
|
}
|
|
}
|