Download raw body.
bin/ksh: fix emacs/vi line editor getting wrong terminal width
bin/ksh: fix emacs/vi line editor getting wrong terminal width
bin/ksh: fix emacs/vi line editor getting wrong terminal width
On Tue, 20 Jan 2026 22:16:28 +0100,
Helg <helg-openbsd@gmx.de> wrote:
>
> It works in both emacs and vi mode.
>
> However, in testing this, I've found another bug.
>
> I've never looked too closely at this but here's how I understand
> it should work. There are 3 possibilities when an indicator should
> be displayed because some text is not visible.
>
> "> " There is more text to the right
> "< " There is more text to the left
> "* " There is more text in both directions (vi mode shows + instead)
>
> The "> " does not appear at the right edge of the window but about
> 10 characters from the edge. This sometime results in the > appearing
> in the middle of text. It should be output in the same location as
> < and *.
>
> Interestingly, vi mode always displays the indicator 10 characters
> from the right edge of the window. This is too far indented for my
> taste but, either way, it should be consistent in all cases.
>
> Any chance you can fix this too?
>
Thanks for reports.
I had started to dig into it, and it leads to a pandora's box.
So, here I've attached tow patches:
1. fix for terminal width
2. dances around string length and UTF-8
(2) should be applied on (1).
I've tested both patched in vi and emacs mode, and it looks works correctly.
But code of ksh is tricky and I defently need more testing.
Thus, if it commited, I think that it should be in two stages in some delay
between (1) and (2).
--
wbr, Kirill
From 85a0ff1f68dc3c7909d27362bb55d8aea1e9350b Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill@korins.ky>
Date: Wed, 21 Jan 2026 01:16:04 +0100
Subject: [PATCH 1/2] bin/ksh: fix emacs/vi line editor getting wrong terminal
width
Xterm and other terminal emulators resize the pty after starting the
shell. x_init() queries terminal size at startup before the resize
occurs, so x_cols is kept at default 80 colums.
SIGWINCH arrives but check_sigwinch() wasn't called until after command
entry. This causes the line editor thinks that screen is 80 columns,
than leads to show '<' and truncate input before reaching the actual
terminal edge for large window, and wired behaiour for small one.
Here, I call check_sigwinch() when entering edit mode and on EINTR in
x_getc(). Callback lets emacs/vi recalculate display width and redraw.
---
bin/ksh/edit.c | 14 +++++++++++---
bin/ksh/edit.h | 2 ++
bin/ksh/emacs.c | 42 +++++++++++++++++++++++++++++++++++++++++-
bin/ksh/vi.c | 46 +++++++++++++++++++++++++++++++++++++++++-----
4 files changed, 95 insertions(+), 9 deletions(-)
diff --git a/bin/ksh/edit.c b/bin/ksh/edit.c
index 3ed69e5375d..83bab8ffbad 100644
--- a/bin/ksh/edit.c
+++ b/bin/ksh/edit.c
@@ -25,7 +25,8 @@ X_chars edchars;
static void x_sigwinch(int);
volatile sig_atomic_t got_sigwinch;
-static void check_sigwinch(void);
+void check_sigwinch(void);
+void (*x_resize_cb)(void); /* callback for resize during edit */
static int x_file_glob(int, const char *, int, char ***);
static int x_command_glob(int, const char *, int, char ***);
@@ -58,7 +59,7 @@ x_sigwinch(int sig)
got_sigwinch = 1;
}
-static void
+void
check_sigwinch(void)
{
if (got_sigwinch) {
@@ -75,11 +76,16 @@ check_sigwinch(void)
* see the change cause the environ doesn't change.
*/
if (ws.ws_col) {
+ int old_cols = x_cols;
+
x_cols = ws.ws_col < MIN_COLS ? MIN_COLS :
ws.ws_col;
if ((vp = typeset("COLUMNS", 0, 0, 0, 0)))
setint(vp, (int64_t) ws.ws_col);
+
+ if (x_cols != old_cols && x_resize_cb)
+ (*x_resize_cb)();
}
if (ws.ws_row && (vp = typeset("LINES", 0, 0, 0, 0)))
setint(vp, (int64_t) ws.ws_row);
@@ -120,12 +126,14 @@ x_getc(void)
char c;
int n;
- while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR)
+ while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR) {
+ check_sigwinch();
if (trap) {
x_mode(false);
runtraps(0);
x_mode(true);
}
+ }
if (n != 1)
return -1;
return (int) (unsigned char) c;
diff --git a/bin/ksh/edit.h b/bin/ksh/edit.h
index 1d573e1f6fb..abcecc667e0 100644
--- a/bin/ksh/edit.h
+++ b/bin/ksh/edit.h
@@ -39,6 +39,8 @@ extern X_chars edchars;
#define XCF_COMMAND_FILE (XCF_COMMAND|XCF_FILE)
/* edit.c */
+void check_sigwinch(void);
+extern void (*x_resize_cb)(void);
int x_getc(void);
void x_flush(void);
int x_putc(int);
diff --git a/bin/ksh/emacs.c b/bin/ksh/emacs.c
index 3c9b0074d81..fa18ecd3a14 100644
--- a/bin/ksh/emacs.c
+++ b/bin/ksh/emacs.c
@@ -105,6 +105,7 @@ static int xlp_valid;
/* end from 4.9 edit.h } */
static int x_tty; /* are we on a tty? */
static int x_bind_quiet; /* be quiet when binding keys */
+static void x_emacs_resize(void);
static int (*x_last_command)(int);
static char **x_histp; /* history position */
@@ -286,6 +287,8 @@ x_emacs(char *buf, size_t len)
xmp = NULL;
x_histp = histptr + 1;
+ x_resize_cb = x_emacs_resize;
+ check_sigwinch();
xx_cols = x_cols;
x_col = promptlen(prompt, &p);
prompt_skip = p - prompt;
@@ -316,8 +319,11 @@ x_emacs(char *buf, size_t len)
x_last_command = NULL;
while (1) {
x_flush();
- if ((at = x_e_getu8(line, at)) < 0)
+ if ((at = x_e_getu8(line, at)) < 0) {
+ x_resize_cb = NULL;
return 0;
+ }
+
ntries++;
if (x_arg == -1) {
@@ -378,6 +384,7 @@ x_emacs(char *buf, size_t len)
x_last_command = NULL;
break;
case KEOL:
+ x_resize_cb = NULL;
ret = xep - xbuf;
return (ret);
break;
@@ -2162,4 +2169,37 @@ x_lastcp(void)
return (xlp);
}
+static void
+x_emacs_resize(void)
+{
+ /* clear old content before resizing */
+ x_putc('\r');
+ x_putc('\033');
+ x_putc('[');
+ x_putc('J');
+
+ xx_cols = x_cols;
+ x_col = promptlen(prompt, NULL);
+ if (x_col > xx_cols)
+ x_col = x_col - (x_col / xx_cols) * xx_cols;
+ x_displen = xx_cols - 2 - x_col;
+ if (x_displen < 1) {
+ x_col = 0;
+ x_displen = xx_cols - 2;
+ prompt_redraw = 0;
+ } else {
+ xbp = xbuf;
+ xlp_valid = false;
+ if (xcp <= x_lastcp()) {
+ x_redraw(0);
+ x_flush();
+ return;
+ }
+ }
+
+ x_col = 0;
+ x_displen = xx_cols - 2;
+ x_adjust();
+}
+
#endif /* EMACS */
diff --git a/bin/ksh/vi.c b/bin/ksh/vi.c
index 28be809036d..def7aeeae24 100644
--- a/bin/ksh/vi.c
+++ b/bin/ksh/vi.c
@@ -75,6 +75,8 @@ static void vi_error(void);
static void vi_macro_reset(void);
static int x_vi_putbuf(const char *, size_t);
static int isu8cont(unsigned char);
+static void x_vi_resize(void);
+static void calc_winsize(void);
#define C_ 0x1 /* a valid command that isn't a M_, E_, U_ */
#define M_ 0x2 /* movement command (h, l, etc.) */
@@ -201,6 +203,8 @@ x_vi(char *buf, size_t len)
int c;
vi_reset(buf, len > LINE ? LINE : len);
+ x_resize_cb = x_vi_resize;
+ check_sigwinch();
vi_pprompt(1);
x_flush();
while (1) {
@@ -242,6 +246,7 @@ x_vi(char *buf, size_t len)
x_flush();
}
+ x_resize_cb = NULL;
x_putc('\r'); x_putc('\n'); x_flush();
if (c == -1 || len <= (size_t)es->linelen)
@@ -1424,8 +1429,6 @@ free_edstate(struct edstate *old)
static void
edit_reset(char *buf, size_t len)
{
- const char *p;
-
es = &ebuf;
es->cbuf = buf;
es->cbufsize = len;
@@ -1436,6 +1439,17 @@ edit_reset(char *buf, size_t len)
es->cursor = undo->cursor = 0;
es->winleft = undo->winleft = 0;
+ calc_winsize();
+ win = 0;
+ morec = ' ';
+ holdlen = 0;
+}
+
+static void
+calc_winsize(void)
+{
+ const char *p;
+
cur_col = pwidth = promptlen(prompt, &p);
prompt_skip = p - prompt;
if (pwidth > x_cols - 3 - MIN_EDIT_SPACE) {
@@ -1452,9 +1466,6 @@ edit_reset(char *buf, size_t len)
(void) memset(wbuf[0], ' ', wbuf_len);
(void) memset(wbuf[1], ' ', wbuf_len);
winwidth = x_cols - pwidth - 3;
- win = 0;
- morec = ' ';
- holdlen = 0;
}
/*
@@ -2300,4 +2311,29 @@ isu8cont(unsigned char c)
{
return !Flag(FVISHOW8) && (c & (0x80 | 0x40)) == 0x80;
}
+
+static void
+x_vi_resize(void)
+{
+ int cur = 0, col = 0;
+
+ /* clear old content before resizing */
+ x_putc('\r');
+ x_putc('\033');
+ x_putc('[');
+ x_putc('J');
+
+ calc_winsize();
+
+ while (cur < es->linelen)
+ col = newcol((unsigned char) es->cbuf[cur++], col);
+ if (col <= winwidth)
+ es->winleft = 0;
+ else if (outofwin())
+ rewindow();
+
+ redraw_line(0, 0);
+ refresh_line(insert != 0);
+ x_flush();
+}
#endif /* VI */
--
2.52.0
From a4c841e6bb2aa3b85150b9882cacd48f3da84304 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill@korins.ky>
Date: Wed, 21 Jan 2026 17:05:09 +0100
Subject: [PATCH 2/2] bin/ksh: fix emacs/vi mode logic with woring UTF-8
characters
---
bin/ksh/emacs.c | 83 +++++++++++++++++++++++++++++++++++++++----------
bin/ksh/vi.c | 17 +++++++---
2 files changed, 79 insertions(+), 21 deletions(-)
diff --git a/bin/ksh/emacs.c b/bin/ksh/emacs.c
index fa18ecd3a14..b1b384389ae 100644
--- a/bin/ksh/emacs.c
+++ b/bin/ksh/emacs.c
@@ -130,6 +130,7 @@ static int x_size_str(char *);
static int x_size(int);
static void x_zots(char *);
static void x_zotc(int);
+static void x_zotcp(char *);
static void x_load_hist(char **);
static int x_search(char *, int, int);
static int x_match(char *, char *);
@@ -642,16 +643,17 @@ x_fword(void)
static void
x_goto(char *cp)
{
- if (cp < xbp || cp >= (xbp + x_displen)) {
+ x_lastcp();
+ if (cp < xbp || cp >= xlp) {
/* we are heading off screen */
xcp = cp;
x_adjust();
} else if (cp < xcp) { /* move back */
while (cp < xcp)
- x_bs((unsigned char)*--xcp);
+ x_bs(*--xcp);
} else if (cp > xcp) { /* move forward */
while (cp > xcp)
- x_zotc((unsigned char)*xcp++);
+ x_zotcp(xcp++);
}
}
@@ -670,14 +672,14 @@ x_size_str(char *cp)
{
int size = 0;
while (*cp)
- size += x_size(*cp++);
+ size += x_size((unsigned char)*cp++);
return size;
}
static int
x_size(int c)
{
- if (c=='\t')
+ if (c == '\t')
return 4; /* Kludge, tabs are always four spaces. */
if (iscntrl(c)) /* control char */
return 2;
@@ -698,7 +700,7 @@ x_zots(char *str)
}
x_lastcp();
while (*str && str < xlp && adj == x_adj_done)
- x_zotc(*str++);
+ x_zotcp(str++);
}
static void
@@ -714,6 +716,39 @@ x_zotc(int c)
x_e_putc(c);
}
+static void
+x_zotcp(char *p)
+{
+ unsigned char uc = (unsigned char)*p;
+
+ if (uc == '\t') {
+ /* Kludge, tabs are always four spaces. */
+ x_e_puts(" ");
+ return;
+ }
+ if (isu8cont(uc)) {
+ if (x_col <= xx_cols) {
+ x_putc(uc);
+ }
+ if (x_adj_ok && !isu8cont((unsigned char)p[1]) &&
+ (x_col < 0 || x_col >= (xx_cols - 2)))
+ x_adjust();
+ return;
+ }
+ if (iscntrl(uc)) {
+ x_e_putc('^');
+ x_e_putc(UNCTRL(uc));
+ } else {
+ if (x_col < xx_cols) {
+ x_putc(uc);
+ x_col++;
+ }
+ if (x_adj_ok && !isu8cont((unsigned char)p[1]) &&
+ (x_col < 0 || x_col >= (xx_cols - 2)))
+ x_adjust();
+ }
+}
+
static int
x_mv_back(int c)
{
@@ -1033,7 +1068,7 @@ x_clear_screen(int c)
static void
x_redraw(int limit)
{
- int i, j, truncate = 0;
+ int i, j, truncate = 0, dcols;
char *cp;
x_adj_ok = 0;
@@ -1073,10 +1108,12 @@ x_redraw(int limit)
if (xbp != xbuf || xep > xlp)
limit = xx_cols;
if (limit >= 0) {
- if (xep > xlp)
- i = 0; /* we fill the line */
- else
- i = limit - (xlp - xbp);
+ dcols = 0;
+ for (cp = xbp; cp < xlp; cp++)
+ dcols += x_size((unsigned char)*cp);
+ i = limit - dcols;
+ if (i < 0)
+ i = 0;
for (j = 0; j < i && x_col < (xx_cols - 2); j++)
x_e_putc(' ');
@@ -1133,8 +1170,8 @@ x_transpose(int c)
*/
x_bs(xcp[-1]);
x_bs(xcp[-2]);
- x_zotc(xcp[-1]);
- x_zotc(xcp[-2]);
+ x_zotcp(&xcp[-1]);
+ x_zotcp(&xcp[-2]);
tmp = xcp[-1];
xcp[-1] = xcp[-2];
xcp[-2] = tmp;
@@ -1143,8 +1180,8 @@ x_transpose(int c)
* cursor, move cursor position along one.
*/
x_bs(xcp[-1]);
- x_zotc(xcp[0]);
- x_zotc(xcp[-1]);
+ x_zotcp(&xcp[0]);
+ x_zotcp(&xcp[-1]);
tmp = xcp[-1];
xcp[-1] = xcp[0];
xcp[0] = tmp;
@@ -1805,12 +1842,22 @@ do_complete(int flags, /* XCF_{COMMAND,FILE,COMMAND_FILE} */
static void
x_adjust(void)
{
+ char *cp;
+ int col;
+
x_adj_done++; /* flag the fact that we were called. */
/*
* we had a problem if the prompt length > xx_cols / 2
*/
- if ((xbp = xcp - (x_displen / 2)) < xbuf)
- xbp = xbuf;
+ col = x_displen / 2;
+ cp = xcp;
+ while (cp > xbuf && col > 0) {
+ cp--;
+ while (cp > xbuf && isu8cont(*cp))
+ cp--;
+ col -= x_size((unsigned char)*cp);
+ }
+ xbp = cp;
xlp_valid = false;
x_redraw(xx_cols);
x_flush();
@@ -2164,6 +2211,8 @@ x_lastcp(void)
for (i = 0, rcp = xbp; rcp < xep && i < x_displen; rcp++)
i += x_size((unsigned char)*rcp);
xlp = rcp;
+ while (xlp < xep && isu8cont(*xlp))
+ xlp++;
}
xlp_valid = true;
return (xlp);
diff --git a/bin/ksh/vi.c b/bin/ksh/vi.c
index def7aeeae24..3b98856158e 100644
--- a/bin/ksh/vi.c
+++ b/bin/ksh/vi.c
@@ -1880,6 +1880,7 @@ display(char *wb1, char *wb2, int leftside)
int ncol; /* display column of the cursor */
int cnt; /* remaining display columns to fill */
int moreright;
+ int trailu8;
char mc; /* new "more character" at the right of window */
unsigned char ch;
@@ -1891,8 +1892,11 @@ display(char *wb1, char *wb2, int leftside)
ncol = col = 0;
cur = es->winleft;
moreright = 0;
+ trailu8 = 0;
twb1 = wb1;
- while (col < winwidth && cur < es->linelen) {
+ while (cur < es->linelen &&
+ (col < winwidth ||
+ (col == winwidth && isu8cont(es->cbuf[cur])))) {
if (cur == es->cursor && leftside)
ncol = col + pwidth;
if ((ch = es->cbuf[cur]) == '\t') {
@@ -1908,7 +1912,8 @@ display(char *wb1, char *wb2, int leftside)
}
ch &= 0x7f;
}
- if (col < winwidth) {
+ if (col < winwidth ||
+ (col == winwidth && isu8cont(ch))) {
if (ch < ' ' || ch == 0x7f) {
*twb1++ = '^';
if (++col < winwidth) {
@@ -1916,6 +1921,8 @@ display(char *wb1, char *wb2, int leftside)
col++;
}
} else {
+ if (col == winwidth && isu8cont(ch))
+ trailu8 = 1;
*twb1++ = ch;
if (col == 0 || !isu8cont(ch))
col++;
@@ -2008,8 +2015,10 @@ display(char *wb1, char *wb2, int leftside)
mc = '>';
else
mc = ' ';
- if (mc != morec) {
- ed_mov_opt(pwidth + winwidth + 1, wb1);
+ if (mc != morec || trailu8) {
+ ed_mov_opt(pwidth + winwidth, wb1);
+ x_putc(' ');
+ cur_col++;
x_putc(mc);
cur_col++;
morec = mc;
--
2.52.0
bin/ksh: fix emacs/vi line editor getting wrong terminal width
bin/ksh: fix emacs/vi line editor getting wrong terminal width
bin/ksh: fix emacs/vi line editor getting wrong terminal width