Cache glyphs, fix vttest test
[nikiroo-utils.git] / src / jexer / tterminal / ECMA48.java
index 300625b9f244da6bb572f03607744abeea6ca81a..f657eaffa1255bdf0ee5a391ca0cfcdaaec0aa0f 100644 (file)
@@ -129,15 +129,13 @@ public class ECMA48 implements Runnable {
             return "\033[?6c";
 
         case VT220:
+        case XTERM:
             // "I am a VT220" - 7 bit version
             if (!s8c1t) {
                 return "\033[?62;1;6c";
             }
             // "I am a VT220" - 8 bit version
             return "\u009b?62;1;6c";
-        case XTERM:
-            // "I am a VT100 with advanced video option" (often VT102)
-            return "\033[?1;2c";
         default:
             throw new IllegalArgumentException("Invalid device type: " + type);
         }
@@ -698,7 +696,7 @@ public class ECMA48 implements Runnable {
                 return columns132;
         }
 
-        /**
+    /**
      * true = reverse video.  Set by DECSCNM.
      */
     private boolean reverseVideo = false;
@@ -1255,6 +1253,46 @@ public class ECMA48 implements Runnable {
         writeRemote(keypressToString(keypress));
     }
 
+    /**
+     * Build one of the complex xterm keystroke sequences, storing the result in
+     * xterm_keystroke_buffer.
+     *
+     * @param ss3 the prefix to use based on VT100 state.
+     * @param first the first character, usually a number.
+     * @param first the last character, one of the following: ~ A B C D F H
+     * @param ctrl whether or not ctrl is down
+     * @param alt whether or not alt is down
+     * @param shift whether or not shift is down
+     * @return the buffer with the full key sequence
+     */
+    private String xtermBuildKeySequence(final String ss3, final char first,
+        final char last, boolean ctrl, boolean alt, boolean shift) {
+
+        StringBuilder sb = new StringBuilder(ss3);
+        if ((last == '~') || (ctrl == true) || (alt == true)
+            || (shift == true)
+        ) {
+            sb.append(first);
+            if (       (ctrl == false) && (alt == false) && (shift == true)) {
+                sb.append(";2");
+            } else if ((ctrl == false) && (alt == true) && (shift == false)) {
+                sb.append(";3");
+            } else if ((ctrl == false) && (alt == true) && (shift == true)) {
+                sb.append(";4");
+            } else if ((ctrl == true) && (alt == false) && (shift == false)) {
+                sb.append(";5");
+            } else if ((ctrl == true) && (alt == false) && (shift == true)) {
+                sb.append(";6");
+            } else if ((ctrl == true) && (alt == true) && (shift == false)) {
+                sb.append(";7");
+            } else if ((ctrl == true) && (alt == true) && (shift == true)) {
+                sb.append(";8");
+            }
+        }
+        sb.append(last);
+        return sb.toString();
+    }
+
     /**
      * Translate the keyboard press to a VT100, VT220, or XTERM sequence.
      *
@@ -1311,69 +1349,177 @@ public class ECMA48 implements Runnable {
             }
         }
 
-        if (keypress.equals(kbLeft)) {
-            switch (arrowKeyMode) {
-            case ANSI:
-                return "\033[D";
-            case VT52:
-                return "\033D";
-            case VT100:
-                return "\033OD";
+        if (keypress.equalsWithoutModifiers(kbLeft)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '1', 'D',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '1', 'D',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '1', 'D',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return "\033[D";
+                case VT52:
+                    return "\033D";
+                case VT100:
+                    return "\033OD";
+                }
             }
         }
 
-        if (keypress.equals(kbRight)) {
-            switch (arrowKeyMode) {
-            case ANSI:
-                return "\033[C";
-            case VT52:
-                return "\033C";
-            case VT100:
-                return "\033OC";
+        if (keypress.equalsWithoutModifiers(kbRight)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '1', 'C',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '1', 'C',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '1', 'C',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return "\033[C";
+                case VT52:
+                    return "\033C";
+                case VT100:
+                    return "\033OC";
+                }
             }
         }
 
-        if (keypress.equals(kbUp)) {
-            switch (arrowKeyMode) {
-            case ANSI:
-                return "\033[A";
-            case VT52:
-                return "\033A";
-            case VT100:
-                return "\033OA";
+        if (keypress.equalsWithoutModifiers(kbUp)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '1', 'A',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '1', 'A',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '1', 'A',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return "\033[A";
+                case VT52:
+                    return "\033A";
+                case VT100:
+                    return "\033OA";
+                }
             }
         }
 
-        if (keypress.equals(kbDown)) {
-            switch (arrowKeyMode) {
-            case ANSI:
-                return "\033[B";
-            case VT52:
-                return "\033B";
-            case VT100:
-                return "\033OB";
+        if (keypress.equalsWithoutModifiers(kbDown)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '1', 'B',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '1', 'B',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '1', 'B',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return "\033[B";
+                case VT52:
+                    return "\033B";
+                case VT100:
+                    return "\033OB";
+                }
             }
         }
 
-        if (keypress.equals(kbHome)) {
-            switch (arrowKeyMode) {
-            case ANSI:
-                return "\033[H";
-            case VT52:
-                return "\033H";
-            case VT100:
-                return "\033OH";
+        if (keypress.equalsWithoutModifiers(kbHome)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '1', 'H',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '1', 'H',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '1', 'H',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return "\033[H";
+                case VT52:
+                    return "\033H";
+                case VT100:
+                    return "\033OH";
+                }
             }
         }
 
-        if (keypress.equals(kbEnd)) {
-            switch (arrowKeyMode) {
-            case ANSI:
-                return "\033[F";
-            case VT52:
-                return "\033F";
-            case VT100:
-                return "\033OF";
+        if (keypress.equalsWithoutModifiers(kbEnd)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '1', 'F',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '1', 'F',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '1', 'F',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return "\033[F";
+                case VT52:
+                    return "\033F";
+                case VT100:
+                    return "\033OF";
+                }
             }
         }
 
@@ -1500,6 +1646,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0332P";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;2P";
+            }
             return "\033O2P";
         }
 
@@ -1508,6 +1657,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0332Q";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;2Q";
+            }
             return "\033O2Q";
         }
 
@@ -1516,6 +1668,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0332R";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;2R";
+            }
             return "\033O2R";
         }
 
@@ -1524,6 +1679,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0332S";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;2S";
+            }
             return "\033O2S";
         }
 
@@ -1572,6 +1730,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0335P";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;5P";
+            }
             return "\033O5P";
         }
 
@@ -1580,6 +1741,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0335Q";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;5Q";
+            }
             return "\033O5Q";
         }
 
@@ -1588,6 +1752,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0335R";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;5R";
+            }
             return "\033O5R";
         }
 
@@ -1596,6 +1763,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0335S";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;5S";
+            }
             return "\033O5S";
         }
 
@@ -1639,39 +1809,93 @@ public class ECMA48 implements Runnable {
             return "\033[24;5~";
         }
 
-        if (keypress.equals(kbPgUp)) {
-            // Page Up
-            return "\033[5~";
-        }
-
-        if (keypress.equals(kbPgDn)) {
-            // Page Down
-            return "\033[6~";
-        }
-
-        if (keypress.equals(kbIns)) {
-            // Ins
-            return "\033[2~";
+        if (keypress.equalsWithoutModifiers(kbPgUp)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '5', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '5', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '5', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                return "\033[5~";
+            }
         }
 
-        if (keypress.equals(kbShiftIns)) {
-            // This is what xterm sends for SHIFT-INS
-            return "\033[2;2~";
-            // This is what xterm sends for CTRL-INS
-            // return "\033[2;5~";
+        if (keypress.equalsWithoutModifiers(kbPgDn)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '6', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '6', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '6', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                return "\033[6~";
+            }
         }
 
-        if (keypress.equals(kbShiftDel)) {
-            // This is what xterm sends for SHIFT-DEL
-            return "\033[3;2~";
-            // This is what xterm sends for CTRL-DEL
-            // return "\033[3;5~";
+        if (keypress.equalsWithoutModifiers(kbIns)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '2', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '2', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '2', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                return "\033[2~";
+            }
         }
 
-        if (keypress.equals(kbDel)) {
-            // Delete sends real delete for VTxxx
-            return "\177";
-            // return "\033[3~";
+        if (keypress.equalsWithoutModifiers(kbDel)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '3', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '3', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '3', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                // Delete sends real delete for VTxxx
+                return "\177";
+            }
         }
 
         if (keypress.equals(kbEnter)) {
@@ -1690,6 +1914,17 @@ public class ECMA48 implements Runnable {
             return "\011";
         }
 
+        if ((keypress.equalsWithoutModifiers(kbBackTab)) ||
+            (keypress.equals(kbShiftTab))
+        ) {
+            switch (type) {
+            case XTERM:
+                return "\033[Z";
+            default:
+                return "\011";
+            }
+        }
+
         // Non-alt, non-ctrl characters
         if (!keypress.isFnKey()) {
             StringBuilder sb = new StringBuilder();
@@ -3257,9 +3492,41 @@ public class ECMA48 implements Runnable {
                 currentState.attr.setForeColor(Color.WHITE);
                 break;
             case 38:
-                // Underscore on, default foreground color
-                currentState.attr.setUnderline(true);
-                currentState.attr.setForeColor(Color.WHITE);
+                if (type == DeviceType.XTERM) {
+                    /*
+                     * Xterm supports T.416 / ISO-8613-3 codes to select
+                     * either an indexed color or an RGB value.  (It also
+                     * permits these ISO-8613-3 SGR sequences to be separated
+                     * by colons rather than semicolons.)
+                     *
+                     * We will not support any of these additional color
+                     * codes at this time:
+                     *
+                     * 1. http://invisible-island.net/ncurses/ncurses.faq.html#xterm_16MegaColors
+                     *    has a detailed discussion of the current state of
+                     *    RGB in various terminals, the point of which is
+                     *    that none of them really do the same thing despite
+                     *    all appearing to be "xterm".
+                     *
+                     * 2. As seen in
+                     *    https://bugs.kde.org/show_bug.cgi?id=107487#c3,
+                     *    even supporting just the "indexed mode" of these
+                     *    sequences (which could align easily with existing
+                     *    SGR colors) is assumed to mean full support of
+                     *    24-bit RGB.  So it is all or nothing.
+                     *
+                     * Finally, these sequences break the assumptions of
+                     * standard ECMA-48 style parsers as pointed out at
+                     * https://bugs.kde.org/show_bug.cgi?id=107487#c11 .
+                     * Therefore in order to keep a clean display, we cannot
+                     * parse anything else in this sequence.
+                     */
+                    return;
+                } else {
+                    // Underscore on, default foreground color
+                    currentState.attr.setUnderline(true);
+                    currentState.attr.setForeColor(Color.WHITE);
+                }
                 break;
             case 39:
                 // Underscore off, default foreground color
