From 855658e31951fe5c7c09b2fea66d294e6ac2387d Mon Sep 17 00:00:00 2001 From: Sergix Date: Sat, 25 Nov 2017 11:49:46 -0500 Subject: [PATCH 01/13] Fix Docs --- docs/release/jterm-v0.7.0-docs.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/release/jterm-v0.7.0-docs.md b/docs/release/jterm-v0.7.0-docs.md index 0629de9..6bef2c2 100644 --- a/docs/release/jterm-v0.7.0-docs.md +++ b/docs/release/jterm-v0.7.0-docs.md @@ -17,15 +17,15 @@ This document provides information on changes included in release version "0.7.0 ## Build Targets ``` -[VERSION] [FILE] [STATE] +[VERSION] [FILE] [STATE] 0.1.0 jterm-v0.1.0.jar OK 0.2.0 jterm-v0.2.0.jar OK 0.2.1 jterm-v0.2.1.jar OK 0.3.0 jterm-v0.3.0.jar DEPRECATED 0.3.1 jterm-v0.3.1.jar OK 0.4.0 jterm-v0.4.0.jar OK -0.4.1 jterm-v0.4.1.jar OK -0.5.0 jterm-v0.5.0.jar OK +0.4.1 jterm-v0.4.1.jar OK +0.5.0 jterm-v0.5.0.jar OK 0.5.1 jterm-v0.5.1.jar OK 0.6.0 jterm-v0.6.0.jar OK 0.6.1 jterm-v0.6.1.jar DEPRECATED @@ -190,4 +190,4 @@ The `download`, `rmdir`, `mv`/`move`, `rn`, and 'regex' commands have been added > JTerm 0.7.0 > `jterm-v0.7.0.jar` > This project and its source are held under the GNU General Public License, located in the LICENSE file in the project's directory. -> (c) 2017 \ No newline at end of file +> (c) 2017 From 9c882e19c09ec0fd1110a3e8adfd7696db201642 Mon Sep 17 00:00:00 2001 From: Sergix Date: Sun, 7 Oct 2018 21:19:47 -0400 Subject: [PATCH 02/13] Update README for archival :( --- README.md | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index aaf7123..8c0da37 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,6 @@ -[![JTerm](https://sergix.github.io/img/jterm.png)](https://sergix.github.io/projects/jterm/index.html) +# This project's development has halted indefinitely. Feel free, of course, to fork this project! -![Build Status](https://travis-ci.org/Sergix/JTerm.svg?branch=master) -[![Code Triagers Badge](https://www.codetriage.com/sergix/jterm/badges/users.svg)](https://www.codetriage.com/sergix/jterm) - -[![Stories in Ready](https://badge.waffle.io/Sergix/JTerm.svg?label=ready&title=Ready)](http://waffle.io/Sergix/JTerm) -[![Throughput Graph](https://graphs.waffle.io/Sergix/JTerm/throughput.svg)](https://waffle.io/Sergix/JTerm/metrics/throughput) +![JTerm](https://www.sergix.net/assets/img/logo/jterm.png) ## What is it? A terminal written for cross-platform usage. @@ -17,15 +13,9 @@ Because the JTerm project is written in Java, this provides it various advantage Plus, it's open source, so if you find any issues, you can help out everyone else using JTerm! ## Where do I get it? -Check the [releases](https://github.com/Sergix/JTerm/releases) page for binaries, as well as the source code. You can also look in the `/builds` directory for changelogs and other build-related stuff. - -## How can I help? -View the [Contributing Guidelines](https://github.com/Sergix/JTerm/blob/master/CONTRIBUTING.md) for more information. The JTerm project is open to anyone and any code! - -## Slack -The JTerm project now has a Slack messaging group! Request to join the [Sergix](https://sergix.slack.com/) team to recieve notifications on updates, Travis CI build status, and more! +Check the [releases](https://github.com/Sergix/JTerm/releases) page for binaries, as well as the source code. You can also look in the `/builds` directory for changelogs and other build-related files. > JTerm v0.7.0 > `jterm-v0.7.0.jar` > This project and its source are held under the GNU General Public License, located in the LICENSE file in the project's directory. -> (c) 2017 +> 2018 From 687eb75a45b54aca11bbc8e03d6127d3fd5943a8 Mon Sep 17 00:00:00 2001 From: T145 Date: Tue, 9 Oct 2018 17:17:26 -0700 Subject: [PATCH 03/13] Updated Git configuration --- .gitattributes | 28 ++++++++++ .gitignore | 143 +++++++++++++++++++++---------------------------- 2 files changed, 89 insertions(+), 82 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2681eb5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,28 @@ +# Set default behaviour, in case users don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files we want to always be normalized and converted to native line endings on checkout. +# These files are text and should be normalized (Convert crlf => lf) +*.java text +*.properties text +*.sh text +*.md text +*.info text +*.txt text +*.json text + +# Denote all files that are truly binary and should not be modified. +# (binary is a macro for -text -diff) +*.class binary +*.gif binary +*.ico binary +*.jar binary +*.jpg binary +*.jpeg binary +*.png binary + +# Make sure Windows batch files preserve CR/LF line endings, otherwise they may not be able to execute. Windows +# batch files require a CR/LF for labels to work properly, otherwise they may fail when labels straddle 512-byte +# block boundaries. This is important when files are downloaded through a zip archive that was authored on a +# Linux machine (the default behavior on GitHub) +*.bat eol=crlf diff --git a/.gitignore b/.gitignore index 14a9031..b10b82f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,90 +1,69 @@ -# Log file -*.log -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.war -*.ear -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - - -# ignore gradle artifacts -.gradle/ -gradlew.bat -build/libs/ -build/tmp/ - -# test scripts -test.bat - -target/ -combined-batch.bat - -# IDEA files -.idea/ -jterm.iml - -# eclipse files -.classpath -.project -.settings - -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff: -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/dictionaries - -# Sensitive or high-churn files: -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.xml -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml - -# Gradle: -.idea/**/gradle.xml -.idea/**/libraries - -# CMake -cmake-build-debug/ - -# Mongo Explorer plugin: -.idea/**/mongoSettings.xml - -## File-based project format: +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore files that are not wanted in general +.* +_MACOSX +*.db + +# Don't ignore GitHub files +!.github +!.gitignore +!.gitattributes + +# or EditorConfig +!.editorconfig + +# Ignore Eclipse files +/bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +*.launch + +# Ignore IntelliJ IDEA files +*.iml +*.ipr *.iws - -## Plugin-specific files: - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin +/out/ atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties +# Ignore NetBeans files +nbproject/private/ +/build/ +nbbuild/ +/dist/ +nbdist/ +nbactions.xml +nb-configuration.xml + +# Ignore JVM crash logs +hs_err_pid* + +# Ignore generic & archive binaries +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip +*.war +*.ear +*.sar +*.class + +# Project-specific dependencies +/target/ \ No newline at end of file From 49816ce060eeecf2429caa41609c9d71bfaa1d06 Mon Sep 17 00:00:00 2001 From: T145 Date: Tue, 9 Oct 2018 17:22:19 -0700 Subject: [PATCH 04/13] Updated docs TODO: Fix broken links --- CODE_OF_CONDUCT.md => .github/CODE_OF_CONDUCT.md | 0 CONTRIBUTING.md => .github/CONTRIBUTING.md | 0 ISSUE_TEMPLATE.md => .github/ISSUE_TEMPLATE.md | 0 README.md => .github/README.md | 7 ------- ROADMAP.md => .github/ROADMAP.md | 0 changelog.txt => .github/changelog.txt | 0 6 files changed, 7 deletions(-) rename CODE_OF_CONDUCT.md => .github/CODE_OF_CONDUCT.md (100%) rename CONTRIBUTING.md => .github/CONTRIBUTING.md (100%) rename ISSUE_TEMPLATE.md => .github/ISSUE_TEMPLATE.md (100%) rename README.md => .github/README.md (69%) rename ROADMAP.md => .github/ROADMAP.md (100%) rename changelog.txt => .github/changelog.txt (100%) diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md diff --git a/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md similarity index 100% rename from ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE.md diff --git a/README.md b/.github/README.md similarity index 69% rename from README.md rename to .github/README.md index 8c0da37..dc0a0dc 100644 --- a/README.md +++ b/.github/README.md @@ -1,5 +1,3 @@ -# This project's development has halted indefinitely. Feel free, of course, to fork this project! - ![JTerm](https://www.sergix.net/assets/img/logo/jterm.png) ## What is it? @@ -14,8 +12,3 @@ Plus, it's open source, so if you find any issues, you can help out everyone els ## Where do I get it? Check the [releases](https://github.com/Sergix/JTerm/releases) page for binaries, as well as the source code. You can also look in the `/builds` directory for changelogs and other build-related files. - -> JTerm v0.7.0 -> `jterm-v0.7.0.jar` -> This project and its source are held under the GNU General Public License, located in the LICENSE file in the project's directory. -> 2018 diff --git a/ROADMAP.md b/.github/ROADMAP.md similarity index 100% rename from ROADMAP.md rename to .github/ROADMAP.md diff --git a/changelog.txt b/.github/changelog.txt similarity index 100% rename from changelog.txt rename to .github/changelog.txt From 2ba086201d7c3f21f5b6a6b5f635134a53d63efd Mon Sep 17 00:00:00 2001 From: sananta Date: Fri, 26 Apr 2019 22:24:20 -0700 Subject: [PATCH 05/13] Removed unnecessary throws in initWidnows function --- src/main/java/jterm/io/input/RawConsoleInput.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jterm/io/input/RawConsoleInput.java b/src/main/java/jterm/io/input/RawConsoleInput.java index 70fb03c..ce7b329 100644 --- a/src/main/java/jterm/io/input/RawConsoleInput.java +++ b/src/main/java/jterm/io/input/RawConsoleInput.java @@ -129,7 +129,7 @@ private static int getwch() { return c; } // normal key - private static synchronized void initWindows() throws IOException { + private static synchronized void initWindows() { if (initDone) { return; } From f9d492bbf40fd8af885012cb613b7046ec077627 Mon Sep 17 00:00:00 2001 From: nanoandrew4 Date: Sat, 3 Aug 2019 18:15:13 +0200 Subject: [PATCH 06/13] Ported code from ezcli project to JTerm. This includes passing commands to the underlying system (currently only Unix/bash commands are supported), and reading/writing a history file similar to bash_history. The ported code also includes more documentation, which will be expanded on --- src/main/java/jterm/JTerm.java | 17 +- .../jterm/io/handlers/ArrowKeyHandler.java | 106 +++++ .../java/jterm/io/handlers/InputHandler.java | 86 ++++ .../java/jterm/io/handlers/KeyHandler.java | 99 +++++ .../jterm/io/handlers/events/CharEvent.java | 8 + .../java/jterm/io/handlers/events/Event.java | 13 + src/main/java/jterm/io/input/Input.java | 88 +++++ .../java/jterm/io/input/InputHandler.java | 6 +- .../java/jterm/io/input/RawConsoleInput.java | 368 ------------------ src/main/java/jterm/io/input/UnixInput.java | 179 +++++++++ src/main/java/jterm/io/input/WinInput.java | 128 ++++++ .../jterm/io/terminal/HeadlessTerminal.java | 226 +++++++++++ .../io/terminal/TermArrowKeyProcessor.java | 126 ++++++ .../jterm/io/terminal/TermInputProcessor.java | 180 +++++++++ .../jterm/io/terminal/TermKeyProcessor.java | 87 +++++ 15 files changed, 1334 insertions(+), 383 deletions(-) create mode 100644 src/main/java/jterm/io/handlers/ArrowKeyHandler.java create mode 100644 src/main/java/jterm/io/handlers/InputHandler.java create mode 100644 src/main/java/jterm/io/handlers/KeyHandler.java create mode 100644 src/main/java/jterm/io/handlers/events/CharEvent.java create mode 100644 src/main/java/jterm/io/handlers/events/Event.java create mode 100644 src/main/java/jterm/io/input/Input.java delete mode 100644 src/main/java/jterm/io/input/RawConsoleInput.java create mode 100644 src/main/java/jterm/io/input/UnixInput.java create mode 100644 src/main/java/jterm/io/input/WinInput.java create mode 100644 src/main/java/jterm/io/terminal/HeadlessTerminal.java create mode 100644 src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java create mode 100644 src/main/java/jterm/io/terminal/TermInputProcessor.java create mode 100644 src/main/java/jterm/io/terminal/TermKeyProcessor.java diff --git a/src/main/java/jterm/JTerm.java b/src/main/java/jterm/JTerm.java index cde296e..6c7cfbc 100644 --- a/src/main/java/jterm/JTerm.java +++ b/src/main/java/jterm/JTerm.java @@ -21,12 +21,12 @@ import jterm.command.CommandException; import jterm.command.CommandExecutor; import jterm.gui.Terminal; -import jterm.io.input.InputHandler; import jterm.io.input.Keys; -import jterm.io.output.Printer; -import jterm.io.output.TextColor; import jterm.io.output.GuiPrinter; import jterm.io.output.HeadlessPrinter; +import jterm.io.output.Printer; +import jterm.io.output.TextColor; +import jterm.io.terminal.HeadlessTerminal; import jterm.util.Util; import java.io.BufferedReader; @@ -78,15 +78,8 @@ public static void main(String[] args) { } JTerm.out.println(TextColor.INFO, JTerm.LICENSE); JTerm.out.printPrompt(); - if(headless){ - try { - while (true) { - InputHandler.read(); - } - } catch (IOException e){ - e.printStackTrace(); - } - } + if (headless) + new HeadlessTerminal().run(); } public static void executeCommand(String options) { diff --git a/src/main/java/jterm/io/handlers/ArrowKeyHandler.java b/src/main/java/jterm/io/handlers/ArrowKeyHandler.java new file mode 100644 index 0000000..c4a93cc --- /dev/null +++ b/src/main/java/jterm/io/handlers/ArrowKeyHandler.java @@ -0,0 +1,106 @@ +package jterm.io.handlers; + +import jterm.io.handlers.events.Event; +import jterm.io.input.Keys; + +/** + * Abstract class specifying how arrow keys should be handled. + * Each module must implement its own set of Events. + * + * @see Event + */ +public abstract class ArrowKeyHandler { + + // Last arrow key that was pressed (if any other key is pressed sets to Keys.NONE) + protected static Keys lastArrowPress = Keys.NONE; + + private long lastPress = System.currentTimeMillis(); + + // Events to be implemented by any class that inherits ArrowKeyHandler + public Event lArrEvent; + public Event rArrEvent; + public Event uArrEvent; + public Event dArrEvent; + + /** + * Checks if last input was arrow key (only on Windows). + * + * @param i Integer value of last key press + * @return Arrow key pressed (or Keys.NONE if no arrow key was pressed) + */ + public static Keys arrowKeyCheckWindows(final int i) { + switch (i) { + case 57416: + return Keys.UP; + case 57424: + return Keys.DOWN; + case 57421: + return Keys.RIGHT; + case 57419: + return Keys.LEFT; + default: + return Keys.NONE; + } + } + + /** + * Checks if input was arrow key (only on Unix). + *

