misc cleanup
[fanfix.git] / src / jexer / tterminal / ECMA48.java
index 9376828ee7689411ff7492de501d7109bc892445..a4197fb9f20557e31b1a9d6c8cf024244153467b 100644 (file)
@@ -3,7 +3,7 @@
  *
  * The MIT License (MIT)
  *
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
 package jexer.tterminal;
 
 import java.io.BufferedOutputStream;
+import java.io.CharArrayWriter;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
 import java.io.Reader;
 import java.io.UnsupportedEncodingException;
 import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.LinkedList;
 import java.util.List;
 
 import jexer.TKeypress;
@@ -252,12 +253,17 @@ public class ECMA48 implements Runnable {
     /**
      * The scrollback buffer characters + attributes.
      */
-    private volatile List<DisplayLine> scrollback;
+    private volatile ArrayList<DisplayLine> scrollback;
 
     /**
      * The raw display buffer characters + attributes.
      */
-    private volatile List<DisplayLine> display;
+    private volatile ArrayList<DisplayLine> display;
+
+    /**
+     * The maximum number of lines in the scrollback buffer.
+     */
+    private int maxScrollback = 10000;
 
     /**
      * The terminal's input.  For type == XTERM, this is an InputStreamReader
@@ -296,6 +302,15 @@ public class ECMA48 implements Runnable {
      */
     private MouseEncoding mouseEncoding = MouseEncoding.X10;
 
+    /**
+     * A terminal may request that the mouse pointer be hidden using a
+     * Privacy Message containing either "hideMousePointer" or
+     * "showMousePointer".  This is currently only used within Jexer by
+     * TTerminalWindow so that only the bottom-most instance of nested
+     * Jexer's draws the mouse within its application window.
+     */
+    private boolean hideMousePointer = false;
+
     /**
      * Physical display width.  We start at 80x24, but the user can resize us
      * bigger/smaller.
@@ -585,8 +600,8 @@ public class ECMA48 implements Runnable {
 
         csiParams         = new ArrayList<Integer>();
         tabStops          = new ArrayList<Integer>();
-        scrollback        = new LinkedList<DisplayLine>();
-        display           = new LinkedList<DisplayLine>();
+        scrollback        = new ArrayList<DisplayLine>();
+        display           = new ArrayList<DisplayLine>();
 
         this.type         = type;
         if (inputStream instanceof TimeoutInputStream) {
@@ -697,7 +712,7 @@ public class ECMA48 implements Runnable {
                                 ch = readBuffer[i];
                             }
 
-                            consume((char)ch);
+                            consume((char) ch);
                         }
                     }
                     // Permit my enclosing UI to know that I updated.
@@ -707,8 +722,29 @@ public class ECMA48 implements Runnable {
                 }
                 // System.err.println("end while loop"); System.err.flush();
             } catch (IOException e) {
-                e.printStackTrace();
                 done = true;
+
+                // This is an unusual case.  We want to see the stack trace,
+                // but it is related to the spawned process rather than the
+                // actual UI.  We will generate the stack trace, and consume
+                // it as though it was emitted by the shell.
+                CharArrayWriter writer= new CharArrayWriter();
+                // Send a ST and RIS to clear the emulator state.
+                try {
+                    writer.write("\033\\\033c");
+                    writer.write("\n-----------------------------------\n");
+                    e.printStackTrace(new PrintWriter(writer));
+                    writer.write("\n-----------------------------------\n");
+                } catch (IOException e2) {
+                    // SQUASH
+                }
+                char [] stackTrace = writer.toCharArray();
+                for (int i = 0; i < stackTrace.length; i++) {
+                    if (stackTrace[i] == '\n') {
+                        consume('\r');
+                    }
+                    consume(stackTrace[i]);
+                }
             }
 
         } // while ((done == false) && (stopReaderThread == false))
@@ -889,7 +925,7 @@ public class ECMA48 implements Runnable {
             try {
                 readerThread.join(1000);
             } catch (InterruptedException e) {
-                e.printStackTrace();
+                // SQUASH
             }
         }
 
@@ -1078,7 +1114,7 @@ public class ECMA48 implements Runnable {
     private void resetTabStops() {
         tabStops.clear();
         for (int i = 0; (i * 8) <= rightMargin; i++) {
-            tabStops.add(new Integer(i * 8));
+            tabStops.add(Integer.valueOf(i * 8));
         }
     }
 
@@ -1234,7 +1270,12 @@ public class ECMA48 implements Runnable {
     private void newDisplayLine() {
         // Scroll the top line off into the scrollback buffer
         scrollback.add(display.get(0));
+        if (scrollback.size() > maxScrollback) {
+            scrollback.remove(0);
+            scrollback.trimToSize();
+        }
         display.remove(0);
+        display.trimToSize();
         DisplayLine line = new DisplayLine(currentState.attr);
         line.setReverseColor(reverseVideo);
         display.add(line);
@@ -1588,6 +1629,7 @@ public class ECMA48 implements Runnable {
      * @param keypress keypress received from the local user
      * @return string to transmit to the remote side
      */
+    @SuppressWarnings("fallthrough")
     private String keypressToString(final TKeypress keypress) {
 
         if ((fullDuplex == false) && (!keypress.isFnKey())) {
@@ -2326,13 +2368,13 @@ public class ECMA48 implements Runnable {
             switch (currentState.glLockshift) {
 
             case G1_GR:
-                assert (false);
+                throw new IllegalArgumentException("programming bug");
 
             case G2_GR:
-                assert (false);
+                throw new IllegalArgumentException("programming bug");
 
             case G3_GR:
-                assert (false);
+                throw new IllegalArgumentException("programming bug");
 
             case G2_GL:
                 // LS2
@@ -2353,10 +2395,10 @@ public class ECMA48 implements Runnable {
             switch (currentState.grLockshift) {
 
             case G2_GL:
-                assert (false);
+                throw new IllegalArgumentException("programming bug");
 
             case G3_GL:
-                assert (false);
+                throw new IllegalArgumentException("programming bug");
 
             case G1_GR:
                 // LS1R
@@ -2415,7 +2457,7 @@ public class ECMA48 implements Runnable {
             display.size());
         List<DisplayLine> displayMiddle = display.subList(regionBottom + 1
             - remaining, regionBottom + 1);
-        display = new LinkedList<DisplayLine>(displayTop);
+        display = new ArrayList<DisplayLine>(displayTop);
         display.addAll(displayMiddle);
         for (int i = 0; i < n; i++) {
             DisplayLine line = new DisplayLine(currentState.attr);
@@ -2456,7 +2498,7 @@ public class ECMA48 implements Runnable {
             display.size());
         List<DisplayLine> displayMiddle = display.subList(regionTop,
             regionTop + remaining);
-        display = new LinkedList<DisplayLine>(displayTop);
+        display = new ArrayList<DisplayLine>(displayTop);
         for (int i = 0; i < n; i++) {
             DisplayLine line = new DisplayLine(currentState.attr);
             line.setReverseColor(reverseVideo);
@@ -2611,7 +2653,7 @@ public class ECMA48 implements Runnable {
      */
     private void param(final byte ch) {
         if (csiParams.size() == 0) {
-            csiParams.add(new Integer(0));
+            csiParams.add(Integer.valueOf(0));
         }
         Integer x = csiParams.get(csiParams.size() - 1);
         if ((ch >= '0') && (ch <= '9')) {
@@ -2621,7 +2663,7 @@ public class ECMA48 implements Runnable {
         }
 
         if (ch == ';') {
-            csiParams.add(new Integer(0));
+            csiParams.add(Integer.valueOf(0));
         }
     }
 
@@ -4053,12 +4095,12 @@ public class ECMA48 implements Runnable {
             if (collectBuffer.charAt(0) == '>') {
                 extendedFlag = 1;
                 if (collectBuffer.length() >= 2) {
-                    i = Integer.parseInt(args.toString());
+                    i = Integer.parseInt(args);
                 }
             } else if (collectBuffer.charAt(0) == '=') {
                 extendedFlag = 2;
                 if (collectBuffer.length() >= 2) {
-                    i = Integer.parseInt(args.toString());
+                    i = Integer.parseInt(args);
                 }
             } else {
                 // Unknown code, bail out
@@ -4500,7 +4542,7 @@ public class ECMA48 implements Runnable {
                 args = collectBuffer.substring(0, collectBuffer.length() - 2);
             }
 
-            String [] p = args.toString().split(";");
+            String [] p = args.split(";");
             if (p.length > 0) {
                 if ((p[0].equals("0")) || (p[0].equals("2"))) {
                     if (p.length > 1) {
@@ -4527,6 +4569,38 @@ public class ECMA48 implements Runnable {
         }
     }
 
+    /**
+     * Handle the SCAN_SOSPMAPC_STRING state.  This is currently only used by
+     * Jexer ECMA48Terminal to talk to ECMA48.
+     *
+     * @param pmChar the character received from the remote side
+     */
+    private void pmPut(final char pmChar) {
+        // System.err.println("pmPut: " + pmChar);
+
+        // Collect first
+        collectBuffer.append(pmChar);
+
+        // Xterm cases...
+        if (collectBuffer.toString().endsWith("\033\\")) {
+            String arg = null;
+            arg = collectBuffer.substring(0, collectBuffer.length() - 2);
+
+            // System.err.println("arg: '" + arg + "'");
+
+            if (arg.equals("hideMousePointer")) {
+                hideMousePointer = true;
+            }
+            if (arg.equals("showMousePointer")) {
+                hideMousePointer = false;
+            }
+
+            // Go to SCAN_GROUND state
+            toGround();
+            return;
+        }
+    }
+
     /**
      * Run this input character through the ECMA48 state machine.
      *
@@ -4556,9 +4630,11 @@ public class ECMA48 implements Runnable {
         // 0x1B == ESCAPE
         if (ch == 0x1B) {
             if ((type == DeviceType.XTERM)
-                && (scanState == ScanState.OSC_STRING)
+                && ((scanState == ScanState.OSC_STRING)
+                    || (scanState == ScanState.SOSPMAPC_STRING))
             ) {
                 // Xterm can pass ESCAPE to its OSC sequence.
+                // Jexer can pass ESCAPE to its PM sequence.
             } else if ((scanState != ScanState.DCS_ENTRY)
                 && (scanState != ScanState.DCS_INTERMEDIATE)
                 && (scanState != ScanState.DCS_IGNORE)
@@ -6414,6 +6490,15 @@ public class ECMA48 implements Runnable {
         case SOSPMAPC_STRING:
             // 00-17, 19, 1C-1F, 20-7F --> ignore
 
+            // Special case for Jexer: PM can pass one control character
+            if (ch == 0x1B) {
+                pmPut(ch);
+            }
+
+            if ((ch >= 0x20) && (ch <= 0x7F)) {
+                pmPut(ch);
+            }
+
             // 0x9C goes to GROUND
             if (ch == 0x9C) {
                 toGround();
@@ -6477,4 +6562,15 @@ public class ECMA48 implements Runnable {
         return currentState.cursorY;
     }
 
+    /**
+     * Returns true if this terminal has requested the mouse pointer be
+     * hidden.
+     *
+     * @return true if this terminal has requested the mouse pointer be
+     * hidden
+     */
+    public final boolean hasHiddenMousePointer() {
+        return hideMousePointer;
+    }
+
 }