From 6f8ff91a29056209f9fd5f40e2dcf1ae285e0210 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Wed, 23 Aug 2017 15:44:11 -0400 Subject: [PATCH] custom command line terminal window, blinking in color theme, scrolling region resize --- src/jexer/TApplication.java | 31 ++++++++ src/jexer/TTerminalWindow.java | 137 +++++++++++++++++++++----------- src/jexer/bits/ColorTheme.java | 33 +++++--- src/jexer/tterminal/ECMA48.java | 6 +- 4 files changed, 149 insertions(+), 58 deletions(-) diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index b4c53ec..78a81ef 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -2720,6 +2720,37 @@ public class TApplication implements Runnable { return new TTerminalWindow(this, x, y, flags); } + /** + * Convenience function to open a terminal window and execute a custom + * command line inside it. + * + * @param x column relative to parent + * @param y row relative to parent + * @param commandLine the command line to execute + * @return the terminal new window + */ + public final TTerminalWindow openTerminal(final int x, final int y, + final String commandLine) { + + return openTerminal(x, y, TWindow.RESIZABLE, commandLine); + } + + /** + * Convenience function to open a terminal window and execute a custom + * command line inside it. + * + * @param x column relative to parent + * @param y row relative to parent + * @param flags mask of CENTERED, MODAL, or RESIZABLE + * @param commandLine the command line to execute + * @return the terminal new window + */ + public final TTerminalWindow openTerminal(final int x, final int y, + final int flags, final String commandLine) { + + return new TTerminalWindow(this, x, y, flags, commandLine); + } + /** * Convenience function to spawn an file open box. * diff --git a/src/jexer/TTerminalWindow.java b/src/jexer/TTerminalWindow.java index 3dc6a4c..31834ee 100644 --- a/src/jexer/TTerminalWindow.java +++ b/src/jexer/TTerminalWindow.java @@ -146,18 +146,11 @@ public class TTerminalWindow extends TScrollableWindow } /** - * Public constructor spawns a shell. + * Spawn the shell. * - * @param application TApplication that manages this window - * @param x column relative to parent - * @param y row relative to parent - * @param flags mask of CENTERED, MODAL, or RESIZABLE + * @param commandLine the command line to execute */ - public TTerminalWindow(final TApplication application, final int x, - final int y, final int flags) { - - super(application, i18n.getString("windowTitle"), x, y, - 80 + 2, 24 + 2, flags); + private void spawnShell(final String commandLine) { vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2); setBottomValue(0); @@ -166,43 +159,8 @@ public class TTerminalWindow extends TScrollableWindow ECMA48.DeviceType deviceType = ECMA48.DeviceType.XTERM; try { - String [] cmdShellWindows = { - "cmd.exe" - }; - - // You cannot run a login shell in a bare Process interactively, - // due to libc's behavior of buffering when stdin/stdout aren't a - // tty. Use 'script' instead to run a shell in a pty. And - // because BSD and GNU differ on the '-f' vs '-F' flags, we need - // two different commands. Lovely. - String [] cmdShellGNU = { - "script", "-fqe", "/dev/null" - }; - String [] cmdShellBSD = { - "script", "-q", "-F", "/dev/null" - }; - String [] cmdShellPtypipe = { - "ptypipe", "/bin/bash", "--login" - }; - // Spawn a shell and pass its I/O to the other constructor. - - ProcessBuilder pb; - if ((System.getProperty("jexer.TTerminal.ptypipe") != null) - && (System.getProperty("jexer.TTerminal.ptypipe"). - equals("true")) - ) { - pb = new ProcessBuilder(cmdShellPtypipe); - ptypipe = true; - } else if (System.getProperty("os.name").startsWith("Windows")) { - pb = new ProcessBuilder(cmdShellWindows); - } else if (System.getProperty("os.name").startsWith("Mac")) { - pb = new ProcessBuilder(cmdShellBSD); - } else if (System.getProperty("os.name").startsWith("Linux")) { - pb = new ProcessBuilder(cmdShellGNU); - } else { - // When all else fails, assume GNU. - pb = new ProcessBuilder(cmdShellGNU); - } + String [] command = commandLine.split("\\s"); + ProcessBuilder pb = new ProcessBuilder(command); Map env = pb.environment(); env.put("TERM", ECMA48.deviceTypeTerm(deviceType)); env.put("LANG", ECMA48.deviceTypeLang(deviceType, "en")); @@ -229,6 +187,91 @@ public class TTerminalWindow extends TScrollableWindow newStatusBar(i18n.getString("statusBarRunning")); } + /** + * Public constructor spawns a custom command line. + * + * @param application TApplication that manages this window + * @param x column relative to parent + * @param y row relative to parent + * @param flags mask of CENTERED, MODAL, or RESIZABLE + * @param commandLine the command line to execute + */ + public TTerminalWindow(final TApplication application, final int x, + final int y, final int flags, final String commandLine) { + + super(application, i18n.getString("windowTitle"), x, y, + 80 + 2, 24 + 2, flags); + + String cmdShellWindows = "cmd.exe /c" + commandLine; + String cmdShellGNU = "script -fqe /dev/null -c " + commandLine; + String cmdShellBSD = "script -q -F /dev/null -c " + commandLine; + String cmdShellPtypipe = "ptypipe " + commandLine; + + // Spawn a shell and pass its I/O to the other constructor. + if ((System.getProperty("jexer.TTerminal.ptypipe") != null) + && (System.getProperty("jexer.TTerminal.ptypipe"). + equals("true")) + ) { + ptypipe = true; + spawnShell(cmdShellPtypipe); + } else if (System.getProperty("os.name").startsWith("Windows")) { + spawnShell(cmdShellWindows); + } else if (System.getProperty("os.name").startsWith("Mac")) { + spawnShell(cmdShellBSD); + } else if (System.getProperty("os.name").startsWith("Linux")) { + spawnShell(cmdShellGNU); + } else { + // When all else fails, assume GNU. + spawnShell(cmdShellGNU); + } + } + + /** + * Public constructor spawns a shell. + * + * @param application TApplication that manages this window + * @param x column relative to parent + * @param y row relative to parent + * @param flags mask of CENTERED, MODAL, or RESIZABLE + */ + public TTerminalWindow(final TApplication application, final int x, + final int y, final int flags) { + + super(application, i18n.getString("windowTitle"), x, y, + 80 + 2, 24 + 2, flags); + + String cmdShellWindows = "cmd.exe"; + + // You cannot run a login shell in a bare Process interactively, due + // to libc's behavior of buffering when stdin/stdout aren't a tty. + // Use 'script' instead to run a shell in a pty. And because BSD and + // GNU differ on the '-f' vs '-F' flags, we need two different + // commands. Lovely. + String cmdShellGNU = "script -fqe /dev/null"; + String cmdShellBSD = "script -q -F /dev/null"; + + // ptypipe is another solution that permits dynamic window resizing. + String cmdShellPtypipe = "ptypipe /bin/bash --login"; + + // Spawn a shell and pass its I/O to the other constructor. + if ((System.getProperty("jexer.TTerminal.ptypipe") != null) + && (System.getProperty("jexer.TTerminal.ptypipe"). + equals("true")) + ) { + ptypipe = true; + spawnShell(cmdShellPtypipe); + } else if (System.getProperty("os.name").startsWith("Windows")) { + spawnShell(cmdShellWindows); + } else if (System.getProperty("os.name").startsWith("Mac")) { + spawnShell(cmdShellBSD); + } else if (System.getProperty("os.name").startsWith("Linux")) { + spawnShell(cmdShellGNU); + } else { + // When all else fails, assume GNU. + spawnShell(cmdShellGNU); + } + } + /** * Terminate the child of the 'script' process used on POSIX. This may * or may not work. diff --git a/src/jexer/bits/ColorTheme.java b/src/jexer/bits/ColorTheme.java index 1c7c620..e55398a 100644 --- a/src/jexer/bits/ColorTheme.java +++ b/src/jexer/bits/ColorTheme.java @@ -130,27 +130,39 @@ public final class ColorTheme { String line = bufferedReader.readLine(); for (; line != null; line = bufferedReader.readLine()) { String key; - String bold; + boolean bold = false; + boolean blink = false; String foreColor; String backColor; + String token; // Look for lines that resemble: // "key = blah on blah" // "key = bold blah on blah" + // "key = blink bold blah on blah" + // "key = bold blink blah on blah" + // "key = blink blah on blah" StringTokenizer tokenizer = new StringTokenizer(line); key = tokenizer.nextToken(); if (!tokenizer.nextToken().equals("=")) { // Skip this line continue; } - bold = tokenizer.nextToken(); - if (!bold.toLowerCase().equals("bold")) { - // "key = blah on blah" - foreColor = bold; - } else { - // "key = bold blah on blah" - foreColor = tokenizer.nextToken().toLowerCase(); + token = tokenizer.nextToken(); + while (token.equals("bold") || token.equals("blink")) { + if (token.equals("bold")) { + bold = true; + token = tokenizer.nextToken(); + } + if (token.equals("blink")) { + blink = true; + token = tokenizer.nextToken(); + } } + + // What's left is "blah on blah" or "blah" + foreColor = token.toLowerCase(); + if (!tokenizer.nextToken().toLowerCase().equals("on")) { // Skip this line continue; @@ -158,9 +170,12 @@ public final class ColorTheme { backColor = tokenizer.nextToken().toLowerCase(); CellAttributes color = new CellAttributes(); - if (bold.equals("bold")) { + if (bold) { color.setBold(true); } + if (blink) { + color.setBlink(true); + } color.setForeColor(Color.getColor(foreColor)); color.setBackColor(Color.getColor(backColor)); colors.put(key, color); diff --git a/src/jexer/tterminal/ECMA48.java b/src/jexer/tterminal/ECMA48.java index 96f4e40..31bb5b1 100644 --- a/src/jexer/tterminal/ECMA48.java +++ b/src/jexer/tterminal/ECMA48.java @@ -567,9 +567,11 @@ public class ECMA48 implements Runnable { * @param height the new height */ public final void setHeight(final int height) { + int delta = height - this.height; this.height = height; - if (scrollRegionBottom >= height) { - scrollRegionBottom = height - 1; + scrollRegionBottom += delta; + if (scrollRegionBottom < 0) { + scrollRegionBottom = height; } if (scrollRegionTop >= scrollRegionBottom) { scrollRegionTop = 0; -- 2.27.0