@@ -3298,6 +3565,21 @@ public class ECMA48 implements Runnable {
                 // Set white background
                 currentState.attr.setBackColor(Color.WHITE);
                 break;
+            case 48:
+                if (type == DeviceType.XTERM) {
+                    /*
+                     * Xterm supports T.416 / ISO-8613-3 codes to select
+                     * either an indexed color or an RGB value.  (It also
+                     * permits these ISO-8613-3 SGR sequences to be separated
+                     * by colons rather than semicolons.)
+                     *
+                     * We will not support this at this time.  Also, in order
+                     * to keep a clean display, we cannot parse anything else
+                     * in this sequence.
+                     */
+                    return;
+                }
+                break;
             case 49:
                 // Default background
                 currentState.attr.setBackColor(Color.BLACK);
@@ -3476,6 +3758,7 @@ public class ECMA48 implements Runnable {
      */
     private void dsr() {
         boolean decPrivateModeFlag = false;
+        int row = currentState.cursorY;
 
         for (int i = 0; i < collectBuffer.length(); i++) {
             if (collectBuffer.charAt(i) == '?') {
@@ -3503,15 +3786,18 @@ public class ECMA48 implements Runnable {
 
         case 6:
             // Request cursor position.  Respond with current position.
+            if (currentState.originMode == true) {
+                row -= scrollRegionTop;
+            }
             String str = "";
             if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
                 && (s8c1t == true)
             ) {
-                str = String.format("\u009b%d;%dR",
-                    currentState.cursorY + 1, currentState.cursorX + 1);
+                str = String.format("\u009b%d;%dR", row + 1,
+                    currentState.cursorX + 1);
             } else {
-                str = String.format("\033[%d;%dR",
-                    currentState.cursorY + 1, currentState.cursorX + 1);
+                str = String.format("\033[%d;%dR", row + 1,
+                    currentState.cursorX + 1);
             }
 
             // Send string directly to remote side