]> granicus.if.org Git - procps-ng/commitdiff
top: enable processing utf8 multi-byte character input
authorJim Warner <james.warner@comcast.net>
Mon, 13 Jun 2022 05:00:00 +0000 (00:00 -0500)
committerCraig Small <csmall@dropbear.xyz>
Tue, 14 Jun 2022 11:52:13 +0000 (21:52 +1000)
This program was well equipped to properly handle utf8
multi-byte characters - except for one important area!

If users typed any unicode character (shift+ctrl+u) or
pasted a utf-8 multi-byte string as a response to some
input prompt, those characters would simply be ignored
since they would not pass the internal 'isprint' test.

Well, now we can handle such data while preserving all
line editing provisions such as insertions, deletions,
destructive backspace, prior line recall (up/down) and
those all important cursor left plus right arrow keys.

[ we even support overtype mode for multi-byte stuff ]
[ even though our gui emulator will not let us alter ]
[ the cursor as confirmation (as we do at a console) ]

Signed-off-by: Jim Warner <james.warner@comcast.net>
top/top.c

index 962d16a7b828e3fed60ad2e880959fd6ebd57978..39c511ad76a53a6b32e161cc23262d4c1c710cb4 100644 (file)
--- a/top/top.c
+++ b/top/top.c
@@ -1157,7 +1157,7 @@ static int iokey (int action) {
 #ifdef TERMIOS_ONLY
    char buf[SMLBUFSIZ], *pb;
 #else
-   static char buf[SMLBUFSIZ];
+   static char buf[MEDBUFSIZ];
    static int pos, len;
    char *pb;
 #endif
@@ -1196,7 +1196,7 @@ static int iokey (int action) {
       pos = len = 0;
       memset(buf, '\0', sizeof(buf));
       if (1 > ioch(0, buf, sizeof(buf)-1)) return 0;
-      if (isprint(buf[0])) {           // no need for translation
+      if (!iscntrl(buf[0])) {          // no need for translation
          len = strlen(buf);
          pos = 1;
          return buf[0];
@@ -1240,44 +1240,56 @@ static char *ioline (const char *prompt) {
 
 #else
         /*
-         * Get line oriented interactive input from the user,
-         * going way beyond native tty support by providing:
-         * . true line editing, not just destructive backspace
-         * . an input limit sensitive to current screen dimensions
-         * . ability to recall prior strings for re-input/re-editing */
+         * Get some line oriented interactive input from the ol' user,
+         * going way, way beyond that native tty support by providing:
+         * . true input line editing, not just a destructive backspace
+         * . an input limit sensitive to the current screen dimensions
+         * . an ability to recall prior strings for editing & re-input */
 static char *ioline (const char *prompt) {
+ #define setLEN    ( len = strlen(buf) - utf8_delta(buf) )
+ #define setPOS(X) ( pos = utf8_embody(buf, X) )
+ #define utfCHR(X) ( (unsigned char *)&buf[X] )
+ #define utfTOT(X) ( UTF8_tab[(unsigned char)buf[X]] )
+ #define utfCOL(X) ( utf8_cols(utfCHR(X), utfTOT(X)) )
+ #define movBKW    { setPOS(cur - 1); while (utfTOT(pos) < 0) --pos; }
+ #define chkCUR    { if (cur < 0) cur = 0; if (cur > len) cur = len; }
+    // thank goodness ol' memmove will safely allow strings to overlap
+ #define sqzSTR  { i = utfTOT(pos); while (i < 0) i = utfTOT(--pos); \
+       memmove(&buf[pos], &buf[pos + i], bufMAX-(pos + i)); \
+       memset(&buf[bufMAX - i], '\0', i); }
+ #define expSTR(X)  if (bufNXT < bufMAX && scrNXT < Screen_cols) { \
+       memmove(&buf[pos + X], &buf[pos], bufMAX - pos); }
  #define savMAX  50
-    // thank goodness memmove allows the two strings to overlap
- #define sqzSTR  { memmove(&buf[pos], &buf[pos+1], bufMAX-pos); \
-       buf[sizeof(buf)-1] = '\0'; }
- #define expSTR  if (len+1 < bufMAX && len+beg+1 < Screen_cols) { \
-       memmove(&buf[pos+1], &buf[pos], bufMAX-pos); buf[pos] = ' '; }
- #define logCOL  (pos+1)
- #define phyCOL  (beg+pos+1)
+ #define bufNXT  ( pos + 4 )           // four equals longest utf8 str
+ #define scrNXT  ( beg + len + 2 )     // two due to multi-column char
  #define bufMAX  ((int)sizeof(buf)-2)  // -1 for '\0' string delimeter
    static char buf[MEDBUFSIZ+1];       // +1 for '\0' string delimeter
    static int ovt;
-   int beg, pos, len, key, i;
+   int beg,           // the physical column where input began, buf[0]
+       cur,           // the logical current column/insertion position
+       len,           // the logical input length, thus the end column
+       pos,           // the physical position in the buffer currently
+       key, i;
    struct lin_s {
-      struct lin_s *bkw;               // ptr to older saved strs
-      struct lin_s *fwd;               // ptr to newer saved strs
-      char *str;                       // the saved string
+      struct lin_s *bkw;               // pointer for older saved strs
+      struct lin_s *fwd;               // pointer for newer saved strs
+      char *str;                       // an actual saved input string
    };
    static struct lin_s *anchor, *plin;
 
    if (!anchor) {
       anchor = alloc_c(sizeof(struct lin_s));
-      anchor->str = alloc_s("");       // top-of-stack == empty str
+      anchor->str = alloc_s("");       // the top-of-stack (empty str)
    }
    plin = anchor;
-   pos = 0;
+   cur = len = pos = 0;
    beg = show_pmt(prompt);
    memset(buf, '\0', sizeof(buf));
+   // this may not work under a gui emulator (but linux console is ok)
    putp(ovt ? Cap_curs_huge : Cap_curs_norm);
 
    do {
       fflush(stdout);
-      len = strlen(buf);
       key = iokey(IOKEY_NEXT);
       switch (key) {
          case 0:
@@ -1287,6 +1299,7 @@ static char *ioline (const char *prompt) {
             buf[0] = kbd_ESC;
             return buf;
          case kbd_ENTER:
+         case kbd_BTAB: case kbd_PGUP: case kbd_PGDN:
             continue;
          case kbd_INS:
             ovt = !ovt;
@@ -1295,45 +1308,63 @@ static char *ioline (const char *prompt) {
          case kbd_DEL:
             sqzSTR
             break;
-         case kbd_BKSP :
-            if (0 < pos) { --pos; sqzSTR }
+         case kbd_BKSP:
+            if (0 < cur) { movBKW; cur -= utfCOL(pos); setPOS(cur); sqzSTR; }
             break;
          case kbd_LEFT:
-            if (0 < pos) --pos;
+            if (0 < cur) { movBKW; cur -= utfCOL(pos); }
             break;
          case kbd_RIGHT:
-            if (pos < len) ++pos;
+            if (cur < len) cur += utfCOL(pos);
             break;
          case kbd_HOME:
-            pos = 0;
+            cur = pos = 0;
             break;
          case kbd_END:
-            pos = len;
+            cur = len;
+            pos = strlen(buf);
             break;
          case kbd_UP:
             if (plin->bkw) {
                plin = plin->bkw;
                memset(buf, '\0', sizeof(buf));
-               pos = snprintf(buf, sizeof(buf), "%.*s", Screen_cols - beg - 1, plin->str);
+               memccpy(buf, plin->str, '\0', bufMAX);
+               cur = setLEN;
+               pos = strlen(buf);
             }
             break;
          case kbd_DOWN:
-            memset(buf, '\0', sizeof(buf));
             if (plin->fwd) plin = plin->fwd;
-            pos = snprintf(buf, sizeof(buf), "%.*s", Screen_cols - beg - 1, plin->str);
+            memset(buf, '\0', sizeof(buf));
+            memccpy(buf, plin->str, '\0', bufMAX);
+            cur = setLEN;
+            pos = strlen(buf);
             break;
          default:                      // what we REALLY wanted (maybe)
-            if (isprint(key) && logCOL < bufMAX && phyCOL < Screen_cols) {
-               if (!ovt) expSTR
+            if (bufNXT < bufMAX && scrNXT < Screen_cols) {
+               int tot = UTF8_tab[(unsigned char)key],
+                   sav = pos;
+               if (tot < 1) tot = 1;
+               if (!ovt) { expSTR(tot); }
+               else { pos = utf8_embody(buf, cur); sqzSTR; expSTR(tot); }
                buf[pos++] = key;
+               while (tot > 1) {
+                 key = iokey(IOKEY_NEXT);
+                 buf[pos++] = key;
+                 --tot;
+               }
+               cur += utfCOL(sav);
             }
             break;
       }
+      setLEN;
+      chkCUR;
+      setPOS(cur);
       putp(fmtmk("%s%s%s", tg2(beg, Msg_row), Cap_clr_eol, buf));
 #ifdef OVERTYPE_SEE
       putp(fmtmk("%s%c", tg2(beg - 1, Msg_row), ovt ? '^' : ' '));
 #endif
-      putp(tg2(beg+pos, Msg_row));
+      putp(tg2(beg + cur, Msg_row));
    } while (key != kbd_ENTER);
 
    // weed out duplicates, including empty strings (top-of-stack)...
@@ -1372,11 +1403,18 @@ static char *ioline (const char *prompt) {
    anchor->bkw = plin;                 // by sliding it in as new number 2!
 
    return buf;                         // protect our copy, return original
- #undef savMAX
+ #undef setLEN
+ #undef setPOS
+ #undef utfCHR
+ #undef utfTOT
+ #undef utfCOL
+ #undef movBKW
+ #undef chkCUR
  #undef sqzSTR
  #undef expSTR
- #undef logCOL
- #undef phyCOL
+ #undef savMAX
+ #undef bufNXT
+ #undef scrNXT
  #undef bufMAX
 } // end: ioline
 #endif