Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ build
test_fjage
docs/doc

.fjage-shell-history
16 changes: 12 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ repositories {

dependencies {
api 'org.codehaus.groovy:groovy:2.5.23'
api 'org.jline:jline:3.25.0'
api 'org.jline:jline:3.30.4'
api 'org.apache.commons:commons-lang3:3.17.0'
api 'commons-io:commons-io:2.19.0'
api 'uk.com.robust-it:cloning:1.9.12'
Expand Down Expand Up @@ -87,6 +87,10 @@ jar {
}
}

processResources {
exclude '**/readme.md'
}

test {
testLogging {
events "passed", "skipped", "failed"
Expand Down Expand Up @@ -149,16 +153,20 @@ task jsdoc(type: Exec){

task updatexterm {
doLast {
// create package.json with `{}` if it does not exist
if (!file('package.json').exists()) {
file('package.json').text = '{}'
}
exec {
workingDir '.'
executable npmcmd
args 'install', 'xterm@4.19.0', 'xterm-addon-attach@0.6.0', 'xterm-addon-fit@0.5.0', 'xterm-addon-web-links@0.6.0'
args 'install', '@xterm/[email protected].0', '@xterm/addon-attach@0.11.0', '@xterm/addon-fit@0.10.0', '@xterm/addon-web-links@0.11.0'
}
copy {
from (['node_modules/xterm-addon-web-links/lib', 'node_modules/xterm-addon-attach/lib', 'node_modules/xterm-addon-fit/lib', 'node_modules/xterm/lib']) {
from (['node_modules/@xterm/addon-web-links/lib', 'node_modules/@xterm/addon-attach/lib', 'node_modules/@xterm/addon-fit/lib', 'node_modules/@xterm/xterm/lib']) {
include '*.js'
}
from ('node_modules/xterm/css'){
from ('node_modules/@xterm/xterm/css'){
include 'xterm.css'
}
into 'src/main/resources/org/arl/fjage/web/shell'
Expand Down
5 changes: 3 additions & 2 deletions etc/initrc.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import org.arl.fjage.*
import org.arl.fjage.remote.*
import org.arl.fjage.shell.*
import org.arl.fjage.connectors.*
import java.nio.file.Paths

boolean web = System.properties.getProperty('fjage.web') == 'true'
int port = 5081
Expand All @@ -26,10 +27,10 @@ if (devname != null) container.addConnector(new SerialPortConnector(devname, ba
if (web) {
WebServer.getInstance(8080).addStatic("/", "/org/arl/fjage/web")
Connector conn = new WebSocketHubConnector(8080, "/shell/ws")
shell = new ShellAgent(new ConsoleShell(conn), new GroovyScriptEngine())
shell = new ShellAgent(new ConsoleShell(conn, Paths.get(".fjage-shell-history")), new GroovyScriptEngine())
container.openWebSocketServer(8080, "/ws")
} else {
shell = new ShellAgent(new ConsoleShell(), new GroovyScriptEngine())
shell = new ShellAgent(new ConsoleShell(Paths.get(".fjage-shell-history")), new GroovyScriptEngine())
}
container.add 'shell', shell
platform.start()
Expand Down
118 changes: 70 additions & 48 deletions src/main/java/org/arl/fjage/shell/ConsoleShell.java
Original file line number Diff line number Diff line change
@@ -1,33 +1,39 @@
/******************************************************************************

Copyright (c) 2018-2019, Mandar Chitre
Copyright (c) 2018-2019, Mandar Chitre

This file is part of fjage which is released under Simplified BSD License.
See file LICENSE.txt or go to http://www.opensource.org/licenses/BSD-3-Clause
for full license details.

******************************************************************************/
This file is part of fjage which is released under Simplified BSD License.
See file LICENSE.txt or go to http://www.opensource.org/licenses/BSD-3-Clause
for full license details.
******************************************************************************/

package org.arl.fjage.shell;

import java.io.*;
import java.nio.file.Path;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.arl.fjage.connectors.ConnectionListener;
import org.arl.fjage.connectors.Connector;
import org.arl.fjage.connectors.WebSocketHubConnector;
import org.jline.reader.*;
import org.jline.reader.impl.history.DefaultHistory;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStyle;
import org.jline.widget.AutosuggestionWidgets;

/**
* Shell input/output driver for console devices with line editing and
* color support.
*/
public class ConsoleShell implements Shell, ConnectionListener {

private static final String FORCE_BRACKETED_PASTE_ON = "FORCE_BRACKETED_PASTE_ON";
private static final String BRACKETED_PASTE_ON = "\033[?2004h";
private final Logger log = Logger.getLogger(getClass().getName());
private Terminal term = null;
private LineReader console = null;
private Connector connector = null;
Expand All @@ -37,10 +43,7 @@ public class ConsoleShell implements Shell, ConnectionListener {
private AttributedStyle outputStyle = null;
private AttributedStyle notifyStyle = null;
private AttributedStyle errorStyle = null;
private Logger log = Logger.getLogger(getClass().getName());

private static final String FORCE_BRACKETED_PASTE_ON = "FORCE_BRACKETED_PASTE_ON";
private static final String BRACKETED_PASTE_ON = "\033[?2004h";
private Path historyFile = null;

/**
* Create a console shell attached to the system terminal.
Expand All @@ -50,22 +53,22 @@ public ConsoleShell() {
term = TerminalBuilder.terminal();
setupStyles();
} catch (IOException ex) {
log.warning("Unable to open terminal: "+ex.toString());
log.log(Level.WARNING, "Unable to open terminal: ", ex);
}
}

/**
* Create a console shell attached to a specified input and output stream.
*
* @param in input stream.
* @param in input stream.
* @param out output stream.
*/
public ConsoleShell(InputStream in, OutputStream out) {
try {
term = TerminalBuilder.builder().system(false).type("xterm").streams(in, out).build();
term = TerminalBuilder.builder().system(false).type("xterm").jni(false).jansi(false).streams(in, out).build();
setupStyles();
} catch (IOException ex) {
log.warning("Unable to open terminal: "+ex.toString());
log.log(Level.WARNING, "Unable to open terminal: ", ex);
}
}

Expand All @@ -80,13 +83,45 @@ public ConsoleShell(Connector connector) {
OutputStream out = connector.getOutputStream();
connector.setConnectionListener(this);
this.connector = connector;
term = TerminalBuilder.builder().system(false).type("xterm").streams(in, out).build();
term = TerminalBuilder.builder().system(false).type("xterm").jni(false).jansi(false).streams(in, out).build();
setupStyles();
} catch (IOException ex) {
log.warning("Unable to open terminal: "+ex.toString());
log.log(Level.WARNING, "Unable to open terminal: ", ex);
}
}

/**
* Create a console shell attached to the system terminal.
* @param historyFile file to store command history.
*/
public ConsoleShell(Path historyFile) {
this();
this.historyFile = historyFile;
}

/**
* Create a console shell attached to a specified input and output stream.
*
* @param in input stream.
* @param out output stream.
* @param historyFile file to store command history.
*/
public ConsoleShell(InputStream in, OutputStream out, Path historyFile) {
this(in, out);
this.historyFile = historyFile;
}

/**
* Create a console shell attached to a specified connector.
*
* @param connector input/output streams.
* @param historyFile file to store command history.
*/
public ConsoleShell(Connector connector, Path historyFile){
this(connector);
this.historyFile = historyFile;
}

@Override
public void connected(Connector connector) {
try {
Expand All @@ -96,17 +131,17 @@ public void connected(Connector connector) {
console.callWidget(LineReader.REDRAW_LINE);
console.callWidget(LineReader.REDISPLAY);
}
} catch(IllegalStateException ex) {
// safely ignore exception
} catch (IllegalStateException ex) {
log.log(Level.FINE, "Unable to redraw console: ", ex);
}
}

private void setupStyles() {
AttributedStyle style = new AttributedStyle();
promptStyle = style.foreground(AttributedStyle.BRIGHT+AttributedStyle.YELLOW);
promptStyle = style.foreground(AttributedStyle.BRIGHT + AttributedStyle.YELLOW);
inputStyle = style.foreground(AttributedStyle.WHITE);
outputStyle = style.foreground(AttributedStyle.GREEN);
notifyStyle = style.foreground(AttributedStyle.BRIGHT+AttributedStyle.BLUE);
notifyStyle = style.foreground(AttributedStyle.BRIGHT + AttributedStyle.BLUE);
errorStyle = style.foreground(AttributedStyle.RED);
}

Expand All @@ -122,31 +157,18 @@ public void init(ScriptEngine engine) {
if (scriptEngine == null)
console = LineReaderBuilder.builder().terminal(term).option(LineReader.Option.AUTO_FRESH_LINE, true).build();
else {
Parser parser = new Parser() {
@Override
public CompletingParsedLine parse(String s, int cursor) {
if (!scriptEngine.isComplete(s)) throw new EOFError(-1, -1, "");
if (s.contains("\n") && cursor < s.length()) throw new EOFError(-1, -1, "");
return null;
}
@Override
public CompletingParsedLine parse(String s, int cursor, Parser.ParseContext context) {
return parse(s, cursor);
}
@Override
public boolean isEscapeChar(char ch) {
return false;
}
};
console = LineReaderBuilder.builder().parser(parser).terminal(term).build();
console.setVariable(LineReader.DISABLE_COMPLETION, true);
console.setOpt(LineReader.Option.ERASE_LINE_ON_FINISH);
console.getWidgets().put(FORCE_BRACKETED_PASTE_ON, new Widget() {
@Override
public boolean apply() {
console.getTerminal().writer().write(BRACKETED_PASTE_ON);
return true;
}
if (historyFile == null) {
console = LineReaderBuilder.builder().terminal(term).build();
} else {
console = LineReaderBuilder.builder().terminal(term).history(new DefaultHistory()).build();
console.setVariable(LineReader.HISTORY_FILE, historyFile); // set history file
console.setVariable(LineReader.HISTORY_SIZE, 1000); // set history size
}
AutosuggestionWidgets autosuggestionWidgets = new AutosuggestionWidgets(console);
autosuggestionWidgets.enable();
console.getWidgets().put(FORCE_BRACKETED_PASTE_ON, () -> {
console.getTerminal().writer().write(BRACKETED_PASTE_ON);
return true;
});
}
}
Expand Down Expand Up @@ -186,13 +208,13 @@ public String readLine(String prompt1, String prompt2, String line) {
if (console == null) return null;
try {
console.setVariable(LineReader.SECONDARY_PROMPT_PATTERN, prompt2);
return console.readLine(prompt1, null, (Character)null, line);
return console.readLine(prompt1, null, (Character) null, line);
} catch (UserInterruptException ex) {
return ABORT;
} catch (EndOfFileException ex) {
return null;
} catch (Throwable ex) {
log.warning(ex.toString());
log.log(Level.WARNING, "Error reading line: ", ex);
return "";
}
}
Expand All @@ -208,7 +230,7 @@ public void shutdown() {
try {
term.close();
} catch (IOException ex) {
// do nothing
log.log(Level.FINE, "Error closing terminal: ", ex);
}
term = null;
}
Expand Down
Loading
Loading