+ * When Unix processes arrow keys, they are read as a sequence of 3 numbers, for example 27 91 65 + * which means that the implementation of InputHandler owning the implementation of ArrowKeyHandler + * must read 3 values, only blocking for the first. That way, if an arrow key is pressed, all three values are + * caught, and if not, no input is lost, since the time for catching in non-blocking mode is ~1ms, and keyboard + * presses are only detected every ~30ms interval. + * + * @param i Integer value of last key press + * @return Arrow key pressed (or Keys.NONE if no arrow key was pressed) + */ + public static Keys arrowKeyCheckUnix(final int... i) { + + if (i[0] == 27 && i[1] == 91) { + switch (i[2]) { + case 65: + return Keys.UP; + case 66: + return Keys.DOWN; + case 67: + return Keys.RIGHT; + case 68: + return Keys.LEFT; + default: + return Keys.NONE; + } + } + + return Keys.NONE; + } + + /** + * Process an arrow key press. + *

+ * Handles sending event status to all Modules, so they can react appropriately, as well + * relegating to the appropriate lambda expression. + * + * @param ak Arrow key to process + */ + public void process(final Keys ak) { + if (ak != Keys.NONE && System.currentTimeMillis() - lastPress > InputHandler.minWaitTime) { + lastPress = System.currentTimeMillis(); + switch (ak) { + case UP: + uArrEvent.process(); + break; + case DOWN: + dArrEvent.process(); + break; + case LEFT: + lArrEvent.process(); + break; + case RIGHT: + rArrEvent.process(); + break; + } + } + } +} + diff --git a/src/main/java/jterm/io/handlers/InputHandler.java b/src/main/java/jterm/io/handlers/InputHandler.java new file mode 100644 index 0000000..dde3b31 --- /dev/null +++ b/src/main/java/jterm/io/handlers/InputHandler.java @@ -0,0 +1,86 @@ +package jterm.io.handlers; + + +import jterm.JTerm; +import jterm.io.input.Input; + +import java.io.IOException; + +/** + * Abstract class specifying how input should be handled. + * Each module must run its own implementation of this class. + */ +public abstract class InputHandler { + + // Key handlers for module + protected ArrowKeyHandler arrowKeyHandler; + protected KeyHandler keyHandler; + + /** + * Determines how long the program will ignore user input (in ms). + * Prevents program from going visually insane, by not overloading the system with too much output. + */ + public static int minWaitTime = 30; + + /** + * Create key handler object with null arrow key handler and key handler. + * Only call this constructor if you plan to manually assign the handlers + * straight after this constructor returns. + */ + public InputHandler() { + arrowKeyHandler = null; + keyHandler = null; + } + + /** + * Set KeyHandler and ArrowKeyHandler for module. + *

+ * Classes extending KeyHandler and ArrowKeyHandler should passed as parameters, + * so they can be later used to process input for this module. + * + * @param kh Key handler to use + * @param akh Arrow key handler to use + */ + public InputHandler(KeyHandler kh, ArrowKeyHandler akh) { + this.arrowKeyHandler = akh; + this.keyHandler = kh; + } + + /** + * Code to run when processing input for module. + * Can (and should) make use of keyHandler and/or arrowKeyHandler for input processing. + */ + public abstract void process(int input); + + /** + * Returns key char value of last key pressed. + * + * @return Char value of key pressed + */ + public static char getKey() { + try { + return (char) Input.read(true); + } catch (IOException e) { + e.printStackTrace(); + } + + return 0; + } + + /** + * Clears a line in the console of size line.length(). + * + * @param line line to be cleared + * @param clearPrompt choose to clear prompt along with line (only use true if prompt exists) + */ + public static void clearLine(String line, boolean clearPrompt) { + for (int i = 0; i < line.length() + (clearPrompt ? JTerm.PROMPT.length() : 0); i++) + System.out.print("\b"); + + for (int i = 0; i < line.length() + (clearPrompt ? JTerm.PROMPT.length() : 0); i++) + System.out.print(" "); + + for (int i = 0; i < line.length() + (clearPrompt ? JTerm.PROMPT.length() : 0); i++) + System.out.print("\b"); + } +} diff --git a/src/main/java/jterm/io/handlers/KeyHandler.java b/src/main/java/jterm/io/handlers/KeyHandler.java new file mode 100644 index 0000000..b76a34b --- /dev/null +++ b/src/main/java/jterm/io/handlers/KeyHandler.java @@ -0,0 +1,99 @@ +package jterm.io.handlers; + +import jterm.JTerm; +import jterm.io.handlers.events.CharEvent; +import jterm.io.handlers.events.Event; +import jterm.io.input.Keys; + +import java.util.HashMap; + +/** + * Abstract class specifying methods needed to process key events properly. + * Each module must run its own implementation of this class. + */ +public abstract class KeyHandler { + + // Stores all key integer values and maps them to the values in Keys enum + private static HashMap keymap = new HashMap<>(); + + private long lastPress = System.currentTimeMillis(); + + // Events to implemented by each class that inherits KeyHandler + public Event tabEvent; + public Event newLineEvent; + public CharEvent charEvent; + public Event backspaceEvent; + + // Returns a map pairing key values stored as ints to values in Keys enum + public static HashMap getKeymap() { + return keymap; + } + + /** + * Processes all input by relegating it to the appropriate lambda expression + * and triggering events so that other Modules can react appropriately. + * + * @param input ASCII key code representing key pressed + */ + public void process(int input) { + Keys key = getKey(input); + + if (System.currentTimeMillis() - lastPress < InputHandler.minWaitTime) + return; + lastPress = System.currentTimeMillis(); + + if (key == Keys.BACKSPACE) + backspaceEvent.process(); + else if (key == Keys.TAB) + tabEvent.process(); + else if (key == Keys.NWLN) + newLineEvent.process(); + else if (input > 31 && input < 127) + charEvent.process((char) input); + + signalCatch(input); + } + + /** + * Catches system signals, such as Ctrl+C. + * Useful for running while waiting for some process to finish, so user can cancel if they wish. + * For use in above use case, loop while process is not done and pass input.read(false) to this method. + * + * @param input ASCII key code value of key pressed + * @return true if process should be cancelled, false if no signals were caught + */ + public static Keys signalCatch(int input) { + if (input == 3) // Ctrl+C + return Keys.CTRL_C; + if (input == 26) { // Ctrl+Z -> force quit program + System.out.println(); + System.exit(122); + } + return Keys.NONE; + } + + /** + * Loads all integer values of keys to keymap. + */ + public static void initKeysMap() { + if (JTerm.IS_WIN) { + keymap.put(8, Keys.BACKSPACE); + keymap.put(9, Keys.TAB); + keymap.put(13, Keys.NWLN); + } else if (JTerm.IS_UNIX) { + keymap.put(127, Keys.BACKSPACE); + keymap.put((int) '\t', Keys.TAB); + keymap.put((int) '\n', Keys.NWLN); + } + } + + /** + * Returns associated key value from keymap. + * + * @param i Integer value of key pressed + */ + protected static Keys getKey(int i) { + return keymap.get(i); + } +} + diff --git a/src/main/java/jterm/io/handlers/events/CharEvent.java b/src/main/java/jterm/io/handlers/events/CharEvent.java new file mode 100644 index 0000000..d2b809a --- /dev/null +++ b/src/main/java/jterm/io/handlers/events/CharEvent.java @@ -0,0 +1,8 @@ +package jterm.io.handlers.events; + +/** + * Functional interface allowing the implementation of a lambda function that processes charEvents. + */ +public interface CharEvent { + void process(char input); +} diff --git a/src/main/java/jterm/io/handlers/events/Event.java b/src/main/java/jterm/io/handlers/events/Event.java new file mode 100644 index 0000000..9fd2ea6 --- /dev/null +++ b/src/main/java/jterm/io/handlers/events/Event.java @@ -0,0 +1,13 @@ +package jterm.io.handlers.events; + +/** + * Functional interface allowing the implementation of an event. + * This and CharEvent exist so that another module can implement their own versions + * of certain event handlers, in the event that the provided implementation does something + * undesirable or inconvenient. + * + * @see CharEvent + */ +public interface Event { + void process(); +} diff --git a/src/main/java/jterm/io/input/Input.java b/src/main/java/jterm/io/input/Input.java new file mode 100644 index 0000000..b568408 --- /dev/null +++ b/src/main/java/jterm/io/input/Input.java @@ -0,0 +1,88 @@ +package jterm.io.input; + +// Copyright 2015 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland +// www.source-code.biz, www.inventec.ch/chdh +// +// This module is multi-licensed and may be used under the terms of any of the following licenses: +// +// LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html +// EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal +// +// Please contact the author if you need another license. +// This module is provided "as is", without warranties of any kind. +// +// Home page: http://www.source-code.biz/snippets/java/RawConsoleInput + +import java.io.IOException; + +/** + * A JNA based driver for reading single characters from the console. + *

+ *

This class is used for console mode programs. + * It supports non-blocking reads of single key strokes without echo. + */ + +public class Input { + + protected static final boolean isWindows = System.getProperty("os.name").startsWith("Windows"); + protected static final int invalidKey = 0xFFFE; + protected static final String invalidKeyStr = String.valueOf((char) invalidKey); + + protected static boolean initDone; + protected static boolean stdinIsConsole; + protected static boolean consoleModeAltered; + + /** + * Reads a character from the console without echo. + * + * @param wait true to wait until an input character is available, + * false to return immediately if no character is available. + * @return -2 if wait is false and no character is available. + * -1 on EOF. + * Otherwise an Unicode character code within the range 0 to 0xFFFF. + */ + public static int read(boolean wait) throws IOException { + if (isWindows) { + return WinInput.readWindows(wait); + } else { + try { + return UnixInput.readUnix(wait); + } catch (Exception e) { + System.err.println("Error reading input"); + } + } + + return -1; + } + + /** + * Resets console mode to normal line mode with echo. + *

+ *

On Windows this method re-enables Ctrl-C processing. + *

+ *

On Unix this method switches the console back to echo mode. + * read() leaves the console in non-echo mode. + */ + private static void resetConsoleMode() throws IOException { + if (isWindows) { + WinInput.resetConsoleModeWindows(); + } else { + try { + UnixInput.resetConsoleModeUnix(); + } catch (Exception e) { + System.err.println("Error resetting console mode"); + } + } + } + + protected static void registerShutdownHook() { + Runtime.getRuntime().addShutdownHook(new Thread(Input::shutdownHook)); + } + + private static void shutdownHook() { + try { + resetConsoleMode(); + } catch (Exception ignored) { + } + } +} \ No newline at end of file diff --git a/src/main/java/jterm/io/input/InputHandler.java b/src/main/java/jterm/io/input/InputHandler.java index 98a0385..dff9cf7 100644 --- a/src/main/java/jterm/io/input/InputHandler.java +++ b/src/main/java/jterm/io/input/InputHandler.java @@ -34,9 +34,9 @@ private static void setCommandListPosition(int commandListPos) { public static void read() throws IOException { - int c1 = RawConsoleInput.read(true); - int c2 = RawConsoleInput.read(false); - int c3 = RawConsoleInput.read(false); + int c1 = Input.read(true); + int c2 = Input.read(false); + int c3 = Input.read(false); Keys keyType; if (!(c2 == -2 && c3 == -2)) c1 = (c1 + c2 + c3) * -1; keyType = Keys.getKeyByValue(c1); diff --git a/src/main/java/jterm/io/input/RawConsoleInput.java b/src/main/java/jterm/io/input/RawConsoleInput.java deleted file mode 100644 index ce7b329..0000000 --- a/src/main/java/jterm/io/input/RawConsoleInput.java +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright 2015 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland -// www.source-code.biz, www.inventec.ch/chdh -// -// This module is multi-licensed and may be used under the terms of any of the following licenses: -// -// LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html -// EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal -// -// Please contact the author if you need another license. -// This module is provided "as is", without warranties of any kind. -// -// Home page: http://www.source-code.biz/snippets/java/RawConsoleInput - -package jterm.io.input; - -import com.sun.jna.*; -import com.sun.jna.ptr.IntByReference; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.CodingErrorAction; -import java.util.Arrays; -import java.util.List; - -/** - * A JNA based driver for reading single characters from the console. - *

- *

This class is used for console mode programs. - * It supports non-blocking reads of single key strokes without echo. - */ -public class RawConsoleInput { - - private static final boolean isWindows = System.getProperty("os.name").startsWith("Windows"); - private static final int invalidKey = 0xFFFE; - private static final String invalidKeyStr = String.valueOf((char) invalidKey); - - private static boolean initDone; - private static boolean stdinIsConsole; - private static boolean consoleModeAltered; - - /** - * Reads a character from the console without echo. - * - * @param wait true to wait until an input character is available, - * false to return immediately if no character is available. - * @return -2 if wait is false and no character is available. - * -1 on EOF. - * Otherwise an Unicode character code within the range 0 to 0xFFFF. - */ - public static int read(boolean wait) throws IOException { - if (isWindows) { - return readWindows(wait); - } else { - return readUnix(wait); - } - } - - /** - * Resets console mode to normal line mode with echo. - *

- *

On Windows this method re-enables Ctrl-C processing. - *

- *

On Unix this method switches the console back to echo mode. - * read() leaves the console in non-echo mode. - */ - public static void resetConsoleMode() throws IOException { - if (isWindows) { - resetConsoleModeWindows(); - } else { - resetConsoleModeUnix(); - } - } - - private static void registerShutdownHook() { - Runtime.getRuntime().addShutdownHook(new Thread(RawConsoleInput::shutdownHook)); - } - - private static void shutdownHook() { - try { - resetConsoleMode(); - } catch (Exception e) { - } - } - -//--- Windows ------------------------------------------------------------------ - -// The Windows version uses _kbhit() and _getwch() from msvcrt.dll. - - private static Msvcrt msvcrt; - private static Kernel32 kernel32; - private static Pointer consoleHandle; - private static int originalConsoleMode; - - private static int readWindows(boolean wait) throws IOException { - initWindows(); - if (!stdinIsConsole) { - int c = msvcrt.getwchar(); - if (c == 0xFFFF) { - c = -1; - } - return c; - } - consoleModeAltered = true; - setConsoleMode(consoleHandle, originalConsoleMode & ~Kernel32Defs.ENABLE_PROCESSED_INPUT); - // ENABLE_PROCESSED_INPUT must remain off to prevent Ctrl-C from beeing processed by the system - // while the program is not within getwch(). - if (!wait && msvcrt._kbhit() == 0) { - return -2; - } // no key available - return getwch(); - } - - private static int getwch() { - int c = msvcrt._getwch(); - if (c == 0 || c == 0xE0) { // Function key or arrow key - c = msvcrt._getwch(); - if (c >= 0 && c <= 0x18FF) { - return 0xE000 + c; - } // construct key code in private Unicode range - return invalidKey; - } - if (c < 0 || c > 0xFFFF) { - return invalidKey; - } - return c; - } // normal key - - private static synchronized void initWindows() { - if (initDone) { - return; - } - msvcrt = (Msvcrt) Native.loadLibrary("msvcrt", Msvcrt.class); - kernel32 = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class); - try { - consoleHandle = getStdInputHandle(); - originalConsoleMode = getConsoleMode(consoleHandle); - stdinIsConsole = true; - } catch (IOException e) { - stdinIsConsole = false; - } - if (stdinIsConsole) { - registerShutdownHook(); - } - initDone = true; - } - - private static Pointer getStdInputHandle() throws IOException { - Pointer handle = kernel32.GetStdHandle(Kernel32Defs.STD_INPUT_HANDLE); - if (Pointer.nativeValue(handle) == 0 || Pointer.nativeValue(handle) == Kernel32Defs.INVALID_HANDLE_VALUE) { - throw new IOException("GetStdHandle(STD_INPUT_HANDLE) failed."); - } - return handle; - } - - private static int getConsoleMode(Pointer handle) throws IOException { - IntByReference mode = new IntByReference(); - int rc = kernel32.GetConsoleMode(handle, mode); - if (rc == 0) { - throw new IOException("GetConsoleMode() failed."); - } - return mode.getValue(); - } - - private static void setConsoleMode(Pointer handle, int mode) throws IOException { - int rc = kernel32.SetConsoleMode(handle, mode); - if (rc == 0) { - throw new IOException("SetConsoleMode() failed."); - } - } - - private static void resetConsoleModeWindows() throws IOException { - if (!initDone || !stdinIsConsole || !consoleModeAltered) { - return; - } - setConsoleMode(consoleHandle, originalConsoleMode); - consoleModeAltered = false; - } - - private static interface Msvcrt extends Library { - int _kbhit(); - - int _getwch(); - - int getwchar(); - } - - private static class Kernel32Defs { - static final int STD_INPUT_HANDLE = -10; - static final long INVALID_HANDLE_VALUE = (Pointer.SIZE == 8) ? -1 : 0xFFFFFFFFL; - static final int ENABLE_PROCESSED_INPUT = 0x0001; - static final int ENABLE_LINE_INPUT = 0x0002; - static final int ENABLE_ECHO_INPUT = 0x0004; - static final int ENABLE_WINDOW_INPUT = 0x0008; - } - - private static interface Kernel32 extends Library { - int GetConsoleMode(Pointer hConsoleHandle, IntByReference lpMode); - - int SetConsoleMode(Pointer hConsoleHandle, int dwMode); - - Pointer GetStdHandle(int nStdHandle); - } - -//--- Unix --------------------------------------------------------------------- - -// The Unix version uses tcsetattr() to switch the console to non-canonical mode, -// System.in.available() to check whether data is available and System.in.read() -// to read bytes from the console. -// A CharsetDecoder is used to convert bytes to characters. - - private static final int stdinFd = 0; - private static Libc libc; - private static CharsetDecoder charsetDecoder; - private static Termios originalTermios; - private static Termios rawTermios; - private static Termios intermediateTermios; - - private static int readUnix(boolean wait) throws IOException { - initUnix(); - if (!stdinIsConsole) { // STDIN is not a console - return readSingleCharFromByteStream(System.in); - } - consoleModeAltered = true; - setTerminalAttrs(stdinFd, rawTermios); // switch off canonical mode, echo and signals - try { - if (!wait && System.in.available() == 0) { - return -2; - } // no input available - return readSingleCharFromByteStream(System.in); - } finally { - setTerminalAttrs(stdinFd, intermediateTermios); - } - } // reset some console attributes - - private static Termios getTerminalAttrs(int fd) throws IOException { - Termios termios = new Termios(); - try { - int rc = libc.tcgetattr(fd, termios); - if (rc != 0) { - throw new RuntimeException("tcgetattr() failed."); - } - } catch (LastErrorException e) { - throw new IOException("tcgetattr() failed.", e); - } - return termios; - } - - private static void setTerminalAttrs(int fd, Termios termios) throws IOException { - try { - int rc = libc.tcsetattr(fd, LibcDefs.TCSANOW, termios); - if (rc != 0) { - throw new RuntimeException("tcsetattr() failed."); - } - } catch (LastErrorException e) { - throw new IOException("tcsetattr() failed.", e); - } - } - - private static int readSingleCharFromByteStream(InputStream inputStream) throws IOException { - byte[] inBuf = new byte[4]; - int inLen = 0; - while (true) { - if (inLen >= inBuf.length) { // input buffer overflow - return invalidKey; - } - int b = inputStream.read(); // read next byte - if (b == -1) { // EOF - return -1; - } - inBuf[inLen++] = (byte) b; - int c = decodeCharFromBytes(inBuf, inLen); - if (c != -1) { - return c; - } - } - } - - // (This method is synchronized because the charsetDecoder must only be used by a single thread at once.) - private static synchronized int decodeCharFromBytes(byte[] inBytes, int inLen) { - charsetDecoder.reset(); - charsetDecoder.onMalformedInput(CodingErrorAction.REPLACE); - charsetDecoder.replaceWith(invalidKeyStr); - ByteBuffer in = ByteBuffer.wrap(inBytes, 0, inLen); - CharBuffer out = CharBuffer.allocate(1); - charsetDecoder.decode(in, out, false); - if (out.position() == 0) { - return -1; - } - return out.get(0); - } - - private static synchronized void initUnix() throws IOException { - if (initDone) { - return; - } - libc = (Libc) Native.loadLibrary("c", Libc.class); - stdinIsConsole = libc.isatty(stdinFd) == 1; - charsetDecoder = Charset.defaultCharset().newDecoder(); - if (stdinIsConsole) { - originalTermios = getTerminalAttrs(stdinFd); - rawTermios = new Termios(originalTermios); - rawTermios.c_lflag &= ~(LibcDefs.ICANON | LibcDefs.ECHO | LibcDefs.ECHONL | LibcDefs.ISIG); - intermediateTermios = new Termios(rawTermios); - intermediateTermios.c_lflag |= LibcDefs.ICANON; - // Canonical mode can be switched off between the read() calls, but echo must remain disabled. - registerShutdownHook(); - } - initDone = true; - } - - private static void resetConsoleModeUnix() throws IOException { - if (!initDone || !stdinIsConsole || !consoleModeAltered) { - return; - } - setTerminalAttrs(stdinFd, originalTermios); - consoleModeAltered = false; - } - - protected static class Termios extends Structure { // termios.h - public int c_iflag; - public int c_oflag; - public int c_cflag; - public int c_lflag; - public byte c_line; - public byte[] filler = new byte[64]; // actual length is platform dependent - - @Override - protected List getFieldOrder() { - return Arrays.asList("c_iflag", "c_oflag", "c_cflag", "c_lflag", "c_line", "filler"); - } - - Termios() { - } - - Termios(Termios t) { - c_iflag = t.c_iflag; - c_oflag = t.c_oflag; - c_cflag = t.c_cflag; - c_lflag = t.c_lflag; - c_line = t.c_line; - filler = t.filler.clone(); - } - } - - private static class LibcDefs { - // termios.h - static final int ISIG = 0000001; - static final int ICANON = 0000002; - static final int ECHO = 0000010; - static final int ECHONL = 0000100; - static final int TCSANOW = 0; - } - - private static interface Libc extends Library { - // termios.h - int tcgetattr(int fd, Termios termios) throws LastErrorException; - - int tcsetattr(int fd, int opt, Termios termios) throws LastErrorException; - - // unistd.h - int isatty(int fd); - } - -} \ No newline at end of file diff --git a/src/main/java/jterm/io/input/UnixInput.java b/src/main/java/jterm/io/input/UnixInput.java new file mode 100644 index 0000000..432dfcc --- /dev/null +++ b/src/main/java/jterm/io/input/UnixInput.java @@ -0,0 +1,179 @@ +package jterm.io.input; + +import com.sun.jna.LastErrorException; +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Structure; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; +import java.util.Arrays; +import java.util.List; + +import static jterm.io.input.Input.*; + +public class UnixInput { + +// The Unix version uses tcsetattr() to switch the console to non-canonical mode, +// System.in.available() to check whether data is available and System.in.read() +// to read bytes from the console. +// A CharsetDecoder is used to convert bytes to characters. + + private static final int stdinFd = 0; + private static Libc libc; + private static CharsetDecoder charsetDecoder; + private static Termios originalTermios; + private static Termios rawTermios; + private static Termios intermediateTermios; + + protected static int readUnix(boolean wait) throws Exception { + initUnix(); + if (!stdinIsConsole) { // STDIN is not a console + return readSingleCharFromByteStream(System.in); + } + consoleModeAltered = true; + setTerminalAttrs(stdinFd, rawTermios); // switch off canonical mode, echo and signals + try { + if (!wait && System.in.available() == 0) { + return -2; + } // no input available + return readSingleCharFromByteStream(System.in); + } finally { + setTerminalAttrs(stdinFd, intermediateTermios); + } + } // reset some console attributes + + private static Termios getTerminalAttrs(int fd) throws Exception { + Termios termios = new Termios(); + try { + int rc = libc.tcgetattr(fd, termios); + if (rc != 0) { + throw new Exception("tcgetattr() failed."); + } + } catch (LastErrorException e) { + throw new IOException("tcgetattr() failed.", e); + } + return termios; + } + + private static void setTerminalAttrs(int fd, Termios termios) throws Exception { + try { + int rc = libc.tcsetattr(fd, LibcDefs.TCSANOW, termios); + if (rc != 0) { + throw new Exception("tcsetattr() failed."); + } + } catch (LastErrorException e) { + throw new IOException("tcsetattr() failed.", e); + } + } + + private static int readSingleCharFromByteStream(InputStream inputStream) throws IOException { + byte[] inBuf = new byte[4]; + int inLen = 0; + while (true) { + if (inLen >= inBuf.length) { // input buffer overflow + return invalidKey; + } + int b = inputStream.read(); // read next byte + if (b == -1) { // EOF + return -1; + } + inBuf[inLen++] = (byte) b; + int c = decodeCharFromBytes(inBuf, inLen); + if (c != -1) { + return c; + } + } + } + + // (This method is synchronized because the charsetDecoder must only be used by a single thread at once.) + private static synchronized int decodeCharFromBytes(byte[] inBytes, int inLen) { + charsetDecoder.reset(); + charsetDecoder.onMalformedInput(CodingErrorAction.REPLACE); + charsetDecoder.replaceWith(invalidKeyStr); + ByteBuffer in = ByteBuffer.wrap(inBytes, 0, inLen); + CharBuffer out = CharBuffer.allocate(1); + charsetDecoder.decode(in, out, false); + if (out.position() == 0) { + return -1; + } + return out.get(0); + } + + private static synchronized void initUnix() throws Exception { + if (initDone) { + return; + } + libc = Native.loadLibrary("c", Libc.class); + stdinIsConsole = libc.isatty(stdinFd) == 1; + charsetDecoder = Charset.defaultCharset().newDecoder(); + if (stdinIsConsole) { + originalTermios = getTerminalAttrs(stdinFd); + rawTermios = new Termios(originalTermios); + rawTermios.c_lflag &= ~(LibcDefs.ICANON | LibcDefs.ECHO | LibcDefs.ECHONL | LibcDefs.ISIG); + intermediateTermios = new Termios(rawTermios); + intermediateTermios.c_lflag |= LibcDefs.ICANON; + // Canonical mode can be switched off between the read() calls, but echo must remain disabled. + registerShutdownHook(); + } + initDone = true; + } + + protected static void resetConsoleModeUnix() throws Exception { + if (!initDone || !stdinIsConsole || !consoleModeAltered) { + return; + } + setTerminalAttrs(stdinFd, originalTermios); + consoleModeAltered = false; + } + + protected static class Termios extends Structure { // termios.h + public int c_iflag; + public int c_oflag; + public int c_cflag; + public int c_lflag; + public byte c_line; + public byte[] filler = new byte[64]; // actual length is platform dependent + + @Override + protected List getFieldOrder() { + return Arrays.asList("c_iflag", "c_oflag", "c_cflag", "c_lflag", "c_line", "filler"); + } + + Termios() { + } + + Termios(Termios t) { + c_iflag = t.c_iflag; + c_oflag = t.c_oflag; + c_cflag = t.c_cflag; + c_lflag = t.c_lflag; + c_line = t.c_line; + filler = t.filler.clone(); + } + } + + private static class LibcDefs { + // termios.h + private static final int ISIG = 0000001; + private static final int ICANON = 0000002; + private static final int ECHO = 0000010; + private static final int ECHONL = 0000100; + private static final int TCSANOW = 0; + } + + private interface Libc extends Library { + // termios.h + int tcgetattr(int fd, Termios termios) throws LastErrorException; + + int tcsetattr(int fd, int opt, Termios termios) throws LastErrorException; + + // unistd.h + int isatty(int fd); + } +} diff --git a/src/main/java/jterm/io/input/WinInput.java b/src/main/java/jterm/io/input/WinInput.java new file mode 100644 index 0000000..78d0e95 --- /dev/null +++ b/src/main/java/jterm/io/input/WinInput.java @@ -0,0 +1,128 @@ +package jterm.io.input; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Pointer; +import com.sun.jna.ptr.IntByReference; + +import java.io.IOException; + +import static jterm.io.input.Input.*; + +public class WinInput { + +// The Windows version uses _kbhit() and _getwch() from msvcrt.dll. + + private static Msvcrt msvcrt; + private static Kernel32 kernel32; + private static Pointer consoleHandle; + private static int originalConsoleMode; + + protected static int readWindows(boolean wait) throws IOException { + initWindows(); + if (!stdinIsConsole) { + int c = msvcrt.getwchar(); + if (c == 0xFFFF) { + c = -1; + } + return c; + } + consoleModeAltered = true; + setConsoleMode(consoleHandle, originalConsoleMode & ~Kernel32Defs.ENABLE_PROCESSED_INPUT); + // ENABLE_PROCESSED_INPUT must remain off to prevent Ctrl-C from beeing processed by the system + // while the program is not within getwch(). + if (!wait && msvcrt.kbhit() == 0) { + return -2; + } // no key available + return getwch(); + } + + private static int getwch() { + int c = msvcrt.getwch(); + if (c == 0 || c == 0xE0) { // Function key or arrow key + c = msvcrt.getwch(); + if (c >= 0 && c <= 0x18FF) { + return 0xE000 + c; + } // construct key code in private Unicode range + return invalidKey; + } + if (c < 0 || c > 0xFFFF) { + return invalidKey; + } + return c; + } // normal key + + private static synchronized void initWindows() throws IOException { + if (initDone) { + return; + } + msvcrt = Native.loadLibrary("msvcrt", Msvcrt.class); + kernel32 = Native.loadLibrary("kernel32", Kernel32.class); + try { + consoleHandle = getStdInputHandle(); + originalConsoleMode = getConsoleMode(consoleHandle); + stdinIsConsole = true; + } catch (IOException e) { + stdinIsConsole = false; + } + if (stdinIsConsole) { + registerShutdownHook(); + } + initDone = true; + } + + private static Pointer getStdInputHandle() throws IOException { + Pointer handle = kernel32.getStdHandle(Kernel32Defs.STD_INPUT_HANDLE); + if (Pointer.nativeValue(handle) == 0 || Pointer.nativeValue(handle) == Kernel32Defs.INVALID_HANDLE_VALUE) { + throw new IOException("GetStdHandle(STD_INPUT_HANDLE) failed."); + } + return handle; + } + + private static int getConsoleMode(Pointer handle) throws IOException { + IntByReference mode = new IntByReference(); + int rc = kernel32.getConsoleMode(handle, mode); + if (rc == 0) { + throw new IOException("GetConsoleMode() failed."); + } + return mode.getValue(); + } + + private static void setConsoleMode(Pointer handle, int mode) throws IOException { + int rc = kernel32.setConsoleMode(handle, mode); + if (rc == 0) { + throw new IOException("SetConsoleMode() failed."); + } + } + + protected static void resetConsoleModeWindows() throws IOException { + if (!initDone || !stdinIsConsole || !consoleModeAltered) { + return; + } + setConsoleMode(consoleHandle, originalConsoleMode); + consoleModeAltered = false; + } + + private interface Msvcrt extends Library { + int kbhit(); + + int getwch(); + + int getwchar(); + } + + private static class Kernel32Defs { + private static final int STD_INPUT_HANDLE = -10; + private static final long INVALID_HANDLE_VALUE = (Pointer.SIZE == 8) ? -1 : 0xFFFFFFFFL; + private static final int ENABLE_PROCESSED_INPUT = 0x0001; + } + + private interface Kernel32 extends Library { + int getConsoleMode(Pointer hConsoleHandle, IntByReference lpMode); + + int setConsoleMode(Pointer hConsoleHandle, int dwMode); + + Pointer getStdHandle(int nStdHandle); + } + +} diff --git a/src/main/java/jterm/io/terminal/HeadlessTerminal.java b/src/main/java/jterm/io/terminal/HeadlessTerminal.java new file mode 100644 index 0000000..0c67fe6 --- /dev/null +++ b/src/main/java/jterm/io/terminal/HeadlessTerminal.java @@ -0,0 +1,226 @@ +package jterm.io.terminal; + +import jterm.JTerm; +import jterm.io.handlers.InputHandler; +import jterm.io.output.TextColor; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +/** + * Terminal module. Interfaces directly with bash, so it is as if you were running a normal terminal session. + */ +public class HeadlessTerminal { + + // Input handler for this module + public TermInputProcessor inputProcessor; + + // Hard cap on lines in history file. If more are added after this, the oldest ones are deleted + private final static int maxLinesInHistory = 10000; + + private boolean exit; + + public HeadlessTerminal() { + inputProcessor = new TermInputProcessor(this); + + importJTermHistory(); + } + + public void run() { + exit = false; + JTerm.PROMPT = JTerm.currentDirectory + " >> "; + + JTerm.out.println(TextColor.INFO, "Entered terminal mode"); + JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); + while (!exit) + inputProcessor.process(InputHandler.getKey()); + + writeCommandsToFile(); + JTerm.PROMPT = ">> "; + } + + public void parse(String rawCommand) { + final String[] split = rawCommand.split("&&"); + JTerm.out.println(TextColor.INFO); + + for (String command : split) { + command = command.trim(); + + /* + * cd command has to be interpreted separately, since once the JVM runs any cd commands do not take effect + * inside the program, nor once it exists. + */ + if (command.startsWith("cd ")) { + changeDir(command); + continue; + } + + if ("exit".equals(command)) { + inputProcessor.setCursorPos(0); + inputProcessor.setCommand(""); + exit = true; + return; + } + + final ProcessBuilder pb; + final Process p; + try { // Assumes unix, Windows would require a separate implementation... + pb = new ProcessBuilder("/bin/bash", "-c", command); + pb.inheritIO(); // Make program and process share IO to allow user to interact with program + pb.directory(new File(JTerm.currentDirectory)); // Set working directory for command + p = pb.start(); + p.waitFor(); + p.destroy(); + } catch (IOException | IllegalArgumentException | InterruptedException e) { + System.err.println("Parsing command \"" + command + "\" failed, enter \"t-help\" for help using module."); + } + + JTerm.currentDirectory = System.getProperty("user.dir") + "/"; + } + + JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); + } + + /** + * Reads JTerm history file and stores all the previous commands in the commandHistory list. + *

+ * If for some reason this fails, it will try to read the ~/.bash_history file + */ + private void importJTermHistory() { + try { + List history = Files.readAllLines(Paths.get(JTerm.USER_HOME_DIR + "/.jterm_history")); + inputProcessor.commandHistory.addAll(history); + inputProcessor.getArrowKeyProcessor().setCommandListPosition(history.size()); + if (history.size() > 0) + return; + } catch (IOException e) { + System.err.println("Error reading JTerm history file"); + importBashHistory(); + return; + } + + importBashHistory(); + } + + /** + * Reads bash history file and stores all command listed in the commandHistory list. + */ + private void importBashHistory() { + System.out.println("You currently have an empty or non-existent history file, importing bash history..."); + try { + List bashHistory = Files.readAllLines(Paths.get(JTerm.USER_HOME_DIR + "/.bash_history")); + inputProcessor.commandHistory.addAll(bashHistory); + inputProcessor.getArrowKeyProcessor().setCommandListPosition(bashHistory.size()); + writeCommandsToFile(); + } catch (IOException e) { + System.err.println(JTerm.USER_HOME_DIR + "/.bash_history"); + System.err.println("Error reading bash history file. Aborting import..."); + } + } + + /** + * Writes the commandHistory list to the ~/.jterm_history file. If this file does not exist, it will + * be created. + */ + private void writeCommandsToFile() { + File historyFile = new File(JTerm.USER_HOME_DIR + "/.jterm_history"); + + try { + historyFile.createNewFile(); + } catch (IOException e) { + System.err.println("Error creating new history file in directory: " + JTerm.USER_HOME_DIR); + return; + } + + List original; + try { + original = Files.readAllLines(Paths.get(historyFile.getAbsolutePath())); + } catch (IOException e) { + System.err.println("Error reading lines from original history file"); + return; + } + + if (historyFile.delete()) { + historyFile = new File(JTerm.USER_HOME_DIR + "/.jterm_history"); + + final PrintWriter pw; + try { + pw = new PrintWriter(historyFile); + } catch (FileNotFoundException e) { + System.err.println("Error creating print writer"); + return; + } + + final int startPos = original.size() + + inputProcessor.commandHistory.size() > maxLinesInHistory ? inputProcessor.commandHistory.size() : 0; + for (int i = startPos; i < original.size(); i++) + pw.println(original.get(i)); + + for (String s : inputProcessor.commandHistory) + pw.println(s); + + pw.close(); + } + } + + /** + * Changes the terminals directory, since the system does not interpret chdir commands. + * Attempts to emulate the "cd" command. + * + * @param command cd command to parse + */ + public void changeDir(String command) { + final String[] chdirSplit = command.split(" "); + + if (chdirSplit.length != 2 || !"cd".equals(chdirSplit[0])) { + JTerm.out.println(TextColor.PROMPT, "Invalid chdir command passed"); + } else { + final String dirChange = chdirSplit[1]; + final String currDir = JTerm.currentDirectory; + final File f; + + // "cd .." + if ("..".equals(dirChange) && !"/".equals(JTerm.currentDirectory)) { + final String[] dirSplit = JTerm.currentDirectory.split("/"); + final StringBuilder newPath = new StringBuilder(); + + for (int i = 0; i < dirSplit.length - 1; i++) + newPath.append(dirSplit[i]).append("/"); + + System.setProperty("user.dir", newPath.toString()); + JTerm.currentDirectory = newPath.toString(); + JTerm.PROMPT = JTerm.currentDirectory + " >> "; + return; + } + + // "cd /home/username/example/" + if (dirChange.startsWith("/")) + f = Paths.get(dirChange).toFile(); + + // "cd ~/example/" + else if (dirChange.startsWith("~") && dirChange.length() > 1) + f = Paths.get(JTerm.USER_HOME_DIR + dirChange.substring(1)).toFile(); + + // "cd ~" + else if ("~".equals(dirChange)) + f = Paths.get(JTerm.USER_HOME_DIR).toFile(); + + // "cd example/src/morexamples/" + else + f = Paths.get(currDir + dirChange).toFile(); + + if (f.exists() && f.isDirectory()) { + System.setProperty("user.dir", f.getAbsolutePath()); + JTerm.currentDirectory = f.getAbsolutePath() + "/"; + JTerm.PROMPT = JTerm.currentDirectory + " >> "; + } else { + JTerm.out.println(TextColor.INFO, "Please enter a valid directory to change to"); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java b/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java new file mode 100644 index 0000000..91352b1 --- /dev/null +++ b/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java @@ -0,0 +1,126 @@ +package jterm.io.terminal; + +import jterm.JTerm; +import jterm.io.handlers.ArrowKeyHandler; +import jterm.io.handlers.InputHandler; +import jterm.io.input.Keys; +import jterm.io.output.TextColor; + +/** + * Processes arrow keys for Terminal module. + * + * @see HeadlessTerminal + * @see TermInputProcessor + */ +public class TermArrowKeyProcessor extends ArrowKeyHandler { + + // Input processor owning this class + private TermInputProcessor inputProcessor; + + // Position on prevCommands list (used to iterate through it) + private int commandListPosition = 0; + + // Stores current TermInputProcessor.command when iterating through prevCommands + private String currCommand = ""; + + TermArrowKeyProcessor(TermInputProcessor inputProcessor) { + this.inputProcessor = inputProcessor; + setLArrowBehaviour(); + setRArrowBehaviour(); + setUArrowBehaviour(); + setDArrowBehaviour(); + } + + protected void setCurrCommand(String currCommand) { + this.currCommand = currCommand; + } + + protected void setCommandListPosition(int commandListPosition) { + this.commandListPosition = commandListPosition; + } + + private void setLArrowBehaviour() { + lArrEvent = () -> { + if (inputProcessor.getCursorPos() > 0) { + JTerm.out.print(TextColor.INPUT, "\b"); + inputProcessor.decreaseCursorPos(); + } + }; + } + + private void setRArrowBehaviour() { + rArrEvent = () -> { + if (inputProcessor.getCursorPos() < inputProcessor.getCommand().length()) { + InputHandler.clearLine(inputProcessor.getCommand(), true); + JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); + JTerm.out.print(TextColor.INPUT, inputProcessor.getCommand()); + inputProcessor.increaseCursorPos(); + inputProcessor.moveToCursorPos(); + } + }; + } + + private void setUArrowBehaviour() { + uArrEvent = () -> { + prevCommandIterator(Keys.UP); + inputProcessor.setCursorPos(inputProcessor.getCommand().length()); + }; + } + + private void setDArrowBehaviour() { + dArrEvent = () -> { + prevCommandIterator(Keys.DOWN); + inputProcessor.setCursorPos(inputProcessor.getCommand().length()); + }; + } + + /** + * Iterates through the prevCommands list. Emulates Unix terminal behaviour when using + * vertical arrow keys in the terminal. + * + * @param ak Arrow key to process + */ + private void prevCommandIterator(Keys ak) { + if (inputProcessor.commandHistory.size() == 0) + return; + + int cmdHistorySize = inputProcessor.commandHistory.size() - 1; + + if (commandListPosition == inputProcessor.commandHistory.size() && lastArrowPress == Keys.NONE) + currCommand = inputProcessor.getCommand(); + + if (ak == Keys.UP && commandListPosition > 0) { + // Move through the list towards first typed command + + lastArrowPress = ak; + InputHandler.clearLine(inputProcessor.getCommand(), true); + + if (commandListPosition > inputProcessor.commandHistory.size()) + commandListPosition = inputProcessor.commandHistory.size(); + + JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); + JTerm.out.print(TextColor.INPUT, inputProcessor.commandHistory.get(--commandListPosition)); + inputProcessor.setCommand(inputProcessor.commandHistory.get(commandListPosition)); + + } else if (ak == Keys.DOWN) { + lastArrowPress = ak; + + if (commandListPosition < cmdHistorySize) { + // Move through list towards last typed element + InputHandler.clearLine(inputProcessor.getCommand(), true); + + JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); + JTerm.out.print(TextColor.INPUT, inputProcessor.commandHistory.get(++commandListPosition)); + inputProcessor.setCommand(inputProcessor.commandHistory.get(commandListPosition)); + } else if (!inputProcessor.getCommand().equals(currCommand)) { + // Print command that was stored before iteration through list began + InputHandler.clearLine(inputProcessor.getCommand(), true); + commandListPosition++; + + JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); + JTerm.out.print(TextColor.INPUT, currCommand); + inputProcessor.setCommand(currCommand); + } + } + } +} diff --git a/src/main/java/jterm/io/terminal/TermInputProcessor.java b/src/main/java/jterm/io/terminal/TermInputProcessor.java new file mode 100644 index 0000000..85acd23 --- /dev/null +++ b/src/main/java/jterm/io/terminal/TermInputProcessor.java @@ -0,0 +1,180 @@ +package jterm.io.terminal; + +import jterm.JTerm; +import jterm.io.handlers.ArrowKeyHandler; +import jterm.io.handlers.InputHandler; +import jterm.io.handlers.KeyHandler; +import jterm.io.input.Input; +import jterm.io.output.TextColor; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedList; + +/** + * Input processor for terminal module. + * + * @see HeadlessTerminal + * @see TermKeyProcessor + * @see TermArrowKeyProcessor + */ +public class TermInputProcessor extends InputHandler { + + private HeadlessTerminal headlessTerminal; + + // Stores all entered commands + public ArrayList commandHistory = new ArrayList<>(); + + private String command = ""; + + private int cursorPos = 0; + + TermInputProcessor(HeadlessTerminal headlessTerminal) { + super(); + this.headlessTerminal = headlessTerminal; + + keyHandler = new TermKeyProcessor(this); + arrowKeyHandler = new TermArrowKeyProcessor(this); + + KeyHandler.initKeysMap(); + } + + public TermKeyProcessor getKeyProcessor() { + return (TermKeyProcessor) keyHandler; + } + + public TermArrowKeyProcessor getArrowKeyProcessor() { + return (TermArrowKeyProcessor) arrowKeyHandler; + } + + public String getCommand() { + return command; + } + + public void setCommand(String command) { + this.command = command; + } + + public void increaseCursorPos() { + cursorPos++; + } + + public void decreaseCursorPos() { + cursorPos--; + } + + public int getCursorPos() { + return cursorPos; + } + + protected void setCursorPos(int cursorPos) { + this.cursorPos = cursorPos; + } + + /** + * Calls appropriate method for handling input read from the input class. + */ + @Override + public void process(int input) { + if (JTerm.IS_WIN) { + arrowKeyHandler.process(ArrowKeyHandler.arrowKeyCheckWindows(input)); + keyHandler.process(input); + } else if (JTerm.IS_UNIX) { + int c1, c2; + try { + c1 = Input.read(false); + c2 = Input.read(false); + + if (c1 == -2 && c2 == -2) + keyHandler.process(input); + else + arrowKeyHandler.process(ArrowKeyHandler.arrowKeyCheckUnix(input, c1, c2)); + } catch (IOException e) { + System.err.println("Error reading arrow key press"); + } + } + } + + /** + * Sends command to terminal class for parsing, source is the newlineEvent in the key processor + */ + protected void parse() { + headlessTerminal.parse(command); + } + + /** + * Moves the cursor from the end of the command to where it should be (if the user is using arrow keys) + * Usually only used after modifying 'command' + */ + public void moveToCursorPos() { + for (int i = command.length(); i > cursorPos; i--) + JTerm.out.print(TextColor.INPUT, "\b"); + } + + /** + * Splits a command into 3 parts for the autocomplete function to operate properly. + *

+ * Elements 0 and 2 are the non-relevant part of the command to the autocomplete function + * and are used when stitching the autocompleted command back together. + *

+ * Element 1 is the portion of the command that needs completing, and the one on which + * the autocomplete class will operate on. + * + * @param command Command to split + * @return Returns disassembled string, with non relevant info in elements 0 and 2, and the string to autocomplete + * in element 1 + */ + protected static String[] disassembleCommand(String command, int cursorPos) { + + if (!command.contains("&&")) + return new String[]{"", command, ""}; + + LinkedList ampPos = new LinkedList<>(); + for (int i = 0; i < command.length() - 1; i++) { + if (command.substring(i, i + 2).equals("&&")) { + ampPos.add(i); + if (cursorPos - i < 2 && cursorPos - i > 0) + return new String[]{"", command, ""}; + } + } + + String[] splitCommand = new String[3]; + + if (ampPos.size() > 1) { + // Deals with commands that have more than one && + for (int i = 0; i < ampPos.size(); i++) { + if (ampPos.get(i) > cursorPos) { + splitCommand[0] = command.substring(0, ampPos.get(i - 1) + 2) + " "; + splitCommand[1] = command.substring(ampPos.get(i - 1) + 2, cursorPos); + splitCommand[2] = " " + command.substring(cursorPos, command.length()); + } else if (i + 1 == ampPos.size()) { + splitCommand[0] = command.substring(0, ampPos.get(i) + 2) + " "; + splitCommand[1] = command.substring(ampPos.get(i) + 2, cursorPos); + splitCommand[2] = " " + command.substring(cursorPos, command.length()); + } + } + } else { + // Deals with commands that have exactly one && + if (cursorPos > ampPos.get(0)) { + splitCommand[0] = command.substring(0, ampPos.get(0) + 2) + " "; + splitCommand[1] = command.substring(ampPos.get(0) + 2, cursorPos); + splitCommand[2] = command.substring(cursorPos, command.length()); + } else if (cursorPos < ampPos.get(0)) { + splitCommand[0] = ""; + splitCommand[1] = command.substring(0, cursorPos); + splitCommand[2] = command.substring(cursorPos, command.length()); + } else { + String[] split = command.split("&&"); + splitCommand[0] = split[0]; + splitCommand[1] = ""; + splitCommand[2] = "&&" + split[1]; + } + } + + // Remove spaces so that autocomplete can work properly + splitCommand[1] = splitCommand[1].trim(); + + return splitCommand; + } +} + diff --git a/src/main/java/jterm/io/terminal/TermKeyProcessor.java b/src/main/java/jterm/io/terminal/TermKeyProcessor.java new file mode 100644 index 0000000..99969cd --- /dev/null +++ b/src/main/java/jterm/io/terminal/TermKeyProcessor.java @@ -0,0 +1,87 @@ +package jterm.io.terminal; + +import jterm.JTerm; +import jterm.io.handlers.InputHandler; +import jterm.io.handlers.KeyHandler; +import jterm.io.output.TextColor; + +/** + * Processes key presses (except arrow keys) for Terminal module. + * + * @see HeadlessTerminal + */ +public class TermKeyProcessor extends KeyHandler { + + private TermInputProcessor inputProcessor; + + TermKeyProcessor(TermInputProcessor inputProcessor) { + this.inputProcessor = inputProcessor; + setUpTabEvents(); + setUpNWLNEvent(); + setUpCharEvents(); + setUpBackspaceEvent(); + } + + private void setUpTabEvents() { + tabEvent = () -> {}; + } + + private void setUpNWLNEvent() { + newLineEvent = () -> { + + String command = inputProcessor.getCommand(); + + boolean empty = "".equals(command.trim()); + + if (!empty) + inputProcessor.commandHistory.add(command); + + inputProcessor.getArrowKeyProcessor().setCommandListPosition(inputProcessor.commandHistory.size()); + inputProcessor.getArrowKeyProcessor().setCurrCommand(""); + inputProcessor.setCursorPos(0); + inputProcessor.parse(); + + inputProcessor.setCommand(""); + }; + } + + private void setUpCharEvents() { + charEvent = (char input) -> { + String command = inputProcessor.getCommand(); + int cursorPos = inputProcessor.getCursorPos(); + + InputHandler.clearLine(command, true); + + if (inputProcessor.getCursorPos() == command.length()) { + JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); + JTerm.out.print(TextColor.INPUT, command + input); + inputProcessor.setCommand(command + input); + } else { + inputProcessor.setCommand(new StringBuilder(command).insert(cursorPos, input).toString()); + JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); + JTerm.out.print(TextColor.INPUT, inputProcessor.getCommand()); + } + + inputProcessor.increaseCursorPos(); + inputProcessor.moveToCursorPos(); + }; + } + + private void setUpBackspaceEvent() { + backspaceEvent = () -> { + if (inputProcessor.getCommand().length() > 0 && inputProcessor.getCursorPos() > 0) { + int charToDelete = inputProcessor.getCursorPos() - 1; + String command = inputProcessor.getCommand(); + + InputHandler.clearLine(command, true); + + inputProcessor.setCommand(new StringBuilder(command).deleteCharAt(charToDelete).toString()); + JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); + JTerm.out.print(TextColor.INPUT, inputProcessor.getCommand()); + + inputProcessor.decreaseCursorPos(); + inputProcessor.moveToCursorPos(); + } + }; + } +} From a6e63329bff33416f571178efd96f91cdfabee66 Mon Sep 17 00:00:00 2001 From: nanoandrew4 Date: Sun, 4 Aug 2019 23:41:39 +0200 Subject: [PATCH 07/13] Fixed cd command and java docs referencing ported code, and implemented custom command running to enable custom commands previously implemented by JTerm --- src/main/java/jterm/JTerm.java | 18 +++---- .../jterm/io/handlers/ArrowKeyHandler.java | 4 -- .../java/jterm/io/handlers/InputHandler.java | 10 ++-- .../java/jterm/io/handlers/KeyHandler.java | 1 - .../java/jterm/io/input/InputHandler.java | 1 - .../jterm/io/terminal/HeadlessTerminal.java | 50 ++++++++++++------- .../io/terminal/TermArrowKeyProcessor.java | 2 +- .../jterm/io/terminal/TermInputProcessor.java | 2 +- .../jterm/io/terminal/TermKeyProcessor.java | 2 +- 9 files changed, 49 insertions(+), 41 deletions(-) diff --git a/src/main/java/jterm/JTerm.java b/src/main/java/jterm/JTerm.java index 6c7cfbc..ea626ee 100644 --- a/src/main/java/jterm/JTerm.java +++ b/src/main/java/jterm/JTerm.java @@ -51,7 +51,7 @@ public class JTerm { // Default value of getProperty("user.dir") is equal to the default directory set when the program starts // Global directory variable (use "cd" command to change) - public static String currentDirectory = System.getProperty("user.dir"); + public static String currentDirectory = System.getProperty("user.dir") + "/"; public static final String USER_HOME_DIR = System.getProperty("user.home"); public static boolean IS_WIN; @@ -82,24 +82,24 @@ public static void main(String[] args) { new HeadlessTerminal().run(); } - public static void executeCommand(String options) { - List optionsArray = Util.getAsArray(options); + public static boolean executeCommand(final String options) { + final List optionsArray = Util.getAsArray(options); if (optionsArray.size() == 0) { - return; + return false; } - String command = optionsArray.remove(0); - if (!COMMANDS.containsKey(command)) { - out.printf(TextColor.ERROR,"Command \"%s\" is not available%n", command); - return; - } + final String command = optionsArray.remove(0); + if (!COMMANDS.containsKey(command)) + return false; try { if (JTerm.isHeadless()) out.println(TextColor.INFO); COMMANDS.get(command).execute(optionsArray); + return true; } catch (CommandException e) { System.err.println(e.getMessage()); + return false; } } diff --git a/src/main/java/jterm/io/handlers/ArrowKeyHandler.java b/src/main/java/jterm/io/handlers/ArrowKeyHandler.java index c4a93cc..345c39a 100644 --- a/src/main/java/jterm/io/handlers/ArrowKeyHandler.java +++ b/src/main/java/jterm/io/handlers/ArrowKeyHandler.java @@ -5,7 +5,6 @@ /** * Abstract class specifying how arrow keys should be handled. - * Each module must implement its own set of Events. * * @see Event */ @@ -77,9 +76,6 @@ public static Keys arrowKeyCheckUnix(final int... i) { /** * Process an arrow key press. - *

- * Handles sending event status to all Modules, so they can react appropriately, as well - * relegating to the appropriate lambda expression. * * @param ak Arrow key to process */ diff --git a/src/main/java/jterm/io/handlers/InputHandler.java b/src/main/java/jterm/io/handlers/InputHandler.java index dde3b31..4a62651 100644 --- a/src/main/java/jterm/io/handlers/InputHandler.java +++ b/src/main/java/jterm/io/handlers/InputHandler.java @@ -8,11 +8,9 @@ /** * Abstract class specifying how input should be handled. - * Each module must run its own implementation of this class. */ public abstract class InputHandler { - // Key handlers for module protected ArrowKeyHandler arrowKeyHandler; protected KeyHandler keyHandler; @@ -20,7 +18,7 @@ public abstract class InputHandler { * Determines how long the program will ignore user input (in ms). * Prevents program from going visually insane, by not overloading the system with too much output. */ - public static int minWaitTime = 30; + public static int minWaitTime = 10; /** * Create key handler object with null arrow key handler and key handler. @@ -33,10 +31,10 @@ public InputHandler() { } /** - * Set KeyHandler and ArrowKeyHandler for module. + * Set KeyHandler and ArrowKeyHandler for JTerm. *

* Classes extending KeyHandler and ArrowKeyHandler should passed as parameters, - * so they can be later used to process input for this module. + * so they can be later used to process input. * * @param kh Key handler to use * @param akh Arrow key handler to use @@ -47,7 +45,7 @@ public InputHandler(KeyHandler kh, ArrowKeyHandler akh) { } /** - * Code to run when processing input for module. + * Code to run when processing input for JTerm headless mode. * Can (and should) make use of keyHandler and/or arrowKeyHandler for input processing. */ public abstract void process(int input); diff --git a/src/main/java/jterm/io/handlers/KeyHandler.java b/src/main/java/jterm/io/handlers/KeyHandler.java index b76a34b..7b8d595 100644 --- a/src/main/java/jterm/io/handlers/KeyHandler.java +++ b/src/main/java/jterm/io/handlers/KeyHandler.java @@ -9,7 +9,6 @@ /** * Abstract class specifying methods needed to process key events properly. - * Each module must run its own implementation of this class. */ public abstract class KeyHandler { diff --git a/src/main/java/jterm/io/input/InputHandler.java b/src/main/java/jterm/io/input/InputHandler.java index dff9cf7..e78f280 100644 --- a/src/main/java/jterm/io/input/InputHandler.java +++ b/src/main/java/jterm/io/input/InputHandler.java @@ -32,7 +32,6 @@ private static void setCommandListPosition(int commandListPos) { commandListPosition = commandListPos; } - public static void read() throws IOException { int c1 = Input.read(true); int c2 = Input.read(false); diff --git a/src/main/java/jterm/io/terminal/HeadlessTerminal.java b/src/main/java/jterm/io/terminal/HeadlessTerminal.java index 0c67fe6..ec8a31d 100644 --- a/src/main/java/jterm/io/terminal/HeadlessTerminal.java +++ b/src/main/java/jterm/io/terminal/HeadlessTerminal.java @@ -4,16 +4,13 @@ import jterm.io.handlers.InputHandler; import jterm.io.output.TextColor; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; +import java.io.*; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; /** - * Terminal module. Interfaces directly with bash, so it is as if you were running a normal terminal session. + * Headless terminal module. Interfaces directly with bash, so it is as if you were running a normal terminal session. */ public class HeadlessTerminal { @@ -33,15 +30,10 @@ public HeadlessTerminal() { public void run() { exit = false; - JTerm.PROMPT = JTerm.currentDirectory + " >> "; - - JTerm.out.println(TextColor.INFO, "Entered terminal mode"); - JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); while (!exit) inputProcessor.process(InputHandler.getKey()); writeCommandsToFile(); - JTerm.PROMPT = ">> "; } public void parse(String rawCommand) { @@ -51,6 +43,9 @@ public void parse(String rawCommand) { for (String command : split) { command = command.trim(); + if (JTerm.executeCommand(command)) + continue; + /* * cd command has to be interpreted separately, since once the JVM runs any cd commands do not take effect * inside the program, nor once it exists. @@ -71,19 +66,42 @@ public void parse(String rawCommand) { final Process p; try { // Assumes unix, Windows would require a separate implementation... pb = new ProcessBuilder("/bin/bash", "-c", command); - pb.inheritIO(); // Make program and process share IO to allow user to interact with program +// pb.inheritIO(); // Make program and process share IO to allow user to interact with program + pb.redirectInput(ProcessBuilder.Redirect.PIPE); + pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); + pb.redirectError(ProcessBuilder.Redirect.PIPE); pb.directory(new File(JTerm.currentDirectory)); // Set working directory for command p = pb.start(); p.waitFor(); + + if (p.exitValue() == 0) + JTerm.out.println(TextColor.INFO, readInputStream(p.getInputStream())); + else + JTerm.out.println(TextColor.ERROR, readInputStream(p.getErrorStream())); p.destroy(); } catch (IOException | IllegalArgumentException | InterruptedException e) { - System.err.println("Parsing command \"" + command + "\" failed, enter \"t-help\" for help using module."); + System.err.println("Parsing command \"" + command + "\" failed, enter \"help\" for help using JTerm."); } - - JTerm.currentDirectory = System.getProperty("user.dir") + "/"; } - JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); + JTerm.out.printPrompt(); + } + + /** + * Reads an {@link InputStream} and returns its contents as a {@link String}. + * + * @param inputStream Input stream to read from + * @return Contents of the input stream, as a String + * @throws IOException If the stream is null + */ + private String readInputStream(final InputStream inputStream) throws IOException { + final BufferedReader in = new BufferedReader(new InputStreamReader(inputStream)); + final StringBuilder responseBuffer = new StringBuilder(); + + String line; + while ((line = in.readLine()) != null) + responseBuffer.append(line); + return responseBuffer.toString(); } /** @@ -194,7 +212,6 @@ public void changeDir(String command) { System.setProperty("user.dir", newPath.toString()); JTerm.currentDirectory = newPath.toString(); - JTerm.PROMPT = JTerm.currentDirectory + " >> "; return; } @@ -217,7 +234,6 @@ else if ("~".equals(dirChange)) if (f.exists() && f.isDirectory()) { System.setProperty("user.dir", f.getAbsolutePath()); JTerm.currentDirectory = f.getAbsolutePath() + "/"; - JTerm.PROMPT = JTerm.currentDirectory + " >> "; } else { JTerm.out.println(TextColor.INFO, "Please enter a valid directory to change to"); } diff --git a/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java b/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java index 91352b1..c577881 100644 --- a/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java +++ b/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java @@ -7,7 +7,7 @@ import jterm.io.output.TextColor; /** - * Processes arrow keys for Terminal module. + * Processes arrow keys for JTerm headless terminal. * * @see HeadlessTerminal * @see TermInputProcessor diff --git a/src/main/java/jterm/io/terminal/TermInputProcessor.java b/src/main/java/jterm/io/terminal/TermInputProcessor.java index 85acd23..b084c01 100644 --- a/src/main/java/jterm/io/terminal/TermInputProcessor.java +++ b/src/main/java/jterm/io/terminal/TermInputProcessor.java @@ -12,7 +12,7 @@ import java.util.LinkedList; /** - * Input processor for terminal module. + * Input processor for JTerm headless terminal. * * @see HeadlessTerminal * @see TermKeyProcessor diff --git a/src/main/java/jterm/io/terminal/TermKeyProcessor.java b/src/main/java/jterm/io/terminal/TermKeyProcessor.java index 99969cd..bc11667 100644 --- a/src/main/java/jterm/io/terminal/TermKeyProcessor.java +++ b/src/main/java/jterm/io/terminal/TermKeyProcessor.java @@ -6,7 +6,7 @@ import jterm.io.output.TextColor; /** - * Processes key presses (except arrow keys) for Terminal module. + * Processes key presses (except arrow keys) for JTerm headless terminal. * * @see HeadlessTerminal */ From d2a8ff825124c310bfcd6975478abfa2705f694c Mon Sep 17 00:00:00 2001 From: nanoandrew4 Date: Mon, 5 Aug 2019 22:48:52 +0200 Subject: [PATCH 08/13] Finished backporting code, headless and GUI mode are operating as they did previously, though more in depth tests are pending --- src/main/java/jterm/gui/Terminal.java | 12 +- .../jterm/io/handlers/ArrowKeyHandler.java | 16 +- .../java/jterm/io/handlers/InputHandler.java | 9 +- .../java/jterm/io/handlers/KeyHandler.java | 26 +- .../java/jterm/io/input/InputHandler.java | 309 ------------------ src/main/java/jterm/io/input/Keys.java | 69 ++-- src/main/java/jterm/io/output/GuiPrinter.java | 50 +-- .../jterm/io/terminal/HeadlessTerminal.java | 1 - .../io/terminal/TermArrowKeyProcessor.java | 16 +- .../jterm/io/terminal/TermInputProcessor.java | 13 +- .../jterm/io/terminal/TermKeyProcessor.java | 56 +++- src/test/java/jterm/io/InputHandlerTest.java | 11 +- 12 files changed, 165 insertions(+), 423 deletions(-) delete mode 100644 src/main/java/jterm/io/input/InputHandler.java diff --git a/src/main/java/jterm/gui/Terminal.java b/src/main/java/jterm/gui/Terminal.java index 3a7d181..9821cfe 100644 --- a/src/main/java/jterm/gui/Terminal.java +++ b/src/main/java/jterm/gui/Terminal.java @@ -1,9 +1,11 @@ package jterm.gui; import jterm.JTerm; -import jterm.io.input.InputHandler; +import jterm.io.handlers.InputHandler; import jterm.io.input.Keys; import jterm.io.output.TextColor; +import jterm.io.terminal.HeadlessTerminal; +import jterm.io.terminal.TermInputProcessor; import javax.swing.*; import java.awt.*; @@ -16,6 +18,8 @@ public class Terminal extends JFrame implements KeyListener { private JPanel contentPane; private JTextPane textPane; + private HeadlessTerminal headlessTerminal = new HeadlessTerminal(); + private InputHandler inputHandler = new TermInputProcessor(headlessTerminal); public Terminal() { TextColor.initGui(); @@ -58,10 +62,10 @@ public void keyPressed(KeyEvent e) { return; } if ((int) e.getKeyChar() == 65535) { - //An arrow key was pressed. Switch the key code into the negatives so it wont interfere with any real chars - new Thread(() -> InputHandler.process(Keys.getKeyByValue(e.getKeyCode() * -1), e.getKeyChar())).start(); +// An arrow key was pressed. Switch the key code into the negatives so it wont interfere with any real chars + new Thread(() -> inputHandler.process(Keys.getKeyByValue(e.getKeyCode() * -1))).start(); } else - new Thread(() -> InputHandler.process(Keys.getKeyByValue((int) e.getKeyChar()), e.getKeyChar())).start(); + new Thread(() -> inputHandler.process(Keys.getKeyByValue(e.getKeyChar()))).start(); } private void onCancel() { diff --git a/src/main/java/jterm/io/handlers/ArrowKeyHandler.java b/src/main/java/jterm/io/handlers/ArrowKeyHandler.java index 345c39a..4ee406e 100644 --- a/src/main/java/jterm/io/handlers/ArrowKeyHandler.java +++ b/src/main/java/jterm/io/handlers/ArrowKeyHandler.java @@ -16,10 +16,10 @@ public abstract class ArrowKeyHandler { private long lastPress = System.currentTimeMillis(); // Events to be implemented by any class that inherits ArrowKeyHandler - public Event lArrEvent; - public Event rArrEvent; - public Event uArrEvent; - public Event dArrEvent; + public Event leftArrEvent; + public Event rightArrEvent; + public Event upArrEvent; + public Event downArrEvent; /** * Checks if last input was arrow key (only on Windows). @@ -84,16 +84,16 @@ public void process(final Keys ak) { lastPress = System.currentTimeMillis(); switch (ak) { case UP: - uArrEvent.process(); + upArrEvent.process(); break; case DOWN: - dArrEvent.process(); + downArrEvent.process(); break; case LEFT: - lArrEvent.process(); + leftArrEvent.process(); break; case RIGHT: - rArrEvent.process(); + rightArrEvent.process(); break; } } diff --git a/src/main/java/jterm/io/handlers/InputHandler.java b/src/main/java/jterm/io/handlers/InputHandler.java index 4a62651..a4bb28a 100644 --- a/src/main/java/jterm/io/handlers/InputHandler.java +++ b/src/main/java/jterm/io/handlers/InputHandler.java @@ -3,6 +3,7 @@ import jterm.JTerm; import jterm.io.input.Input; +import jterm.io.input.Keys; import java.io.IOException; @@ -48,21 +49,21 @@ public InputHandler(KeyHandler kh, ArrowKeyHandler akh) { * Code to run when processing input for JTerm headless mode. * Can (and should) make use of keyHandler and/or arrowKeyHandler for input processing. */ - public abstract void process(int input); + public abstract void process(final Keys key); /** * Returns key char value of last key pressed. * * @return Char value of key pressed */ - public static char getKey() { + public static Keys getKey() { try { - return (char) Input.read(true); + return Keys.getKeyByValue(Input.read(true)); } catch (IOException e) { e.printStackTrace(); } - return 0; + return Keys.NONE; } /** diff --git a/src/main/java/jterm/io/handlers/KeyHandler.java b/src/main/java/jterm/io/handlers/KeyHandler.java index 7b8d595..549c523 100644 --- a/src/main/java/jterm/io/handlers/KeyHandler.java +++ b/src/main/java/jterm/io/handlers/KeyHandler.java @@ -22,6 +22,8 @@ public abstract class KeyHandler { public Event newLineEvent; public CharEvent charEvent; public Event backspaceEvent; + public Event ctrlCEvent; + public Event ctrlZEvent; // Returns a map pairing key values stored as ints to values in Keys enum public static HashMap getKeymap() { @@ -32,11 +34,9 @@ public static HashMap getKeymap() { * Processes all input by relegating it to the appropriate lambda expression * and triggering events so that other Modules can react appropriately. * - * @param input ASCII key code representing key pressed + * @param key Key pressed */ - public void process(int input) { - Keys key = getKey(input); - + public void process(final Keys key) { if (System.currentTimeMillis() - lastPress < InputHandler.minWaitTime) return; lastPress = System.currentTimeMillis(); @@ -47,10 +47,10 @@ else if (key == Keys.TAB) tabEvent.process(); else if (key == Keys.NWLN) newLineEvent.process(); - else if (input > 31 && input < 127) - charEvent.process((char) input); + else if (key == Keys.CHAR) + charEvent.process((char) key.getValue()); - signalCatch(input); + signalCatch(key); } /** @@ -58,16 +58,12 @@ else if (input > 31 && input < 127) * Useful for running while waiting for some process to finish, so user can cancel if they wish. * For use in above use case, loop while process is not done and pass input.read(false) to this method. * - * @param input ASCII key code value of key pressed + * @param key Key pressed * @return true if process should be cancelled, false if no signals were caught */ - public static Keys signalCatch(int input) { - if (input == 3) // Ctrl+C - return Keys.CTRL_C; - if (input == 26) { // Ctrl+Z -> force quit program - System.out.println(); - System.exit(122); - } + public static Keys signalCatch(final Keys key) { + if (key == Keys.CTRL_C || key == Keys.CTRL_Z) + key.executeAction(); return Keys.NONE; } diff --git a/src/main/java/jterm/io/input/InputHandler.java b/src/main/java/jterm/io/input/InputHandler.java deleted file mode 100644 index e78f280..0000000 --- a/src/main/java/jterm/io/input/InputHandler.java +++ /dev/null @@ -1,309 +0,0 @@ -package jterm.io.input; - -import jterm.JTerm; -import jterm.io.output.TextColor; -import jterm.util.FileAutocomplete; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.LinkedList; - -public class InputHandler { - - // Position on prevCommands list (used to iterate through it) - private static int commandListPosition = 0; - - // Stores current TermInputProcessor.command when iterating through prevCommands - private static String currCommand = ""; - - // Stores all entered commands - private static final ArrayList prevCommands = new ArrayList<>(); - private static String command = ""; - - // For resetting all variables in FileAutocomplete once a key press other than a tab is registered - private static boolean resetVars = false; - private static int cursorPos = 0; - private static char lastChar; - - // Last arrow key that was pressed (if any other key is pressed sets to Keys.NONE) - private static Keys lastArrowPress = Keys.NONE; - - private static void setCommandListPosition(int commandListPos) { - commandListPosition = commandListPos; - } - - public static void read() throws IOException { - int c1 = Input.read(true); - int c2 = Input.read(false); - int c3 = Input.read(false); - Keys keyType; - if (!(c2 == -2 && c3 == -2)) c1 = (c1 + c2 + c3) * -1; - keyType = Keys.getKeyByValue(c1); - process(keyType, (char) c1); - } - - public static void process(Keys key, char c) { - lastChar = c; - if (key != Keys.TAB) - resetVars = true; - key.executeAction(); - } - - static void ctrlCEvent() { - System.exit(0); - } - - static void ctrlZEvent() { - System.exit(0); - } - - static void processUp() { - prevCommandIterator(Keys.UP); - setCursorPos(command.length()); - } - - static void processDown() { - prevCommandIterator(Keys.DOWN); - setCursorPos(command.length()); - } - - static void processLeft() { - if (getCursorPos() > 0) { - if (JTerm.isHeadless()) JTerm.out.print(TextColor.INPUT, "\b"); - decreaseCursorPos(); - } - } - - static void processRight() { - if (getCursorPos() < command.length()) { - if (JTerm.isHeadless()) { - JTerm.out.clearLine(command, cursorPos, false); - JTerm.out.print(TextColor.INPUT, command); - } - increaseCursorPos(); - moveToCursorPos(); - } - } - - /** - * Iterates through the prevCommands list. Emulates Unix terminal behaviour when using - * vertical arrow keys. - * - * @param ak Arrow key to process - */ - private static void prevCommandIterator(Keys ak) { - // Saves currently typed command before moving through the list of previously typed commands - if (lastArrowPress == Keys.NONE) - currCommand = command; - lastArrowPress = ak; - JTerm.out.clearLine(command, cursorPos, false); - commandListPosition += ak == Keys.UP ? -1 : 1; - commandListPosition = Math.max(commandListPosition, 0); - if (commandListPosition >= getPrevCommands().size()) { - commandListPosition = Math.min(commandListPosition, getPrevCommands().size()); - JTerm.out.print(TextColor.INPUT, currCommand); - command = currCommand; - } else { - JTerm.out.print(TextColor.INPUT, getPrevCommands().get(commandListPosition)); - command = getPrevCommands().get(commandListPosition); - } - } - - static void tabEvent() { - lastArrowPress = Keys.NONE; - fileAutocomplete(); - setResetVars(false); - } - - static void newLineEvent() { - lastArrowPress = Keys.NONE; - boolean empty = command.trim().isEmpty(); - - ArrayList prevCommands = getPrevCommands(); - - if (!empty) - prevCommands.add(command); - - setCommandListPosition(prevCommands.size()); - currCommand = ""; - setCursorPos(0); - setResetVars(true); - System.out.println(); - parse(); - command = ""; - JTerm.out.printPrompt(); - } - - static void charEvent() { - lastArrowPress = Keys.NONE; - int cursorPos = getCursorPos(); - - if (getCursorPos() == command.length()) { - if (JTerm.isHeadless()) JTerm.out.print(TextColor.INPUT, lastChar); - command += lastChar; - } else { - JTerm.out.clearLine(command, cursorPos, false); - command = new StringBuilder(command).insert(cursorPos, lastChar).toString(); - JTerm.out.print(TextColor.INPUT, command); - } - - increaseCursorPos(); - moveToCursorPos(); - setResetVars(true); - } - - static void backspaceEvent() { - lastArrowPress = Keys.NONE; - if (command.length() > 0 && getCursorPos() > 0) { - - int charToDelete = getCursorPos() - 1; - if (JTerm.isHeadless()) - JTerm.out.clearLine(command, cursorPos, false); - - command = new StringBuilder(command).deleteCharAt(charToDelete).toString(); - - if (JTerm.isHeadless()) - JTerm.out.print(TextColor.INPUT, command); - - decreaseCursorPos(); - moveToCursorPos(); - setResetVars(true); - } - } - - /** - * Sends command to terminal class for parsing, source is the newlineEvent in the key processor - */ - private static void parse() { - String[] commands = command.split("&&"); - for (String command : commands) { - command = command.trim(); - JTerm.executeCommand(command); - } - } - - /** - * Moves the cursor from the end of the command to where it should be (if the user is using arrow keys) - * Usually only used after modifying 'command' - */ - private static void moveToCursorPos() { - if (JTerm.isHeadless()) { - for (int i = command.length(); i > cursorPos; i--) - JTerm.out.print(TextColor.INPUT, "\b"); - } - } - - /** - * Autocompletes desired file name similar to how terminals do it. - */ - private static void fileAutocomplete() { - - if (resetVars) - FileAutocomplete.resetVars(); - - if (FileAutocomplete.getFiles() == null) { - FileAutocomplete.init(disassembleCommand(command), false, false); - resetVars = false; - } else - FileAutocomplete.fileAutocomplete(); - - command = FileAutocomplete.getCommand(); - - if (FileAutocomplete.isResetVars()) - FileAutocomplete.resetVars(); - - // Get variables and set cursor position - setCursorPos(FileAutocomplete.getCursorPos()); - moveToCursorPos(); - } - - /** - * Splits a command into 3 parts for the autocomplete function to operate properly. - *

- * Elements 0 and 2 are the non-relevant part of the command to the autocomplete function - * and are used when stitching the autocompleted command back together. - *

- * Element 1 is the portion of the command that needs completing, and the one on which - * the autocomplete class will operate on. - * - * @param command Command to split - * @return Returns disassembled string, with non relevant info in elements 0 and 2, and the string to autocomplete - * in element 1 - */ - private static String[] disassembleCommand(String command) { - - if (!command.contains("&&")) - return new String[]{"", command, ""}; - - LinkedList ampPos = new LinkedList<>(); - for (int i = 0; i < command.length() - 1; i++) { - if (command.substring(i, i + 2).equals("&&")) { - ampPos.add(i); - if (cursorPos - i < 2 && cursorPos - i > 0) - return new String[]{"", command, ""}; - } - } - - String[] splitCommand = new String[3]; - - if (ampPos.size() > 1) { - // Deals with commands that have more than one && - for (int i = 0; i < ampPos.size(); i++) { - if (ampPos.get(i) > cursorPos) { - splitCommand[0] = command.substring(0, ampPos.get(i - 1) + 2) + " "; - splitCommand[1] = command.substring(ampPos.get(i - 1) + 2, cursorPos); - splitCommand[2] = " " + command.substring(cursorPos, command.length()); - } else if (i + 1 == ampPos.size()) { - splitCommand[0] = command.substring(0, ampPos.get(i) + 2) + " "; - splitCommand[1] = command.substring(ampPos.get(i) + 2, cursorPos); - splitCommand[2] = " " + command.substring(cursorPos, command.length()); - } - } - } else { - // Deals with commands that have exactly one && - if (cursorPos > ampPos.get(0)) { - splitCommand[0] = command.substring(0, ampPos.get(0) + 2) + " "; - splitCommand[1] = command.substring(ampPos.get(0) + 2, cursorPos); - splitCommand[2] = command.substring(cursorPos, command.length()); - } else if (cursorPos < ampPos.get(0)) { - splitCommand[0] = ""; - splitCommand[1] = command.substring(0, cursorPos); - splitCommand[2] = command.substring(cursorPos, command.length()); - } else { - String[] split = command.split("&&"); - splitCommand[0] = split[0]; - splitCommand[1] = ""; - splitCommand[2] = "&&" + split[1]; - } - } - - // Remove spaces so that autocomplete can work properly - splitCommand[1] = splitCommand[1].trim(); - - return splitCommand; - } - - private static ArrayList getPrevCommands() { - return prevCommands; - } - - private static void setResetVars(boolean resetVa) { - resetVars = resetVa; - } - - private static void increaseCursorPos() { - cursorPos++; - } - - private static void decreaseCursorPos() { - cursorPos--; - } - - private static int getCursorPos() { - return cursorPos; - } - - private static void setCursorPos(int cursorPosition) { - cursorPos = cursorPosition; - } -} diff --git a/src/main/java/jterm/io/input/Keys.java b/src/main/java/jterm/io/input/Keys.java index 112d22c..a776327 100644 --- a/src/main/java/jterm/io/input/Keys.java +++ b/src/main/java/jterm/io/input/Keys.java @@ -1,41 +1,63 @@ package jterm.io.input; +import jterm.io.handlers.events.CharEvent; +import jterm.io.handlers.events.Event; + public enum Keys { - UP(InputHandler::processUp), - DOWN(InputHandler::processDown), - LEFT(InputHandler::processLeft), - RIGHT(InputHandler::processRight), - TAB(9, InputHandler::tabEvent), - BACKSPACE(InputHandler::backspaceEvent), - NWLN(InputHandler::newLineEvent), - CHAR(InputHandler::charEvent), - CTRL_C(3, InputHandler::ctrlCEvent), - CTRL_Z(26, InputHandler::ctrlZEvent), - NONE(-1, null); + UP, + DOWN, + LEFT, + RIGHT, + TAB(9), + BACKSPACE, + NWLN, + CHAR, + CTRL_C(3), + CTRL_Z(26), + NONE(-1); int value; - final Runnable r; - - Keys(Runnable r) { - this.r = r; - } + Event event; + CharEvent charEvent; - public void executeAction() { - if (r != null) r.run(); + Keys() { } - Keys(int value, Runnable r) { - this.r = r; + Keys(final int value) { this.value = value; } + public boolean executeAction() { + if (event != null) { + event.process(); + return true; + } + return false; + } + + public boolean executeAction(final char c) { + if (charEvent != null) { + charEvent.process(c); + return true; + } + return false; + } + public int getValue() { return value; } - public void setValue(int value) { + public void setValue(final int value) { this.value = value; } + public void setEvent(final Event event) { + this.event = event; + } + + public void setCharEvent(CharEvent charEvent) { + this.charEvent = charEvent; + } + public static void initWindows() { UP.value = 57416; DOWN.value = 57424; @@ -63,12 +85,13 @@ public static void initGUI() { NWLN.value = 10; } - public static Keys getKeyByValue(int c) { - Keys[] keys = Keys.values(); + public static Keys getKeyByValue(final int c) { + final Keys[] keys = Keys.values(); for (Keys key : keys) { if (c == (key.value)) return key; } + CHAR.setValue(c); return CHAR; } } diff --git a/src/main/java/jterm/io/output/GuiPrinter.java b/src/main/java/jterm/io/output/GuiPrinter.java index 103ce17..3ff9fb1 100644 --- a/src/main/java/jterm/io/output/GuiPrinter.java +++ b/src/main/java/jterm/io/output/GuiPrinter.java @@ -17,49 +17,49 @@ public GuiPrinter(JTextPane textPane) { ptc = new ProtectedTextComponent(textPane); } - public void print(TextColor color, String x) { + public void print(final TextColor color, String x) { print(x, color); } @Override - public void print(TextColor color, char x) { + public void print(final TextColor color, final char x) { print(String.valueOf(x), color); } - public void print(TextColor color, Object x) { + public void print(final TextColor color, final Object x) { print(String.valueOf(x), color); } - public void println(TextColor color) { + public void println(final TextColor color) { print("\n", color); } - public void println(TextColor color, String x) { + public void println(final TextColor color, final String x) { print(x + "\n", color); } @Override - public void println(TextColor color, char x) { + public void println(final TextColor color, final char x) { print(String.valueOf(x) + "\n", color); } - public void println(TextColor color, Object x) { + public void println(final TextColor color, final Object x) { print(String.valueOf(x) + "\n", color); } - public GuiPrinter printf(TextColor color, String format, Object... args) { + public GuiPrinter printf(final TextColor color, final String format, final Object... args) { print(String.format(format, args), color); return this; } - public GuiPrinter printf(TextColor color, Locale l, String format, Object... args) { + public GuiPrinter printf(final TextColor color, final Locale l, final String format, final Object... args) { print(String.format(l, format, args), color); return this; } @Override - public void printWithPrompt(TextColor color, String s) { + public void printWithPrompt(final TextColor color, final String s) { printPrompt(); print(s, color); } @@ -76,17 +76,17 @@ public void printPrompt() { }); } - public void clearLine(String line, int cursorPosition, boolean clearPrompt) { - if (clearPrompt) ptc.clearProtections(); - String text = textPane.getText().replaceAll("\r", ""); - int ix = text.lastIndexOf("\n") + 1; - int len = line.length(); - int fullPromptLen = JTerm.PROMPT.length() + JTerm.currentDirectory.length(); - if (clearPrompt) len += fullPromptLen; - else ix += fullPromptLen; - if (ix >= text.length()) return; + public void clearLine(final String line, final int cursorPosition, final boolean clearPrompt) { + if (clearPrompt) + ptc.clearProtections(); + + String textToClear = ""; + if (clearPrompt) + textToClear += JTerm.currentDirectory + JTerm.PROMPT; + textToClear += line; + try { - textPane.getDocument().remove(ix, len); + textPane.getDocument().remove(textPane.getText().lastIndexOf(textToClear), textToClear.length()); } catch (BadLocationException e) { e.printStackTrace(); } @@ -98,16 +98,16 @@ public void clearAll() { textPane.setText(""); } - private void print(String s, TextColor c) { - StyleContext sc = StyleContext.getDefaultStyleContext(); - AttributeSet color = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, c.getColor()); - int len = textPane.getDocument().getLength(); + private void print(final String s, final TextColor c) { + final StyleContext sc = StyleContext.getDefaultStyleContext(); + final AttributeSet color = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, c.getColor()); + final int len = textPane.getDocument().getLength(); textPane.setCaretPosition(len); textPane.setCharacterAttributes(color, false); textPane.replaceSelection(s); } - private void invoke(Runnable action) { + private void invoke(final Runnable action) { try { SwingUtilities.invokeAndWait(action); } catch (InterruptedException | InvocationTargetException e) { diff --git a/src/main/java/jterm/io/terminal/HeadlessTerminal.java b/src/main/java/jterm/io/terminal/HeadlessTerminal.java index ec8a31d..af6e6bc 100644 --- a/src/main/java/jterm/io/terminal/HeadlessTerminal.java +++ b/src/main/java/jterm/io/terminal/HeadlessTerminal.java @@ -66,7 +66,6 @@ public void parse(String rawCommand) { final Process p; try { // Assumes unix, Windows would require a separate implementation... pb = new ProcessBuilder("/bin/bash", "-c", command); -// pb.inheritIO(); // Make program and process share IO to allow user to interact with program pb.redirectInput(ProcessBuilder.Redirect.PIPE); pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); pb.redirectError(ProcessBuilder.Redirect.PIPE); diff --git a/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java b/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java index c577881..66d41a2 100644 --- a/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java +++ b/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java @@ -40,16 +40,18 @@ protected void setCommandListPosition(int commandListPosition) { } private void setLArrowBehaviour() { - lArrEvent = () -> { + leftArrEvent = () -> { if (inputProcessor.getCursorPos() > 0) { JTerm.out.print(TextColor.INPUT, "\b"); inputProcessor.decreaseCursorPos(); } }; + + Keys.LEFT.setEvent(leftArrEvent); } private void setRArrowBehaviour() { - rArrEvent = () -> { + rightArrEvent = () -> { if (inputProcessor.getCursorPos() < inputProcessor.getCommand().length()) { InputHandler.clearLine(inputProcessor.getCommand(), true); JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); @@ -58,20 +60,26 @@ private void setRArrowBehaviour() { inputProcessor.moveToCursorPos(); } }; + + Keys.RIGHT.setEvent(rightArrEvent); } private void setUArrowBehaviour() { - uArrEvent = () -> { + upArrEvent = () -> { prevCommandIterator(Keys.UP); inputProcessor.setCursorPos(inputProcessor.getCommand().length()); }; + + Keys.UP.setEvent(upArrEvent); } private void setDArrowBehaviour() { - dArrEvent = () -> { + downArrEvent = () -> { prevCommandIterator(Keys.DOWN); inputProcessor.setCursorPos(inputProcessor.getCommand().length()); }; + + Keys.DOWN.setEvent(downArrEvent); } /** diff --git a/src/main/java/jterm/io/terminal/TermInputProcessor.java b/src/main/java/jterm/io/terminal/TermInputProcessor.java index b084c01..2464158 100644 --- a/src/main/java/jterm/io/terminal/TermInputProcessor.java +++ b/src/main/java/jterm/io/terminal/TermInputProcessor.java @@ -5,6 +5,7 @@ import jterm.io.handlers.InputHandler; import jterm.io.handlers.KeyHandler; import jterm.io.input.Input; +import jterm.io.input.Keys; import jterm.io.output.TextColor; import java.io.IOException; @@ -29,7 +30,7 @@ public class TermInputProcessor extends InputHandler { private int cursorPos = 0; - TermInputProcessor(HeadlessTerminal headlessTerminal) { + public TermInputProcessor(HeadlessTerminal headlessTerminal) { super(); this.headlessTerminal = headlessTerminal; @@ -75,10 +76,10 @@ protected void setCursorPos(int cursorPos) { * Calls appropriate method for handling input read from the input class. */ @Override - public void process(int input) { + public void process(final Keys key) { if (JTerm.IS_WIN) { - arrowKeyHandler.process(ArrowKeyHandler.arrowKeyCheckWindows(input)); - keyHandler.process(input); + arrowKeyHandler.process(ArrowKeyHandler.arrowKeyCheckWindows(key.getValue())); + keyHandler.process(key); } else if (JTerm.IS_UNIX) { int c1, c2; try { @@ -86,9 +87,9 @@ public void process(int input) { c2 = Input.read(false); if (c1 == -2 && c2 == -2) - keyHandler.process(input); + keyHandler.process(key); else - arrowKeyHandler.process(ArrowKeyHandler.arrowKeyCheckUnix(input, c1, c2)); + arrowKeyHandler.process(ArrowKeyHandler.arrowKeyCheckUnix(key.getValue(), c1, c2)); } catch (IOException e) { System.err.println("Error reading arrow key press"); } diff --git a/src/main/java/jterm/io/terminal/TermKeyProcessor.java b/src/main/java/jterm/io/terminal/TermKeyProcessor.java index bc11667..d851e22 100644 --- a/src/main/java/jterm/io/terminal/TermKeyProcessor.java +++ b/src/main/java/jterm/io/terminal/TermKeyProcessor.java @@ -1,8 +1,8 @@ package jterm.io.terminal; import jterm.JTerm; -import jterm.io.handlers.InputHandler; import jterm.io.handlers.KeyHandler; +import jterm.io.input.Keys; import jterm.io.output.TextColor; /** @@ -28,10 +28,8 @@ private void setUpTabEvents() { private void setUpNWLNEvent() { newLineEvent = () -> { - - String command = inputProcessor.getCommand(); - - boolean empty = "".equals(command.trim()); + final String command = inputProcessor.getCommand(); + final boolean empty = "".equals(command.trim()); if (!empty) inputProcessor.commandHistory.add(command); @@ -43,45 +41,67 @@ private void setUpNWLNEvent() { inputProcessor.setCommand(""); }; + + Keys.NWLN.setEvent(newLineEvent); } private void setUpCharEvents() { charEvent = (char input) -> { - String command = inputProcessor.getCommand(); + final String command = inputProcessor.getCommand(); int cursorPos = inputProcessor.getCursorPos(); - InputHandler.clearLine(command, true); + if (JTerm.isHeadless()) + JTerm.out.clearLine(command, cursorPos, true); - if (inputProcessor.getCursorPos() == command.length()) { - JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); - JTerm.out.print(TextColor.INPUT, command + input); + if (inputProcessor.getCursorPos() == command.length()) inputProcessor.setCommand(command + input); - } else { + else inputProcessor.setCommand(new StringBuilder(command).insert(cursorPos, input).toString()); - JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); - JTerm.out.print(TextColor.INPUT, inputProcessor.getCommand()); - } + + if (JTerm.isHeadless()) + JTerm.out.printWithPrompt(TextColor.INPUT, inputProcessor.getCommand()); inputProcessor.increaseCursorPos(); inputProcessor.moveToCursorPos(); }; + + Keys.CHAR.setCharEvent(charEvent); } private void setUpBackspaceEvent() { backspaceEvent = () -> { if (inputProcessor.getCommand().length() > 0 && inputProcessor.getCursorPos() > 0) { int charToDelete = inputProcessor.getCursorPos() - 1; - String command = inputProcessor.getCommand(); + final String command = inputProcessor.getCommand(); - InputHandler.clearLine(command, true); + if (JTerm.isHeadless()) + JTerm.out.clearLine(command, inputProcessor.getCursorPos(), true); inputProcessor.setCommand(new StringBuilder(command).deleteCharAt(charToDelete).toString()); - JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); - JTerm.out.print(TextColor.INPUT, inputProcessor.getCommand()); + if (JTerm.isHeadless()) + JTerm.out.printWithPrompt(TextColor.INPUT, inputProcessor.getCommand()); inputProcessor.decreaseCursorPos(); inputProcessor.moveToCursorPos(); } }; + + Keys.BACKSPACE.setEvent(backspaceEvent); + } + + private void setUpCtrlCEvent() { + ctrlCEvent = () -> { + System.exit(130); + }; + + Keys.CTRL_C.setEvent(ctrlCEvent); + } + + private void setupCtrlZEvent() { + ctrlZEvent = () -> { + System.exit(131); + }; + + Keys.CTRL_Z.setEvent(ctrlZEvent); } } diff --git a/src/test/java/jterm/io/InputHandlerTest.java b/src/test/java/jterm/io/InputHandlerTest.java index 76eb837..5b9f55a 100644 --- a/src/test/java/jterm/io/InputHandlerTest.java +++ b/src/test/java/jterm/io/InputHandlerTest.java @@ -1,12 +1,10 @@ package jterm.io; -import jterm.io.input.InputHandler; import org.junit.jupiter.api.Test; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; class InputHandlerTest { @Test @@ -16,8 +14,9 @@ void testDissassembleCommand() throws NoSuchMethodException, InvocationTargetExc private String[] disassembleCommand(String command) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - Method method = InputHandler.class.getDeclaredMethod("disassembleCommand", String.class); - method.setAccessible(true); - return (String[]) method.invoke(InputHandler.class, command); +// Method method = InputHandler.class.getDeclaredMethod("disassembleCommand", String.class); +// method.setAccessible(true); +// return (String[]) method.invoke(InputHandler.class, command); + return new String[]{}; } } From cf9578629b0df61c404efa90e1acaa7330423c5b Mon Sep 17 00:00:00 2001 From: nanoandrew4 Date: Mon, 5 Aug 2019 22:53:15 +0200 Subject: [PATCH 09/13] Enabled Ctrl+C + Ctrl+Z key combinations for exiting application --- src/main/java/jterm/io/terminal/TermKeyProcessor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/jterm/io/terminal/TermKeyProcessor.java b/src/main/java/jterm/io/terminal/TermKeyProcessor.java index d851e22..82909ac 100644 --- a/src/main/java/jterm/io/terminal/TermKeyProcessor.java +++ b/src/main/java/jterm/io/terminal/TermKeyProcessor.java @@ -20,6 +20,8 @@ public class TermKeyProcessor extends KeyHandler { setUpNWLNEvent(); setUpCharEvents(); setUpBackspaceEvent(); + setUpCtrlCEvent(); + setupCtrlZEvent(); } private void setUpTabEvents() { From 4b9bf0c631d8325f648887481277d804abfc7b00 Mon Sep 17 00:00:00 2001 From: nanoandrew4 Date: Tue, 6 Aug 2019 02:27:28 +0200 Subject: [PATCH 10/13] Fixed visual glitch in headless mode --- .../java/jterm/io/handlers/InputHandler.java | 18 --------- .../java/jterm/io/output/HeadlessPrinter.java | 39 ++++++++++--------- .../io/terminal/TermArrowKeyProcessor.java | 20 +++++----- .../jterm/io/terminal/TermInputProcessor.java | 13 ++++--- .../jterm/io/terminal/TermKeyProcessor.java | 3 +- 5 files changed, 38 insertions(+), 55 deletions(-) diff --git a/src/main/java/jterm/io/handlers/InputHandler.java b/src/main/java/jterm/io/handlers/InputHandler.java index a4bb28a..2f10b82 100644 --- a/src/main/java/jterm/io/handlers/InputHandler.java +++ b/src/main/java/jterm/io/handlers/InputHandler.java @@ -1,7 +1,6 @@ package jterm.io.handlers; -import jterm.JTerm; import jterm.io.input.Input; import jterm.io.input.Keys; @@ -65,21 +64,4 @@ public static Keys getKey() { return Keys.NONE; } - - /** - * Clears a line in the console of size line.length(). - * - * @param line line to be cleared - * @param clearPrompt choose to clear prompt along with line (only use true if prompt exists) - */ - public static void clearLine(String line, boolean clearPrompt) { - for (int i = 0; i < line.length() + (clearPrompt ? JTerm.PROMPT.length() : 0); i++) - System.out.print("\b"); - - for (int i = 0; i < line.length() + (clearPrompt ? JTerm.PROMPT.length() : 0); i++) - System.out.print(" "); - - for (int i = 0; i < line.length() + (clearPrompt ? JTerm.PROMPT.length() : 0); i++) - System.out.print("\b"); - } } diff --git a/src/main/java/jterm/io/output/HeadlessPrinter.java b/src/main/java/jterm/io/output/HeadlessPrinter.java index 8c49a1a..05f5fa5 100644 --- a/src/main/java/jterm/io/output/HeadlessPrinter.java +++ b/src/main/java/jterm/io/output/HeadlessPrinter.java @@ -10,32 +10,32 @@ public class HeadlessPrinter implements Printer { private static final String ANSI_HOME = "\u001b[H"; @Override - public void print(TextColor color, String x) { - System.out.print(color.ansi + x); + public void print(final TextColor color, final String str) { + System.out.print(color.ansi + str); } @Override - public void print(TextColor color, char x) { - System.out.print(color.ansi + x); + public void print(final TextColor color, final char c) { + System.out.print(color.ansi + c); } @Override - public void println(TextColor color) { + public void println(final TextColor color) { System.out.println(color.ansi); } @Override - public void println(TextColor color, String x) { - System.out.println(color.ansi + x); + public void println(final TextColor color, final String str) { + System.out.println(color.ansi + str); } @Override - public void println(TextColor color, char x) { - System.out.println(color.ansi + x); + public void println(final TextColor color, final char c) { + System.out.println(color.ansi + c); } @Override - public Printer printf(TextColor color, String format, Object... args) { + public Printer printf(final TextColor color, final String format, final Object... args) { System.out.println(color.ansi + String.format(format, args)); return this; } @@ -45,19 +45,20 @@ public void printPrompt() { System.out.print(TextColor.PROMPT.ansi + JTerm.PROMPT); } - public void printWithPrompt(TextColor color, String s) { + public void printWithPrompt(final TextColor color, final String s) { printPrompt(); - System.out.print(color + s); + System.out.print(color.ansi + s); } @Override - public void clearLine(String line, int cursorPosition, boolean clearPrompt) { - for (int i = 0; i < cursorPosition + (clearPrompt ? JTerm.PROMPT.length() : 0); i++) - JTerm.out.print(TextColor.INFO, '\b'); - for (int i = 0; i < line.length() + (clearPrompt ? JTerm.PROMPT.length() : 0); i++) - JTerm.out.print(TextColor.INFO, ' '); - for (int i = 0; i < line.length() + (clearPrompt ? JTerm.PROMPT.length() : 0); i++) - JTerm.out.print(TextColor.INFO, '\b'); + public void clearLine(final String line, final int cursorPosition, final boolean clearPrompt) { + final int charsToClear = line.length() + (clearPrompt ? JTerm.PROMPT.length() + JTerm.currentDirectory.length() : 0); + for (int i = 0; i < charsToClear; i++) + System.out.print('\b'); + for (int i = 0; i < charsToClear; i++) + System.out.print(' '); + for (int i = 0; i < charsToClear; i++) + System.out.print('\b'); } @Override diff --git a/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java b/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java index 66d41a2..29796f7 100644 --- a/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java +++ b/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java @@ -2,7 +2,6 @@ import jterm.JTerm; import jterm.io.handlers.ArrowKeyHandler; -import jterm.io.handlers.InputHandler; import jterm.io.input.Keys; import jterm.io.output.TextColor; @@ -23,7 +22,7 @@ public class TermArrowKeyProcessor extends ArrowKeyHandler { // Stores current TermInputProcessor.command when iterating through prevCommands private String currCommand = ""; - TermArrowKeyProcessor(TermInputProcessor inputProcessor) { + TermArrowKeyProcessor(final TermInputProcessor inputProcessor) { this.inputProcessor = inputProcessor; setLArrowBehaviour(); setRArrowBehaviour(); @@ -31,11 +30,11 @@ public class TermArrowKeyProcessor extends ArrowKeyHandler { setDArrowBehaviour(); } - protected void setCurrCommand(String currCommand) { + protected void setCurrCommand(final String currCommand) { this.currCommand = currCommand; } - protected void setCommandListPosition(int commandListPosition) { + protected void setCommandListPosition(final int commandListPosition) { this.commandListPosition = commandListPosition; } @@ -53,9 +52,8 @@ private void setLArrowBehaviour() { private void setRArrowBehaviour() { rightArrEvent = () -> { if (inputProcessor.getCursorPos() < inputProcessor.getCommand().length()) { - InputHandler.clearLine(inputProcessor.getCommand(), true); - JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); - JTerm.out.print(TextColor.INPUT, inputProcessor.getCommand()); + JTerm.out.clearLine(inputProcessor.getCommand(), inputProcessor.getCursorPos(), true); + JTerm.out.printWithPrompt(TextColor.INPUT, inputProcessor.getCommand()); inputProcessor.increaseCursorPos(); inputProcessor.moveToCursorPos(); } @@ -88,7 +86,7 @@ private void setDArrowBehaviour() { * * @param ak Arrow key to process */ - private void prevCommandIterator(Keys ak) { + private void prevCommandIterator(final Keys ak) { if (inputProcessor.commandHistory.size() == 0) return; @@ -101,7 +99,7 @@ private void prevCommandIterator(Keys ak) { // Move through the list towards first typed command lastArrowPress = ak; - InputHandler.clearLine(inputProcessor.getCommand(), true); + JTerm.out.clearLine(inputProcessor.getCommand(), inputProcessor.getCursorPos(), true); if (commandListPosition > inputProcessor.commandHistory.size()) commandListPosition = inputProcessor.commandHistory.size(); @@ -115,14 +113,14 @@ private void prevCommandIterator(Keys ak) { if (commandListPosition < cmdHistorySize) { // Move through list towards last typed element - InputHandler.clearLine(inputProcessor.getCommand(), true); + JTerm.out.clearLine(inputProcessor.getCommand(), inputProcessor.getCursorPos(), true); JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); JTerm.out.print(TextColor.INPUT, inputProcessor.commandHistory.get(++commandListPosition)); inputProcessor.setCommand(inputProcessor.commandHistory.get(commandListPosition)); } else if (!inputProcessor.getCommand().equals(currCommand)) { // Print command that was stored before iteration through list began - InputHandler.clearLine(inputProcessor.getCommand(), true); + JTerm.out.clearLine(inputProcessor.getCommand(), inputProcessor.getCursorPos(), true); commandListPosition++; JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); diff --git a/src/main/java/jterm/io/terminal/TermInputProcessor.java b/src/main/java/jterm/io/terminal/TermInputProcessor.java index 2464158..94c1ed1 100644 --- a/src/main/java/jterm/io/terminal/TermInputProcessor.java +++ b/src/main/java/jterm/io/terminal/TermInputProcessor.java @@ -30,7 +30,7 @@ public class TermInputProcessor extends InputHandler { private int cursorPos = 0; - public TermInputProcessor(HeadlessTerminal headlessTerminal) { + public TermInputProcessor(final HeadlessTerminal headlessTerminal) { super(); this.headlessTerminal = headlessTerminal; @@ -134,24 +134,25 @@ protected static String[] disassembleCommand(String command, int cursorPos) { for (int i = 0; i < command.length() - 1; i++) { if (command.substring(i, i + 2).equals("&&")) { ampPos.add(i); - if (cursorPos - i < 2 && cursorPos - i > 0) + if (cursorPos - i == 1) return new String[]{"", command, ""}; } } String[] splitCommand = new String[3]; + final String rightSideSplit = command.substring(cursorPos); if (ampPos.size() > 1) { // Deals with commands that have more than one && for (int i = 0; i < ampPos.size(); i++) { if (ampPos.get(i) > cursorPos) { splitCommand[0] = command.substring(0, ampPos.get(i - 1) + 2) + " "; splitCommand[1] = command.substring(ampPos.get(i - 1) + 2, cursorPos); - splitCommand[2] = " " + command.substring(cursorPos, command.length()); + splitCommand[2] = " " + rightSideSplit; } else if (i + 1 == ampPos.size()) { splitCommand[0] = command.substring(0, ampPos.get(i) + 2) + " "; splitCommand[1] = command.substring(ampPos.get(i) + 2, cursorPos); - splitCommand[2] = " " + command.substring(cursorPos, command.length()); + splitCommand[2] = " " + rightSideSplit; } } } else { @@ -159,11 +160,11 @@ protected static String[] disassembleCommand(String command, int cursorPos) { if (cursorPos > ampPos.get(0)) { splitCommand[0] = command.substring(0, ampPos.get(0) + 2) + " "; splitCommand[1] = command.substring(ampPos.get(0) + 2, cursorPos); - splitCommand[2] = command.substring(cursorPos, command.length()); + splitCommand[2] = rightSideSplit; } else if (cursorPos < ampPos.get(0)) { splitCommand[0] = ""; splitCommand[1] = command.substring(0, cursorPos); - splitCommand[2] = command.substring(cursorPos, command.length()); + splitCommand[2] = rightSideSplit; } else { String[] split = command.split("&&"); splitCommand[0] = split[0]; diff --git a/src/main/java/jterm/io/terminal/TermKeyProcessor.java b/src/main/java/jterm/io/terminal/TermKeyProcessor.java index 82909ac..1a846fe 100644 --- a/src/main/java/jterm/io/terminal/TermKeyProcessor.java +++ b/src/main/java/jterm/io/terminal/TermKeyProcessor.java @@ -14,7 +14,7 @@ public class TermKeyProcessor extends KeyHandler { private TermInputProcessor inputProcessor; - TermKeyProcessor(TermInputProcessor inputProcessor) { + TermKeyProcessor(final TermInputProcessor inputProcessor) { this.inputProcessor = inputProcessor; setUpTabEvents(); setUpNWLNEvent(); @@ -80,6 +80,7 @@ private void setUpBackspaceEvent() { JTerm.out.clearLine(command, inputProcessor.getCursorPos(), true); inputProcessor.setCommand(new StringBuilder(command).deleteCharAt(charToDelete).toString()); + if (JTerm.isHeadless()) JTerm.out.printWithPrompt(TextColor.INPUT, inputProcessor.getCommand()); From 7389059381ac98bb909255d7b976594870a6c96d Mon Sep 17 00:00:00 2001 From: nanoandrew4 Date: Thu, 8 Aug 2019 00:40:52 +0200 Subject: [PATCH 11/13] Fixed existing tests, all passing now --- src/main/java/jterm/JTerm.java | 22 +++--- src/main/java/jterm/io/input/Input.java | 3 + src/main/java/jterm/io/output/GuiPrinter.java | 55 +++++++------ .../java/jterm/io/output/HeadlessPrinter.java | 79 ++++++++++--------- src/main/java/jterm/io/output/Printer.java | 3 + .../jterm/io/terminal/TermInputProcessor.java | 2 +- src/test/java/jterm/io/InputHandlerTest.java | 22 ------ .../java/jterm/io/TermInputProcessorTest.java | 23 ++++++ src/test/java/jterm/util/UtilTest.java | 19 ++--- 9 files changed, 121 insertions(+), 107 deletions(-) delete mode 100644 src/test/java/jterm/io/InputHandlerTest.java create mode 100644 src/test/java/jterm/io/TermInputProcessorTest.java diff --git a/src/main/java/jterm/JTerm.java b/src/main/java/jterm/JTerm.java index ea626ee..5b2d8ba 100644 --- a/src/main/java/jterm/JTerm.java +++ b/src/main/java/jterm/JTerm.java @@ -104,24 +104,22 @@ public static boolean executeCommand(final String options) { } private static void initCommands() { - // Reflections reflections = new Reflections("jterm.command", new MethodAnnotationsScanner()); - // Set methods = reflections.getMethodsAnnotatedWith(Command.class); - ArrayList methods = new ArrayList<>(); - ArrayList classes = new ArrayList<>(); + final ArrayList methods = new ArrayList<>(); + final ArrayList classes = new ArrayList<>(); try { - CodeSource src = JTerm.class.getProtectionDomain().getCodeSource(); + final CodeSource src = JTerm.class.getProtectionDomain().getCodeSource(); if (src != null) { - ZipInputStream zip = new ZipInputStream(src.getLocation().openStream()); + final ZipInputStream zip = new ZipInputStream(src.getLocation().openStream()); while (true) { - ZipEntry e = zip.getNextEntry(); + final ZipEntry e = zip.getNextEntry(); if (e == null) { break; } - String name = e.getName(); + final String name = e.getName(); if (name.startsWith("jterm/command")) { classes.add(name.replace('/', '.').substring(0, name.length() - 6)); } @@ -148,9 +146,9 @@ private static void initCommands() { methods.forEach(method -> { method.setAccessible(true); - Command command = method.getDeclaredAnnotation(Command.class); + final Command command = method.getDeclaredAnnotation(Command.class); Arrays.stream(command.name()).forEach(commandName -> { - CommandExecutor executor = new CommandExecutor() + final CommandExecutor executor = new CommandExecutor() .setCommandName(commandName) .setSyntax(command.syntax()) .setMinOptions(command.minOptions()) @@ -169,7 +167,7 @@ private static void initCommands() { } private static void setOS() { - String os = System.getProperty("os.name").toLowerCase(); + final String os = System.getProperty("os.name").toLowerCase(); if (os.contains("windows")) { JTerm.IS_WIN = true; dirChar = "\\"; @@ -194,7 +192,7 @@ public static Set getCommands() { } /** For Unit Tests **/ - public static void setheadless(boolean b){ + public static void setHeadless(boolean b) { headless = b; } diff --git a/src/main/java/jterm/io/input/Input.java b/src/main/java/jterm/io/input/Input.java index b568408..ed86f93 100644 --- a/src/main/java/jterm/io/input/Input.java +++ b/src/main/java/jterm/io/input/Input.java @@ -75,6 +75,9 @@ private static void resetConsoleMode() throws IOException { } } + /** + * Handles registering of shutdown hook to return console to normal line mode. + */ protected static void registerShutdownHook() { Runtime.getRuntime().addShutdownHook(new Thread(Input::shutdownHook)); } diff --git a/src/main/java/jterm/io/output/GuiPrinter.java b/src/main/java/jterm/io/output/GuiPrinter.java index 3ff9fb1..00edf04 100644 --- a/src/main/java/jterm/io/output/GuiPrinter.java +++ b/src/main/java/jterm/io/output/GuiPrinter.java @@ -8,43 +8,46 @@ import java.lang.reflect.InvocationTargetException; import java.util.Locale; +/** + * Custom {@link Printer} implementation for the JTerm GUI mode. + */ public class GuiPrinter implements Printer { private final JTextPane textPane; private ProtectedTextComponent ptc; - public GuiPrinter(JTextPane textPane) { + public GuiPrinter(final JTextPane textPane) { this.textPane = textPane; ptc = new ProtectedTextComponent(textPane); } - public void print(final TextColor color, String x) { - print(x, color); + public void print(final TextColor color, final String str) { + print(str, color); } @Override - public void print(final TextColor color, final char x) { - print(String.valueOf(x), color); + public void print(final TextColor color, final char c) { + print(String.valueOf(c), color); } - public void print(final TextColor color, final Object x) { - print(String.valueOf(x), color); + public void print(final TextColor color, final Object o) { + print(String.valueOf(o), color); } public void println(final TextColor color) { print("\n", color); } - public void println(final TextColor color, final String x) { - print(x + "\n", color); + public void println(final TextColor color, final String str) { + print(str + "\n", color); } @Override - public void println(final TextColor color, final char x) { - print(String.valueOf(x) + "\n", color); + public void println(final TextColor color, final char c) { + print(c + "\n", color); } - public void println(final TextColor color, final Object x) { - print(String.valueOf(x) + "\n", color); + public void println(final TextColor color, final Object o) { + print(o + "\n", color); } public GuiPrinter printf(final TextColor color, final String format, final Object... args) { @@ -77,18 +80,20 @@ public void printPrompt() { } public void clearLine(final String line, final int cursorPosition, final boolean clearPrompt) { - if (clearPrompt) - ptc.clearProtections(); - - String textToClear = ""; - if (clearPrompt) - textToClear += JTerm.currentDirectory + JTerm.PROMPT; - textToClear += line; - - try { - textPane.getDocument().remove(textPane.getText().lastIndexOf(textToClear), textToClear.length()); - } catch (BadLocationException e) { - e.printStackTrace(); + if (clearPrompt || line.length() > 0) { + if (clearPrompt) + ptc.clearProtections(); + + String textToClear = ""; + if (clearPrompt) + textToClear += JTerm.currentDirectory + JTerm.PROMPT; + textToClear += line; + + try { + textPane.getDocument().remove(textPane.getText().lastIndexOf(textToClear), textToClear.length()); + } catch (BadLocationException e) { + e.printStackTrace(); + } } } diff --git a/src/main/java/jterm/io/output/HeadlessPrinter.java b/src/main/java/jterm/io/output/HeadlessPrinter.java index 05f5fa5..2df57ca 100644 --- a/src/main/java/jterm/io/output/HeadlessPrinter.java +++ b/src/main/java/jterm/io/output/HeadlessPrinter.java @@ -5,52 +5,55 @@ import java.io.IOException; +/** + * Custom {@link Printer} implementation for the headless JTerm mode. + */ public class HeadlessPrinter implements Printer { - private static final String ANSI_CLS = "\u001b[2J"; - private static final String ANSI_HOME = "\u001b[H"; + private static final String ANSI_CLS = "\u001b[2J"; + private static final String ANSI_HOME = "\u001b[H"; - @Override + @Override public void print(final TextColor color, final String str) { System.out.print(color.ansi + str); - } + } - @Override + @Override public void print(final TextColor color, final char c) { System.out.print(color.ansi + c); - } + } - @Override + @Override public void println(final TextColor color) { - System.out.println(color.ansi); - } + System.out.println(color.ansi); + } - @Override + @Override public void println(final TextColor color, final String str) { System.out.println(color.ansi + str); - } + } - @Override + @Override public void println(final TextColor color, final char c) { System.out.println(color.ansi + c); - } + } - @Override + @Override public Printer printf(final TextColor color, final String format, final Object... args) { - System.out.println(color.ansi + String.format(format, args)); - return this; - } + System.out.println(color.ansi + String.format(format, args)); + return this; + } - public void printPrompt() { - System.out.print(TextColor.PATH.ansi + JTerm.currentDirectory); - System.out.print(TextColor.PROMPT.ansi + JTerm.PROMPT); - } + public void printPrompt() { + System.out.print(TextColor.PATH.ansi + JTerm.currentDirectory); + System.out.print(TextColor.PROMPT.ansi + JTerm.PROMPT); + } public void printWithPrompt(final TextColor color, final String s) { - printPrompt(); + printPrompt(); System.out.print(color.ansi + s); - } + } - @Override + @Override public void clearLine(final String line, final int cursorPosition, final boolean clearPrompt) { final int charsToClear = line.length() + (clearPrompt ? JTerm.PROMPT.length() + JTerm.currentDirectory.length() : 0); for (int i = 0; i < charsToClear; i++) @@ -59,19 +62,19 @@ public void clearLine(final String line, final int cursorPosition, final boolean System.out.print(' '); for (int i = 0; i < charsToClear; i++) System.out.print('\b'); - } + } - @Override - public void clearAll() { - if (JTerm.IS_UNIX) { // escape sequences to clear the screen - System.out.print(ANSI_CLS + ANSI_HOME); - System.out.flush(); - } else if (JTerm.IS_WIN) { // Invoke the command line interpreter's own 'clear' command for Windows OS - try { - new ProcessBuilder("cmd", "/c", "cls").inheritIO().start().waitFor(); - } catch (IOException | InterruptedException e) { - throw new CommandException("Can't clear screen...", e); - } - } - } + @Override + public void clearAll() { + if (JTerm.IS_UNIX) { // escape sequences to clear the screen + System.out.print(ANSI_CLS + ANSI_HOME); + System.out.flush(); + } else if (JTerm.IS_WIN) { // Invoke the command line interpreter's own 'clear' command for Windows OS + try { + new ProcessBuilder("cmd", "/c", "cls").inheritIO().start().waitFor(); + } catch (IOException | InterruptedException e) { + throw new CommandException("Can't clear screen...", e); + } + } + } } diff --git a/src/main/java/jterm/io/output/Printer.java b/src/main/java/jterm/io/output/Printer.java index 30ff5ec..55259f9 100644 --- a/src/main/java/jterm/io/output/Printer.java +++ b/src/main/java/jterm/io/output/Printer.java @@ -1,5 +1,8 @@ package jterm.io.output; +/** + * Interface meant to replace System.out, so that custom printers can be implemented for GUI and headless mode. + */ public interface Printer { void print(TextColor color, String x); diff --git a/src/main/java/jterm/io/terminal/TermInputProcessor.java b/src/main/java/jterm/io/terminal/TermInputProcessor.java index 94c1ed1..2db43af 100644 --- a/src/main/java/jterm/io/terminal/TermInputProcessor.java +++ b/src/main/java/jterm/io/terminal/TermInputProcessor.java @@ -125,7 +125,7 @@ public void moveToCursorPos() { * @return Returns disassembled string, with non relevant info in elements 0 and 2, and the string to autocomplete * in element 1 */ - protected static String[] disassembleCommand(String command, int cursorPos) { + protected static String[] disassembleCommand(final String command, final Integer cursorPos) { if (!command.contains("&&")) return new String[]{"", command, ""}; diff --git a/src/test/java/jterm/io/InputHandlerTest.java b/src/test/java/jterm/io/InputHandlerTest.java deleted file mode 100644 index 5b9f55a..0000000 --- a/src/test/java/jterm/io/InputHandlerTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package jterm.io; - -import org.junit.jupiter.api.Test; - -import java.lang.reflect.InvocationTargetException; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; - -class InputHandlerTest { - @Test - void testDissassembleCommand() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - assertArrayEquals(new String[]{"", "test", ""}, disassembleCommand("test")); - } - - - private String[] disassembleCommand(String command) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { -// Method method = InputHandler.class.getDeclaredMethod("disassembleCommand", String.class); -// method.setAccessible(true); -// return (String[]) method.invoke(InputHandler.class, command); - return new String[]{}; - } -} diff --git a/src/test/java/jterm/io/TermInputProcessorTest.java b/src/test/java/jterm/io/TermInputProcessorTest.java new file mode 100644 index 0000000..5811946 --- /dev/null +++ b/src/test/java/jterm/io/TermInputProcessorTest.java @@ -0,0 +1,23 @@ +package jterm.io; + +import jterm.io.terminal.TermInputProcessor; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +class TermInputProcessorTest { + @Test + void testDissassembleCommand() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + assertArrayEquals(new String[]{"", "test", ""}, disassembleCommand("test")); + } + + + private String[] disassembleCommand(final String command) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Method method = TermInputProcessor.class.getDeclaredMethod("disassembleCommand", String.class, Integer.class); + method.setAccessible(true); + return (String[]) method.invoke(TermInputProcessor.class, command, command.length()); + } +} diff --git a/src/test/java/jterm/util/UtilTest.java b/src/test/java/jterm/util/UtilTest.java index 4cb0165..05dd8db 100644 --- a/src/test/java/jterm/util/UtilTest.java +++ b/src/test/java/jterm/util/UtilTest.java @@ -2,16 +2,17 @@ import jterm.JTerm; import jterm.gui.Terminal; -import jterm.io.output.HeadlessPrinter; -import jterm.io.output.TextColor; import jterm.io.output.CollectorPrinter; import jterm.io.output.GuiPrinter; +import jterm.io.output.HeadlessPrinter; +import jterm.io.output.TextColor; import org.junit.jupiter.api.Test; -import javax.swing.text.*; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; import java.util.Arrays; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; class UtilTest { @Test @@ -21,18 +22,18 @@ void getRunTime() { @Test void clearLineHeadless() throws BadLocationException { - JTerm.setheadless(true); + JTerm.setHeadless(true); CollectorPrinter collector = new CollectorPrinter(new HeadlessPrinter()); JTerm.out = collector; JTerm.setPrompt("/dir>> "); JTerm.out.printPrompt(); JTerm.out.clearLine("", 0, true); - assertEquals("/dir>> \b\b\b\b\b\b\b \b\b\b\b\b\b\b", collector.export()); + assertEquals("/dir>> ", collector.export()); JTerm.out.printWithPrompt(TextColor.INPUT, "stuff"); JTerm.out.clearLine("stuff", 5, true); - assertEquals("/dir>> stuff\b\b\b\b\b\b\b\b\b\b\b\b \b\b\b\b\b\b\b\b\b\b\b\b", collector.export()); + assertEquals("/dir>> stuff", collector.export()); JTerm.out.printPrompt(); JTerm.out.clearLine("", 0, false); @@ -40,12 +41,12 @@ void clearLineHeadless() throws BadLocationException { JTerm.out.printWithPrompt(TextColor.INPUT, "stuff"); JTerm.out.clearLine("stuff", 5, false); - assertEquals("/dir>> stuff\b\b\b\b\b \b\b\b\b\b", collector.export()); + assertEquals("/dir>> stuff", collector.export()); } @Test void clearLineGUI() throws BadLocationException { - JTerm.setheadless(false); + JTerm.setHeadless(false); Terminal terminal = new Terminal(); terminal.setTitle("JTerm"); terminal.setSize(720, 480); From 8ecae0ff3031868081141b69f64f6249d5a5f316 Mon Sep 17 00:00:00 2001 From: nanoandrew4 Date: Sat, 10 Aug 2019 16:23:37 +0200 Subject: [PATCH 12/13] Merged with dev branch, coverage down from 18% to 15% --- src/main/java/jterm/JTerm.java | 2 +- src/main/java/jterm/io/output/GuiPrinter.java | 5 +++++ .../java/jterm/io/output/HeadlessPrinter.java | 20 +++++++++---------- .../jterm/io/terminal/HeadlessTerminal.java | 2 +- src/test/java/jterm/util/GuiUtilTest.java | 2 +- .../java/jterm/util/HeadlessUtilTest.java | 4 ++-- 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/main/java/jterm/JTerm.java b/src/main/java/jterm/JTerm.java index 5b2d8ba..bb5eca9 100644 --- a/src/main/java/jterm/JTerm.java +++ b/src/main/java/jterm/JTerm.java @@ -94,7 +94,7 @@ public static boolean executeCommand(final String options) { return false; try { - if (JTerm.isHeadless()) out.println(TextColor.INFO); + if (JTerm.isHeadless()) out.println(); COMMANDS.get(command).execute(optionsArray); return true; } catch (CommandException e) { diff --git a/src/main/java/jterm/io/output/GuiPrinter.java b/src/main/java/jterm/io/output/GuiPrinter.java index 00edf04..69a5f7d 100644 --- a/src/main/java/jterm/io/output/GuiPrinter.java +++ b/src/main/java/jterm/io/output/GuiPrinter.java @@ -29,6 +29,11 @@ public void print(final TextColor color, final char c) { print(String.valueOf(c), color); } + @Override + public void println() { + System.out.println(); + } + public void print(final TextColor color, final Object o) { print(String.valueOf(o), color); } diff --git a/src/main/java/jterm/io/output/HeadlessPrinter.java b/src/main/java/jterm/io/output/HeadlessPrinter.java index 2df57ca..b956db5 100644 --- a/src/main/java/jterm/io/output/HeadlessPrinter.java +++ b/src/main/java/jterm/io/output/HeadlessPrinter.java @@ -14,43 +14,43 @@ public class HeadlessPrinter implements Printer { @Override public void print(final TextColor color, final String str) { - System.out.print(color.ansi + str); + System.out.print(color.getANSIColor() + str); } @Override public void print(final TextColor color, final char c) { - System.out.print(color.ansi + c); + System.out.print(color.getANSIColor() + c); } @Override - public void println(final TextColor color) { - System.out.println(color.ansi); + public void println() { + System.out.println(); } @Override public void println(final TextColor color, final String str) { - System.out.println(color.ansi + str); + System.out.println(color.getANSIColor() + str); } @Override public void println(final TextColor color, final char c) { - System.out.println(color.ansi + c); + System.out.println(color.getANSIColor() + c); } @Override public Printer printf(final TextColor color, final String format, final Object... args) { - System.out.println(color.ansi + String.format(format, args)); + System.out.println(color.getANSIColor() + String.format(format, args)); return this; } public void printPrompt() { - System.out.print(TextColor.PATH.ansi + JTerm.currentDirectory); - System.out.print(TextColor.PROMPT.ansi + JTerm.PROMPT); + System.out.print(TextColor.PATH.getANSIColor() + JTerm.currentDirectory); + System.out.print(TextColor.PROMPT.getANSIColor() + JTerm.PROMPT); } public void printWithPrompt(final TextColor color, final String s) { printPrompt(); - System.out.print(color.ansi + s); + System.out.print(color.getANSIColor() + s); } @Override diff --git a/src/main/java/jterm/io/terminal/HeadlessTerminal.java b/src/main/java/jterm/io/terminal/HeadlessTerminal.java index af6e6bc..0fb48ac 100644 --- a/src/main/java/jterm/io/terminal/HeadlessTerminal.java +++ b/src/main/java/jterm/io/terminal/HeadlessTerminal.java @@ -38,7 +38,7 @@ public void run() { public void parse(String rawCommand) { final String[] split = rawCommand.split("&&"); - JTerm.out.println(TextColor.INFO); + JTerm.out.println(); for (String command : split) { command = command.trim(); diff --git a/src/test/java/jterm/util/GuiUtilTest.java b/src/test/java/jterm/util/GuiUtilTest.java index 86a7bc5..89e7abc 100644 --- a/src/test/java/jterm/util/GuiUtilTest.java +++ b/src/test/java/jterm/util/GuiUtilTest.java @@ -18,7 +18,7 @@ public class GuiUtilTest { @BeforeAll static void init() { - JTerm.setheadless(false); + JTerm.setHeadless(false); } @BeforeEach diff --git a/src/test/java/jterm/util/HeadlessUtilTest.java b/src/test/java/jterm/util/HeadlessUtilTest.java index e5fec7b..2b8f196 100644 --- a/src/test/java/jterm/util/HeadlessUtilTest.java +++ b/src/test/java/jterm/util/HeadlessUtilTest.java @@ -15,9 +15,9 @@ public class HeadlessUtilTest { @BeforeAll static void init() { - JTerm.setheadless(true); + JTerm.setHeadless(true); JTerm.setPrompt(">> "); - JTerm.setCurrentDirectory("/dir"); + JTerm.currentDirectory = "/dir"; } @BeforeEach From b5fe9963368f0fa0248ea6eacb8c688726fe4e39 Mon Sep 17 00:00:00 2001 From: nanoandrew4 Date: Sun, 11 Aug 2019 19:40:28 +0200 Subject: [PATCH 13/13] Fixed up and down arrow key printing, current directory was previously not printed --- .../java/jterm/io/terminal/TermArrowKeyProcessor.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java b/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java index 29796f7..55af25f 100644 --- a/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java +++ b/src/main/java/jterm/io/terminal/TermArrowKeyProcessor.java @@ -104,8 +104,7 @@ private void prevCommandIterator(final Keys ak) { if (commandListPosition > inputProcessor.commandHistory.size()) commandListPosition = inputProcessor.commandHistory.size(); - JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); - JTerm.out.print(TextColor.INPUT, inputProcessor.commandHistory.get(--commandListPosition)); + JTerm.out.printWithPrompt(TextColor.INPUT, inputProcessor.commandHistory.get(--commandListPosition)); inputProcessor.setCommand(inputProcessor.commandHistory.get(commandListPosition)); } else if (ak == Keys.DOWN) { @@ -115,16 +114,14 @@ private void prevCommandIterator(final Keys ak) { // Move through list towards last typed element JTerm.out.clearLine(inputProcessor.getCommand(), inputProcessor.getCursorPos(), true); - JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); - JTerm.out.print(TextColor.INPUT, inputProcessor.commandHistory.get(++commandListPosition)); + JTerm.out.printWithPrompt(TextColor.INPUT, inputProcessor.commandHistory.get(++commandListPosition)); inputProcessor.setCommand(inputProcessor.commandHistory.get(commandListPosition)); } else if (!inputProcessor.getCommand().equals(currCommand)) { // Print command that was stored before iteration through list began JTerm.out.clearLine(inputProcessor.getCommand(), inputProcessor.getCursorPos(), true); commandListPosition++; - JTerm.out.print(TextColor.PROMPT, JTerm.PROMPT); - JTerm.out.print(TextColor.INPUT, currCommand); + JTerm.out.printWithPrompt(TextColor.INPUT, currCommand); inputProcessor.setCommand(currCommand); } }