diff --git a/.vscode/launch.json b/.vscode/launch.json index aa506d012ad..e5fb91aec1a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -30,7 +30,8 @@ "request": "launch", "mainClass": "org.rascalmpl.shell.RascalShell", "projectName": "rascal", - "vmArgs": "-Xss80m -Xmx2g -ea" + "vmArgs": "-Xss80m -Xmx2g -ea", + "console": "integratedTerminal" }, { "type": "java", @@ -38,7 +39,7 @@ "request": "launch", "mainClass": "org.rascalmpl.shell.RascalShell", "projectName": "rascal", - "cwd" : "${workspaceFolder}/../rascal-tutor", + "cwd": "${workspaceFolder}/../rascal-tutor", "vmArgs": "-Xss80m -Xmx2g -ea" }, { diff --git a/pom.xml b/pom.xml index 171cec5cf5b..9ff6ca5d28f 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,7 @@ 3 11 0.28.9-BOOT1 + 3.27.0 @@ -235,6 +236,7 @@ **/org/rascalmpl/test/AllSuiteParallel.java **/org/rascalmpl/test/library/LibraryLangPaths.java **/org/rascalmpl/test/value/AllTests.java + **/org/rascalmpl/test/repl/*Test.java **/org/rascalmpl/*Test.java @@ -331,19 +333,6 @@ false - - - org.fusesource.jansi - org.rascalmpl.fusesource.jansi - - org.fusesource.jansi.internal.* - - - - jline - org.rascalmpl.jline - - *:* @@ -471,10 +460,35 @@ gson 2.11.0 - - jline - jline - 2.14.6 + + org.jline + jline-reader + ${jline.version} + + + org.jline + jline-terminal + ${jline.version} + + + org.jline + jline-terminal-jni + ${jline.version} + + + org.jline + jline-style + ${jline.version} + + + org.jline + jline-console + ${jline.version} + + + org.jline + jansi-core + ${jline.version} org.yaml @@ -563,44 +577,7 @@ shade - - - - rascal - org.rascalmpl.shell.RascalShell - ${project.version} - http://www.usethesource.io - - - - - - org.fusesource.jansi - org.rascalmpl.fusesource.jansi - - org.fusesource.jansi.internal.* - - - - jline - org.rascalmpl.jline - - - - - *:* - - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - jline:* - - + true diff --git a/src/org/rascalmpl/checker/StaticChecker.java b/src/org/rascalmpl/checker/StaticChecker.java deleted file mode 100644 index acb755fa842..00000000000 --- a/src/org/rascalmpl/checker/StaticChecker.java +++ /dev/null @@ -1,125 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2009-2013 CWI - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI - * * Mark Hills - Mark.Hills@cwi.nl (CWI) - * * Arnold Lankamp - Arnold.Lankamp@cwi.nl -*******************************************************************************/ -package org.rascalmpl.checker; - -import java.io.OutputStream; -import java.util.HashMap; -import java.util.Map; - -import org.rascalmpl.debug.IRascalMonitor; -import org.rascalmpl.exceptions.ImplementationError; -import org.rascalmpl.interpreter.Configuration; -import org.rascalmpl.interpreter.Evaluator; -import org.rascalmpl.interpreter.env.GlobalEnvironment; -import org.rascalmpl.interpreter.env.ModuleEnvironment; -import org.rascalmpl.interpreter.load.StandardLibraryContributor; -import org.rascalmpl.parser.gtd.exception.ParseError; -import org.rascalmpl.uri.URIUtil; -import org.rascalmpl.values.ValueFactoryFactory; -import org.rascalmpl.values.parsetrees.ITree; - -import io.usethesource.vallang.ISourceLocation; -import io.usethesource.vallang.IValue; -import io.usethesource.vallang.IValueFactory; -import io.usethesource.vallang.type.Type; -import io.usethesource.vallang.type.TypeStore; - -public class StaticChecker { - private final Evaluator eval; - public static final String TYPECHECKER = "typecheckTree"; - private boolean checkerEnabled; - private boolean initialized; - private boolean loaded; - private Type pathConfigConstructor = null; - - public StaticChecker(OutputStream stderr, OutputStream stdout) { - GlobalEnvironment heap = new GlobalEnvironment(); - ModuleEnvironment root = heap.addModule(new ModuleEnvironment("$staticchecker$", heap)); - eval = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, stderr, stdout, root, heap, IRascalMonitor.buildConsoleMonitor(System.in, System.out)); - eval.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); - checkerEnabled = false; - initialized = false; - loaded = false; - } - - private IValue eval(IRascalMonitor monitor, String cmd) { - try { - return eval.eval(monitor, cmd, URIUtil.rootLocation("checker")).getValue(); - } catch (ParseError pe) { - throw new ImplementationError("syntax error in static checker modules", pe); - } - } - - public synchronized void load(IRascalMonitor monitor) { - eval(monitor, "import lang::rascal::types::CheckTypes;"); - eval(monitor, "import util::Reflective;"); - TypeStore ts = eval.getHeap().getModule("util::Reflective").getStore(); - pathConfigConstructor = ts.lookupConstructor(ts.lookupAbstractDataType("PathConfig"), "pathConfig").iterator().next(); - loaded = true; - } - - public void init() { - initialized = true; - } - - public Configuration getConfiguration() { - return eval.getConfiguration(); - } - - public boolean isInitialized() { - return initialized; - } - - public synchronized ITree checkModule(IRascalMonitor monitor, ISourceLocation module) { - if (checkerEnabled) { - return (ITree) eval.call(monitor, "check", module, getPathConfig()); - } - return null; - } - - private IValue getPathConfig() { - assert pathConfigConstructor != null; - IValueFactory vf = ValueFactoryFactory.getValueFactory(); - Map kwArgs = new HashMap<>(); - kwArgs.put("srcPath", vf.list(eval.getRascalResolver().collect().toArray(new IValue[0]))); - // default args for the rest - return vf.constructor(pathConfigConstructor, new IValue[0], kwArgs); - } - - public synchronized void disableChecker() { - checkerEnabled = false; - } - - public synchronized void enableChecker(IRascalMonitor monitor) { - if (!loaded) { - load(monitor); - } - checkerEnabled = true; - } - - public boolean isCheckerEnabled() { - return checkerEnabled; - } - - public void addRascalSearchPath(ISourceLocation uri) { - eval.addRascalSearchPath(uri); - } - - public void addClassLoader(ClassLoader classLoader) { - eval.addClassLoader(classLoader); - } - - public Evaluator getEvaluator() { - return eval; - } -} diff --git a/src/org/rascalmpl/debug/IRascalMonitor.java b/src/org/rascalmpl/debug/IRascalMonitor.java index 06452422f98..d64c219a3c8 100644 --- a/src/org/rascalmpl/debug/IRascalMonitor.java +++ b/src/org/rascalmpl/debug/IRascalMonitor.java @@ -12,21 +12,18 @@ *******************************************************************************/ package org.rascalmpl.debug; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; + +import org.jline.terminal.Terminal; import org.rascalmpl.interpreter.BatchProgressMonitor; import org.rascalmpl.interpreter.NullRascalMonitor; import org.rascalmpl.repl.TerminalProgressBarMonitor; import io.usethesource.vallang.ISourceLocation; -import jline.Terminal; -import jline.TerminalFactory; public interface IRascalMonitor { /** @@ -158,8 +155,8 @@ default void jobStep(String name, String message) { * and otherwise default to a dumn terminal console progress logger. * @return */ - public static IRascalMonitor buildConsoleMonitor(InputStream in, OutputStream out) { - return buildConsoleMonitor(in, out, inBatchMode()); + public static IRascalMonitor buildConsoleMonitor(Terminal term) { + return buildConsoleMonitor(term, inBatchMode()); } public static boolean inBatchMode() { @@ -168,12 +165,11 @@ public static boolean inBatchMode() { ; } - public static IRascalMonitor buildConsoleMonitor(InputStream in, OutputStream out, boolean batchMode) { - Terminal terminal = TerminalFactory.get(); + public static IRascalMonitor buildConsoleMonitor(Terminal terminal, boolean batchMode) { - return !batchMode && terminal.isAnsiSupported() - ? new TerminalProgressBarMonitor(out, in, terminal) - : new BatchProgressMonitor(new PrintStream(out)) + return !batchMode && TerminalProgressBarMonitor.shouldWorkIn(terminal) + ? new TerminalProgressBarMonitor(terminal) + : new BatchProgressMonitor(terminal.writer()) ; } diff --git a/src/org/rascalmpl/ideservices/BasicIDEServices.java b/src/org/rascalmpl/ideservices/BasicIDEServices.java index f799fdecb4a..2e9ccbaa3e8 100644 --- a/src/org/rascalmpl/ideservices/BasicIDEServices.java +++ b/src/org/rascalmpl/ideservices/BasicIDEServices.java @@ -20,6 +20,7 @@ import java.nio.file.Path; import java.nio.file.Paths; +import org.jline.terminal.Terminal; import org.rascalmpl.debug.IRascalMonitor; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; @@ -34,16 +35,24 @@ public class BasicIDEServices implements IDEServices { private final IRascalMonitor monitor; private final PrintWriter stderr; + private final Terminal terminal; - public BasicIDEServices(PrintWriter stderr, IRascalMonitor monitor){ + public BasicIDEServices(PrintWriter stderr, IRascalMonitor monitor, Terminal terminal){ this.stderr = stderr; this.monitor = monitor; + this.terminal = terminal; } @Override public PrintWriter stderr() { return stderr; } + + @Override + public Terminal activeTerminal() { + return terminal; + } + public void browse(ISourceLocation loc, String title, int viewColumn){ browse(loc.getURI(), title, viewColumn); diff --git a/src/org/rascalmpl/ideservices/IDEServices.java b/src/org/rascalmpl/ideservices/IDEServices.java index c5164d95071..e85d06c4deb 100644 --- a/src/org/rascalmpl/ideservices/IDEServices.java +++ b/src/org/rascalmpl/ideservices/IDEServices.java @@ -15,6 +15,7 @@ import java.io.PrintWriter; import java.net.URI; +import org.jline.terminal.Terminal; import org.rascalmpl.debug.IRascalMonitor; import org.rascalmpl.uri.LogicalMapResolver; import org.rascalmpl.uri.URIResolverRegistry; @@ -73,6 +74,16 @@ default void unregisterLanguage(IConstructor language) { throw new UnsupportedOperationException("registerLanguage is not implemented in this environment."); } + /** + * Get access to the current terminal.
+ * used for features such as clearing the terminal, and starting a nested REPL.
+ * Can return null if there is no active terminal. + * @return a terminal if there is one, null otherwise. + */ + default Terminal activeTerminal() { + return null; + } + /** * Asks the IDE to apply document edits as defined in the standard library module * analysis::diff::edits::TextEdits, according to the semantics defined in diff --git a/src/org/rascalmpl/interpreter/BatchProgressMonitor.java b/src/org/rascalmpl/interpreter/BatchProgressMonitor.java index 82793b67d80..3da79f59810 100644 --- a/src/org/rascalmpl/interpreter/BatchProgressMonitor.java +++ b/src/org/rascalmpl/interpreter/BatchProgressMonitor.java @@ -24,11 +24,15 @@ public class BatchProgressMonitor implements IRascalMonitor { PrintWriter out; public BatchProgressMonitor() { - this.out = new PrintWriter(System.err); + this(new PrintWriter(System.err, true)); } public BatchProgressMonitor(PrintStream out) { - this.out = new PrintWriter(out); + this(new PrintWriter(out, true)); + } + + public BatchProgressMonitor(PrintWriter out) { + this.out = out; } @Override diff --git a/src/org/rascalmpl/interpreter/DefaultTestResultListener.java b/src/org/rascalmpl/interpreter/DefaultTestResultListener.java index 9d4ced0c270..a15d5529627 100644 --- a/src/org/rascalmpl/interpreter/DefaultTestResultListener.java +++ b/src/org/rascalmpl/interpreter/DefaultTestResultListener.java @@ -15,7 +15,7 @@ import java.io.PrintWriter; -import org.rascalmpl.repl.ReplTextWriter; +import org.rascalmpl.repl.streams.ReplTextWriter; import io.usethesource.vallang.ISourceLocation; @@ -28,14 +28,16 @@ public class DefaultTestResultListener implements ITestResultListener { private String context; private final boolean verbose; private PrintWriter out; + private PrintWriter err; - public DefaultTestResultListener(PrintWriter out) { - this(out, true); + public DefaultTestResultListener(PrintWriter out, PrintWriter err) { + this(out, err, true); } - public DefaultTestResultListener(PrintWriter out, boolean verbose){ + public DefaultTestResultListener(PrintWriter out, PrintWriter err, boolean verbose){ super(); + this.err = err; this.out = out; this.verbose = verbose; reset(); @@ -49,7 +51,7 @@ private void reset() { } public void setErrorStream(PrintWriter errorStream) { - this.out = errorStream; + this.err = errorStream; } @Override @@ -71,14 +73,14 @@ public void done() { // make sure results are reported on a newline out.println(); } - out.println("\rTest report for " + context); + out.println("Test report for " + context); if (errors + failures == 0) { out.println("\tall " + (count - ignored) + "/" + count + " tests succeeded"); } else { out.println("\t" + successes + "/" + count + " tests succeeded"); - out.println("\t" + failures + "/" + count + " tests failed"); - out.println("\t" + errors + "/" + count + " tests threw exceptions"); + err.println("\t" + failures + "/" + count + " tests failed"); + err.println("\t" + errors + "/" + count + " tests threw exceptions"); } if (ignored != 0) { @@ -86,6 +88,7 @@ public void done() { } out.flush(); + err.flush(); } } @@ -99,9 +102,10 @@ else if (t != null) { errors++; if (!verbose) { out.println(); + out.flush(); } - out.println("error: " + test + " @ " + ReplTextWriter.valueToString(loc)); - out.println(message); + err.println("error: " + test + " @ " + ReplTextWriter.valueToString(loc)); + err.println(message); } else { failures++; diff --git a/src/org/rascalmpl/interpreter/Evaluator.java b/src/org/rascalmpl/interpreter/Evaluator.java index 5183ce362df..0f271f7bea0 100755 --- a/src/org/rascalmpl/interpreter/Evaluator.java +++ b/src/org/rascalmpl/interpreter/Evaluator.java @@ -22,16 +22,11 @@ import static org.rascalmpl.semantics.dynamic.Import.parseFragments; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Reader; import java.io.StringReader; import java.net.URI; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -41,9 +36,10 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.SortedMap; import java.util.SortedSet; import java.util.Stack; -import java.util.TreeSet; +import java.util.TreeMap; import java.util.concurrent.CopyOnWriteArrayList; import org.rascalmpl.ast.AbstractAST; @@ -99,7 +95,6 @@ import org.rascalmpl.parser.gtd.result.out.DefaultNodeFlattener; import org.rascalmpl.parser.uptr.UPTRNodeFactory; import org.rascalmpl.parser.uptr.action.NoActionExecutor; -import org.rascalmpl.repl.TerminalProgressBarMonitor; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.values.RascalFunctionValueFactory; @@ -195,17 +190,13 @@ public void decCallNesting() { private final List classLoaders; // sharable if frozen private final ModuleEnvironment rootScope; // sharable if frozen - private final OutputStream defStderr; - private final OutputStream defStdout; private final PrintWriter defOutWriter; private final PrintWriter defErrWriter; - private final InputStream defInput; + private final Reader defInput; - private OutputStream curStderr = null; - private OutputStream curStdout = null; private PrintWriter curOutWriter = null; private PrintWriter curErrWriter = null; - private InputStream curInput = null; + private Reader curInput = null; /** * Probably not sharable @@ -232,26 +223,26 @@ public void decCallNesting() { private static final Object dummy = new Object(); /** - * Promotes the monitor to the outputstream automatically if so required. + * Promotes the monitor to the PrintWriter automatically if so required. */ - public Evaluator(IValueFactory f, InputStream input, OutputStream stderr, OutputStream stdout, IRascalMonitor monitor, ModuleEnvironment scope, GlobalEnvironment heap) { - this(f, input, stderr, monitor instanceof OutputStream ? (OutputStream) monitor : stdout, scope, heap, new ArrayList(Collections.singleton(Evaluator.class.getClassLoader())), new RascalSearchPath()); + public Evaluator(IValueFactory f, Reader input, PrintWriter stderr, PrintWriter stdout, IRascalMonitor monitor, ModuleEnvironment scope, GlobalEnvironment heap) { + this(f, input, stderr, monitor instanceof PrintWriter ? (PrintWriter) monitor : stdout, scope, heap, new ArrayList(Collections.singleton(Evaluator.class.getClassLoader())), new RascalSearchPath()); } /** * If your monitor should wrap stdout (like TerminalProgressBarMonitor) then you can use this constructor. */ - public Evaluator(IValueFactory f, InputStream input, OutputStream stderr, M monitor, ModuleEnvironment scope, GlobalEnvironment heap) { + public Evaluator(IValueFactory f, Reader input, PrintWriter stderr, M monitor, ModuleEnvironment scope, GlobalEnvironment heap) { this(f, input, stderr, monitor, scope, heap, new ArrayList(Collections.singleton(Evaluator.class.getClassLoader())), new RascalSearchPath()); setMonitor(monitor); } - public Evaluator(IValueFactory f, InputStream input, OutputStream stderr, OutputStream stdout, ModuleEnvironment scope, GlobalEnvironment heap, IRascalMonitor monitor) { - this(f, input, stderr, monitor instanceof OutputStream ? (OutputStream) monitor : stdout, scope, heap, new ArrayList(Collections.singleton(Evaluator.class.getClassLoader())), new RascalSearchPath()); + public Evaluator(IValueFactory f, Reader input, PrintWriter stderr, PrintWriter stdout, ModuleEnvironment scope, GlobalEnvironment heap, IRascalMonitor monitor) { + this(f, input, stderr, monitor instanceof PrintWriter ? (PrintWriter) monitor : stdout, scope, heap, new ArrayList(Collections.singleton(Evaluator.class.getClassLoader())), new RascalSearchPath()); setMonitor(monitor); } - public Evaluator(IValueFactory vf, InputStream input, OutputStream stderr, OutputStream stdout, ModuleEnvironment scope, GlobalEnvironment heap, List classLoaders, RascalSearchPath rascalPathResolver) { + public Evaluator(IValueFactory vf, Reader input, PrintWriter stderr, PrintWriter stdout, ModuleEnvironment scope, GlobalEnvironment heap, List classLoaders, RascalSearchPath rascalPathResolver) { super(); this.vf = new RascalFunctionValueFactory(this); @@ -265,10 +256,8 @@ public Evaluator(IValueFactory vf, InputStream input, OutputStream stderr, Outpu this.rascalPathResolver = rascalPathResolver; this.resolverRegistry = rascalPathResolver.getRegistry(); this.defInput = input; - this.defStderr = stderr; - this.defStdout = stdout; - this.defErrWriter = wrapWriter(stderr, true); - this.defOutWriter = wrapWriter(stdout, false); + this.defErrWriter = stderr; + this.defOutWriter = stdout; this.constructorDeclaredListeners = new HashMap(); this.suspendTriggerListeners = new CopyOnWriteArrayList(); @@ -285,10 +274,6 @@ public Evaluator(IValueFactory vf, InputStream input, OutputStream stderr, Outpu setEventTrigger(AbstractInterpreterEventTrigger.newNullEventTrigger()); } - private static PrintWriter wrapWriter(OutputStream out, boolean autoFlush) { - return new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8), autoFlush); - } - private Evaluator(Evaluator source, ModuleEnvironment scope) { super(); @@ -307,8 +292,6 @@ private Evaluator(Evaluator source, ModuleEnvironment scope) { this.javaBridge = new JavaBridge(classLoaders, vf, config); this.rascalPathResolver = source.rascalPathResolver; this.resolverRegistry = source.resolverRegistry; - this.defStderr = source.defStderr; - this.defStdout = source.defStdout; this.defInput = source.defInput; this.defErrWriter = source.defErrWriter; this.defOutWriter = source.defOutWriter; @@ -398,29 +381,20 @@ public List getClassLoaders() { return Collections.unmodifiableList(classLoaders); } - @Override + @Override public ModuleEnvironment __getRootScope() { return rootScope; } - @Override - public OutputStream getStdOut() { - return curStdout == null ? defStdout : curStdout; - } - @Override public PrintWriter getOutPrinter() { - return curOutWriter == null ? defOutWriter : curOutWriter; - } - - @Override - public PrintWriter getErrorPrinter() { - return curErrWriter == null ? defErrWriter : curErrWriter; + return curOutWriter == null ? defOutWriter : curOutWriter; } + @Override - public InputStream getInput() { + public Reader getInput() { return curInput == null ? defInput : curInput; } @@ -469,9 +443,9 @@ public boolean isInterrupted() { return interrupt; } - @Override - public OutputStream getStdErr() { - return curStderr == null ? defStderr : curStderr; + @Override + public PrintWriter getErrorPrinter() { + return curErrWriter == null ? defErrWriter : curErrWriter; } public void setTestResultListener(ITestResultListener l) { @@ -831,7 +805,8 @@ public ParserGenerator getParserGenerator() { synchronized (self) { if (parserGenerator == null) { - parserGenerator = new ParserGenerator(getMonitor(), (monitor instanceof TerminalProgressBarMonitor) ? (OutputStream) getMonitor() : getStdErr(), classLoaders, getValueFactory(), config); + + parserGenerator = new ParserGenerator(getMonitor(), (monitor instanceof PrintWriter) ? (PrintWriter)monitor : getErrorPrinter(), classLoaders, getValueFactory(), config); } } } @@ -1513,7 +1488,7 @@ public boolean runTests(IRascalMonitor monitor) { IRascalMonitor old = setMonitor(monitor); try { final boolean[] allOk = new boolean[] { true }; - final ITestResultListener l = testReporter != null ? testReporter : new DefaultTestResultListener(getOutPrinter()); + final ITestResultListener l = testReporter != null ? testReporter : new DefaultTestResultListener(getOutPrinter(), getErrorPrinter()); new TestEvaluator(this, new ITestResultListener() { @@ -1578,17 +1553,13 @@ public IRascalMonitor getMonitor() { return new NullRascalMonitor(); } - public void overrideDefaultWriters(InputStream newInput, OutputStream newStdOut, OutputStream newStdErr) { - this.curStdout = newStdOut; - this.curStderr = newStdErr; + public void overrideDefaultWriters(Reader newInput, PrintWriter newStdOut, PrintWriter newStdErr) { this.curInput = newInput; - this.curErrWriter = wrapWriter(newStdErr, true); - this.curOutWriter = wrapWriter(newStdOut, false); + this.curErrWriter = newStdErr; + this.curOutWriter = newStdOut; } public void revertToDefaultWriters() { - this.curStderr = null; - this.curStdout = null; this.curInput = null; if (curOutWriter != null) { curOutWriter.flush(); @@ -1707,13 +1678,13 @@ public TraversalEvaluator __popTraversalEvaluator() { } @Override - public Collection completePartialIdentifier(String qualifier, String partialIdentifier) { + public Map completePartialIdentifier(String qualifier, String partialIdentifier) { if (partialIdentifier.startsWith("\\")) { partialIdentifier = partialIdentifier.substring(1); } String partialModuleName = qualifier + "::" + partialIdentifier; - SortedSet result = new TreeSet<>(new Comparator() { + SortedMap result = new TreeMap<>(new Comparator() { @Override public int compare(String a, String b) { if (a.charAt(0) == '\\') { @@ -1735,7 +1706,7 @@ public int compare(String a, String b) { return result; } - private void addCompletionsForModule(String qualifier, String partialIdentifier, String partialModuleName, SortedSet result, ModuleEnvironment env, boolean skipPrivate) { + private void addCompletionsForModule(String qualifier, String partialIdentifier, String partialModuleName, SortedMap result, ModuleEnvironment env, boolean skipPrivate) { for (Pair> p : env.getFunctions()) { for (AbstractFunction f : p.getSecond()) { String module = ((ModuleEnvironment)f.getEnv()).getName(); @@ -1745,7 +1716,7 @@ private void addCompletionsForModule(String qualifier, String partialIdentifier, } if (module.startsWith(qualifier)) { - addIt(result, p.getFirst(), qualifier.isEmpty() ? "" : module, module.startsWith(partialModuleName) ? "" : partialIdentifier); + addCandidate(result, "function", p.getFirst(), qualifier.isEmpty() ? "" : module, module.startsWith(partialModuleName) ? "" : partialIdentifier); } } } @@ -1755,38 +1726,38 @@ private void addCompletionsForModule(String qualifier, String partialIdentifier, if (skipPrivate && env.isNamePrivate(entry.getKey())) { continue; } - addIt(result, entry.getKey(), qualifier, partialIdentifier); + addCandidate(result, "variable", entry.getKey(), qualifier, partialIdentifier); } for (Type t: env.getAbstractDatatypes()) { if (inQualifiedModule) { - addIt(result, t.getName(), qualifier, partialIdentifier); + addCandidate(result, "ADT", t.getName(), qualifier, partialIdentifier); } } for (Type t: env.getAliases()) { - addIt(result, t.getName(), qualifier, partialIdentifier); + addCandidate(result, "alias", t.getName(), qualifier, partialIdentifier); } } if (qualifier.isEmpty()) { Map> annos = env.getAnnotations(); for (Type t: annos.keySet()) { for (String k: annos.get(t).keySet()) { - addIt(result, k, "", partialIdentifier); + addCandidate(result, "annotation", k, "", partialIdentifier); } } } } - private static void addIt(SortedSet result, String v, String qualifier, String originalTerm) { - if (v.startsWith(originalTerm) && !v.equals(originalTerm)) { - if (v.contains("-")) { - v = "\\" + v; + private static void addCandidate(SortedMap result, String category, String name, String qualifier, String originalTerm) { + if (name.startsWith(originalTerm) && !name.equals(originalTerm)) { + if (name.contains("-")) { + name = "\\" + name; } - if (!qualifier.isEmpty() && !v.startsWith(qualifier)) { - v = qualifier + "::" + v; + if (!qualifier.isEmpty() && !name.startsWith(qualifier)) { + name = qualifier + "::" + name; } - result.add(v); + result.put(name, category); } } @@ -1983,4 +1954,10 @@ public void showMessage(IConstructor message) { } } } + + + public void overwritePrintWriter(PrintWriter outWriter, PrintWriter errWriter) { + this.curOutWriter = outWriter; + this.curErrWriter = errWriter; + } } diff --git a/src/org/rascalmpl/interpreter/IEvaluatorContext.java b/src/org/rascalmpl/interpreter/IEvaluatorContext.java index 49db817c31b..4cb527af3f3 100644 --- a/src/org/rascalmpl/interpreter/IEvaluatorContext.java +++ b/src/org/rascalmpl/interpreter/IEvaluatorContext.java @@ -17,10 +17,10 @@ *******************************************************************************/ package org.rascalmpl.interpreter; -import java.io.InputStream; -import java.io.OutputStream; import java.io.PrintWriter; +import java.io.Reader; import java.util.Collection; +import java.util.Map; import java.util.Stack; import org.rascalmpl.ast.AbstractAST; @@ -43,11 +43,8 @@ public interface IEvaluatorContext extends IRascalMonitor { /** for standard IO */ public PrintWriter getOutPrinter(); public PrintWriter getErrorPrinter(); - - public OutputStream getStdOut(); - public OutputStream getStdErr(); - public InputStream getInput(); + public Reader getInput(); /** for "internal use" */ public IEvaluator> getEvaluator(); @@ -77,5 +74,7 @@ public interface IEvaluatorContext extends IRascalMonitor { public Stack getAccumulators(); - public Collection completePartialIdentifier(String qualifier, String partialIdentifier); + /** Given a (possibly empty) qualifier and a partial identifier, look in the current root environment if there are names defined that could match the partial names + * @return identifiers and their category (variable, function, etc) */ + public Map completePartialIdentifier(String qualifier, String partialIdentifier); } diff --git a/src/org/rascalmpl/interpreter/result/JavaMethod.java b/src/org/rascalmpl/interpreter/result/JavaMethod.java index 10530bda752..d10e1833f30 100644 --- a/src/org/rascalmpl/interpreter/result/JavaMethod.java +++ b/src/org/rascalmpl/interpreter/result/JavaMethod.java @@ -91,7 +91,7 @@ private JavaMethod(IEvaluator> eval, Type staticType, Type dynami super(func, eval, staticType, dynamicType, getFormals(func), Names.name(func.getSignature().getName()), isDefault, isTest, varargs, env); this.javaBridge = javaBridge; this.hasReflectiveAccess = hasReflectiveAccess(func); - this.instance = javaBridge.getJavaClassInstance(func, eval.getMonitor(), env.getStore(), eval.getOutPrinter(), eval.getErrorPrinter(), eval.getStdOut(), eval.getStdErr(), eval.getInput(), eval); + this.instance = javaBridge.getJavaClassInstance(func, eval.getMonitor(), env.getStore(), eval.getOutPrinter(), eval.getErrorPrinter(), eval.getInput(), eval); this.method = javaBridge.lookupJavaMethod(eval, func, env, hasReflectiveAccess); } diff --git a/src/org/rascalmpl/interpreter/utils/JavaBridge.java b/src/org/rascalmpl/interpreter/utils/JavaBridge.java index 71362ef5f39..ef045e4538b 100644 --- a/src/org/rascalmpl/interpreter/utils/JavaBridge.java +++ b/src/org/rascalmpl/interpreter/utils/JavaBridge.java @@ -19,6 +19,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; +import java.io.Reader; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -376,14 +377,11 @@ public Class visitFunction(Type type) throws RuntimeException { } } - public synchronized Object getJavaClassInstance(FunctionDeclaration func, IRascalMonitor monitor, TypeStore store, PrintWriter out, PrintWriter err, OutputStream rawOut, OutputStream rawErr, InputStream in, IEvaluatorContext ctx) { + public synchronized Object getJavaClassInstance(FunctionDeclaration func, IRascalMonitor monitor, TypeStore store, PrintWriter out, PrintWriter err, Reader in, IEvaluatorContext ctx) { String className = getClassName(func); PrintWriter[] outputs = new PrintWriter[] { out, err }; int writers = 0; - - OutputStream[] rawOutputs = new OutputStream[] { rawOut, rawErr }; - int rawWriters = 0; try { for(ClassLoader loader : loaders){ @@ -426,10 +424,7 @@ else if (formals[i].isAssignableFrom(TypeFactory.class)) { else if (formals[i].isAssignableFrom(PrintWriter.class)) { args[i] = outputs[writers++ % 2]; } - else if (formals[i].isAssignableFrom(OutputStream.class)) { - args[i] = rawOutputs[rawWriters++ %2]; - } - else if (formals[i].isAssignableFrom(InputStream.class)) { + else if (formals[i].isAssignableFrom(Reader.class)) { args[i] = in; } else if (formals[i].isAssignableFrom(IRascalMonitor.class)) { @@ -446,7 +441,7 @@ else if (formals[i].isAssignableFrom(IDEServices.class)) { args[i] = (IDEServices) monitor; } else { - args[i] = new BasicIDEServices(err, monitor); + args[i] = new BasicIDEServices(err, monitor, null); } } else if (formals[i].isAssignableFrom(IResourceLocationProvider.class)) { diff --git a/src/org/rascalmpl/library/Prelude.java b/src/org/rascalmpl/library/Prelude.java index 75f95c58cd6..2224a6c39ac 100644 --- a/src/org/rascalmpl/library/Prelude.java +++ b/src/org/rascalmpl/library/Prelude.java @@ -78,7 +78,7 @@ import org.rascalmpl.exceptions.Throw; import org.rascalmpl.ideservices.IDEServices; import org.rascalmpl.interpreter.utils.IResourceLocationProvider; -import org.rascalmpl.repl.LimitedLineWriter; +import org.rascalmpl.repl.streams.LimitedLineWriter; import org.rascalmpl.types.TypeReifier; import org.rascalmpl.unicode.UnicodeOffsetLengthReader; import org.rascalmpl.unicode.UnicodeOutputStreamWriter; diff --git a/src/org/rascalmpl/library/lang/json/internal/JsonValueWriter.java b/src/org/rascalmpl/library/lang/json/internal/JsonValueWriter.java index 92e7e230b17..02c91578e0d 100644 --- a/src/org/rascalmpl/library/lang/json/internal/JsonValueWriter.java +++ b/src/org/rascalmpl/library/lang/json/internal/JsonValueWriter.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.Map.Entry; +import org.checkerframework.checker.nullness.qual.Nullable; import org.rascalmpl.exceptions.RuntimeExceptionFactory; import org.rascalmpl.exceptions.Throw; import org.rascalmpl.library.Prelude; @@ -52,7 +53,6 @@ import io.usethesource.vallang.ITuple; import io.usethesource.vallang.IValue; import io.usethesource.vallang.visitors.IValueVisitor; -import jline.internal.Nullable; /** * This class streams an IValue directly to an JSON stream. Useful to communicate IValues to diff --git a/src/org/rascalmpl/library/util/Eval.java b/src/org/rascalmpl/library/util/Eval.java index 6da394f8621..dd2835f8f46 100644 --- a/src/org/rascalmpl/library/util/Eval.java +++ b/src/org/rascalmpl/library/util/Eval.java @@ -16,8 +16,8 @@ import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Reader; import java.net.URISyntaxException; import java.nio.file.Paths; import java.util.HashMap; @@ -80,12 +80,12 @@ public class Eval { private final Type execConstructor; /* the following four fields are inherited by the configuration of nested evaluators */ - private final OutputStream stderr; - private final OutputStream stdout; - private final InputStream input; + private final PrintWriter stderr; + private final PrintWriter stdout; + private final Reader input; private final IDEServices services; - public Eval(IRascalValueFactory values, OutputStream out, OutputStream err, InputStream in, ClassLoader loader, IDEServices services, TypeStore ts) { + public Eval(IRascalValueFactory values, PrintWriter out, PrintWriter err, Reader in, ClassLoader loader, IDEServices services, TypeStore ts) { super(); this.values = values; this.tr = new TypeReifier(values); @@ -219,7 +219,7 @@ private static class RascalRuntime { private final Evaluator eval; private int duration = -1; - public RascalRuntime(PathConfig pcfg, InputStream input, OutputStream stderr, OutputStream stdout, IDEServices services) throws IOException, URISyntaxException{ + public RascalRuntime(PathConfig pcfg, Reader input, PrintWriter stderr, PrintWriter stdout, IDEServices services) throws IOException, URISyntaxException{ GlobalEnvironment heap = new GlobalEnvironment(); ModuleEnvironment root = heap.addModule(new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap)); IValueFactory vf = ValueFactoryFactory.getValueFactory(); diff --git a/src/org/rascalmpl/library/util/IDEServicesLibrary.java b/src/org/rascalmpl/library/util/IDEServicesLibrary.java index 5476c5d2509..3798e7d0e0a 100644 --- a/src/org/rascalmpl/library/util/IDEServicesLibrary.java +++ b/src/org/rascalmpl/library/util/IDEServicesLibrary.java @@ -16,8 +16,8 @@ import org.rascalmpl.exceptions.RuntimeExceptionFactory; import org.rascalmpl.ideservices.IDEServices; -import org.rascalmpl.repl.REPLContentServer; -import org.rascalmpl.repl.REPLContentServerManager; +import org.rascalmpl.repl.http.REPLContentServer; +import org.rascalmpl.repl.http.REPLContentServerManager; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.values.functions.IFunction; diff --git a/src/org/rascalmpl/library/util/REPL.rsc b/src/org/rascalmpl/library/util/REPL.rsc index 54eaa45bf9b..b429c376c1f 100644 --- a/src/org/rascalmpl/library/util/REPL.rsc +++ b/src/org/rascalmpl/library/util/REPL.rsc @@ -2,8 +2,7 @@ module util::REPL extend Content; -alias Completion - = tuple[int offset, list[str] suggestions]; +alias Completion = map[str completion, str group]; data REPL = repl( @@ -13,13 +12,13 @@ data REPL str quit = "", loc history = |home:///.term-repl-history|, Content (str command) handler = echo, - Completion(str line, int cursor) completor = noSuggestions, + Completion(str line, str word) completor = noSuggestions, str () stacktrace = str () { return ""; } ); private Content echo(str line) = plainText(line); -private Completion noSuggestions(str _, int _) = <0, []>; +private Completion noSuggestions(str _, str _) = (); alias Terminal = tuple[void() run, void(str) send]; @@ -36,7 +35,7 @@ java Terminal newREPL(REPL repl, str quit = repl.quit, loc history = repl.history, Content (str ) handler = repl.handler, - Completion(str , int) completor = repl.completor, + Completion(str , str) completor = repl.completor, str () stacktrace = repl.stacktrace); void startREPL(REPL repl, @@ -48,7 +47,7 @@ void startREPL(REPL repl, str quit = repl.quit, loc history = repl.history, Content (str ) handler = repl.handler, - Completion(str , int) completor = repl.completor, + Completion(str , str) completor = repl.completor, str () stacktrace = repl.stacktrace) { Terminal tm = newREPL(repl, title=title, welcome=welcome, diff --git a/src/org/rascalmpl/library/util/Reflective.java b/src/org/rascalmpl/library/util/Reflective.java index 44bb23ce3b1..dba61d268b8 100644 --- a/src/org/rascalmpl/library/util/Reflective.java +++ b/src/org/rascalmpl/library/util/Reflective.java @@ -43,8 +43,8 @@ import org.rascalmpl.parser.gtd.result.out.DefaultNodeFlattener; import org.rascalmpl.parser.uptr.UPTRNodeFactory; import org.rascalmpl.parser.uptr.action.NoActionExecutor; -import org.rascalmpl.repl.LimitedLineWriter; -import org.rascalmpl.repl.LimitedWriter; +import org.rascalmpl.repl.streams.LimitedLineWriter; +import org.rascalmpl.repl.streams.LimitedWriter; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.values.RascalValueFactory; import org.rascalmpl.values.ValueFactoryFactory; @@ -99,21 +99,23 @@ public IConstructor getProjectPathConfig(ISourceLocation projectRoot, IConstruct } } - IEvaluator getDefaultEvaluator(OutputStream stdout, OutputStream stderr) { + IEvaluator getDefaultEvaluator(PrintWriter stdout, PrintWriter stderr) { GlobalEnvironment heap = new GlobalEnvironment(); ModuleEnvironment root = heap.addModule(new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap)); IValueFactory vf = ValueFactoryFactory.getValueFactory(); - Evaluator evaluator = new Evaluator(vf, System.in, stderr, stdout, root, heap, monitor); + Evaluator evaluator = new Evaluator(vf, Reader.nullReader(), stderr, stdout, root, heap, monitor); evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); return evaluator; } public IList evalCommands(IList commands, ISourceLocation loc) { - OutputStream out = new ByteArrayOutputStream(); - OutputStream err = new ByteArrayOutputStream(); + StringWriter out = new StringWriter(); + StringWriter err = new StringWriter(); + PrintWriter outStream = new PrintWriter(out); + PrintWriter errStream = new PrintWriter(err); IListWriter result = values.listWriter(); - IEvaluator evaluator = getDefaultEvaluator(out, err); + IEvaluator evaluator = getDefaultEvaluator(outStream, errStream); int outOffset = 0; int errOffset = 0; @@ -125,11 +127,16 @@ public IList evalCommands(IList commands, ISourceLocation loc) { x = evaluator.eval(evaluator.getMonitor(), ((IString)v).getValue(), loc); } catch (Throwable e) { + errStream.flush(); errOut = err.toString().substring(errOffset); errOffset += errOut.length(); errOut += e.getMessage(); exc = true; } + finally { + outStream.flush(); + errStream.flush(); + } String output = out.toString().substring(outOffset); outOffset += output.length(); if (!exc) { diff --git a/src/org/rascalmpl/library/util/TermREPL.java b/src/org/rascalmpl/library/util/TermREPL.java index 335d1ed1a3e..35baa3e45e4 100644 --- a/src/org/rascalmpl/library/util/TermREPL.java +++ b/src/org/rascalmpl/library/util/TermREPL.java @@ -1,18 +1,16 @@ package org.rascalmpl.library.util; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Reader; import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; +import java.net.URI; +import java.nio.file.Path; +import java.util.HashMap; import java.util.Map; import java.util.function.Function; +import org.jline.reader.EndOfFileException; import org.rascalmpl.exceptions.RuntimeExceptionFactory; import org.rascalmpl.ideservices.IDEServices; import org.rascalmpl.interpreter.Evaluator; @@ -20,11 +18,20 @@ import org.rascalmpl.interpreter.result.AbstractFunction; import org.rascalmpl.library.lang.json.internal.JsonValueWriter; import org.rascalmpl.repl.BaseREPL; -import org.rascalmpl.repl.CompletionResult; -import org.rascalmpl.repl.ILanguageProtocol; -import org.rascalmpl.repl.REPLContentServer; -import org.rascalmpl.repl.REPLContentServerManager; +import org.rascalmpl.repl.http.REPLContentServer; +import org.rascalmpl.repl.http.REPLContentServerManager; +import org.rascalmpl.repl.output.ICommandOutput; +import org.rascalmpl.repl.output.IErrorCommandOutput; +import org.rascalmpl.repl.output.INotebookOutput; +import org.rascalmpl.repl.output.IOutputPrinter; +import org.rascalmpl.repl.output.ISourceLocationCommandOutput; +import org.rascalmpl.repl.output.IWebContentOutput; +import org.rascalmpl.repl.output.MimeTypes; +import org.rascalmpl.repl.output.impl.AsciiStringOutputPrinter; +import org.rascalmpl.repl.parametric.ILanguageProtocol; +import org.rascalmpl.repl.parametric.ParametricReplService; import org.rascalmpl.uri.URIResolverRegistry; +import org.rascalmpl.uri.URIUtil; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.values.functions.IFunction; @@ -32,8 +39,7 @@ import io.usethesource.vallang.IBool; import io.usethesource.vallang.IConstructor; -import io.usethesource.vallang.IInteger; -import io.usethesource.vallang.IList; +import io.usethesource.vallang.IMap; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.IString; import io.usethesource.vallang.ITuple; @@ -42,29 +48,45 @@ import io.usethesource.vallang.IWithKeywordParameters; import io.usethesource.vallang.type.Type; import io.usethesource.vallang.type.TypeFactory; -import jline.TerminalFactory; public class TermREPL { private final IRascalValueFactory vf; + private final IDEServices service; + private final PrintWriter err; private ILanguageProtocol lang; - private final OutputStream out; - private final OutputStream err; - private final InputStream in; - public TermREPL(IRascalValueFactory vf, OutputStream out, OutputStream err, InputStream in) { + public TermREPL(IRascalValueFactory vf, IDEServices service, PrintWriter _ignoredOut, PrintWriter err) { this.vf = vf; - this.out = out; + this.service = service; this.err = err; - this.in = in; + } + + private Path resolveHistoryFile(ISourceLocation historyFile) { + try { + ISourceLocation result = URIResolverRegistry.getInstance().logicalToPhysical(historyFile); + if (result == null || !result.getScheme().equals("file")) { + err.println("Cannot resolve history file to file on disk"); + return null; + } + return Path.of(result.getPath()); + } + catch (IOException e) { + return null; + } } public ITuple newREPL(IConstructor repl, IString title, IString welcome, IString prompt, IString quit, ISourceLocation history, IFunction handler, IFunction completor, IFunction stacktrace, IEvaluatorContext eval) { - lang = new TheREPL(vf, title, welcome, prompt, quit, history, handler, completor, stacktrace, in, err, out); + var term = service.activeTerminal(); + if (term == null) { + throw RuntimeExceptionFactory.io("No terminal found in IDE service, we cannot allocate a REPL"); + } + lang = new TheREPL(vf, title, welcome, prompt, quit, history, handler, completor, stacktrace); + BaseREPL baseRepl; try { - baseRepl = new BaseREPL(lang, null, in, err, out, true, true, history, TerminalFactory.get(), null); + baseRepl = new BaseREPL(new ParametricReplService(lang, service, resolveHistoryFile(history)), term); } catch (Throwable e) { throw RuntimeExceptionFactory.io(e.getMessage()); @@ -94,10 +116,10 @@ public ITuple newREPL(IConstructor repl, IString title, IString welcome, IString public static class TheREPL implements ILanguageProtocol { private final REPLContentServerManager contentManager = new REPLContentServerManager(); private final TypeFactory tf = TypeFactory.getInstance(); - private OutputStream stdout; - private OutputStream stderr; - private InputStream input; - private String currentPrompt; + private PrintWriter stdout; + private PrintWriter stderr; + private Reader input; + private final String currentPrompt; private String quit; private final AbstractFunction handler; private final AbstractFunction completor; @@ -105,12 +127,8 @@ public static class TheREPL implements ILanguageProtocol { private final AbstractFunction stacktrace; public TheREPL(IValueFactory vf, IString title, IString welcome, IString prompt, IString quit, ISourceLocation history, - IFunction handler, IFunction completor, IValue stacktrace, InputStream input, OutputStream stderr, OutputStream stdout) { + IFunction handler, IFunction completor, IValue stacktrace) { this.vf = vf; - this.input = input; - this.stderr = stderr; - this.stdout = stdout; - // TODO: these casts mean that TheRepl only works with functions produced by the // interpreter for now. The reason is that the REPL needs access to environment configuration // parameters of these functions such as stdout, stdin, etc. @@ -124,100 +142,137 @@ public TheREPL(IValueFactory vf, IString title, IString welcome, IString prompt, } @Override - public void cancelRunningCommandRequested() { - handler.getEval().interrupt(); - handler.getEval().__setInterrupt(false); - } - - @Override - public void terminateRequested() { - handler.getEval().interrupt(); + public void initialize(Reader input, PrintWriter stdout, PrintWriter stderr, IDEServices services) { + this.input = input; + this.stdout = stdout; + this.stderr = stdout; } @Override - public void stop() { + public void cancelRunningCommandRequested() { handler.getEval().interrupt(); } - @Override - public void stackTraceRequested() { - stacktrace.call(new Type[0], new IValue[0], null); - } - - @Override - public void initialize(InputStream input, OutputStream stdout, OutputStream stderr, IDEServices services) { - this.stdout = stdout; - this.stderr = stderr; - this.input = input; - } - @Override public String getPrompt() { return currentPrompt; } @Override - public void handleInput(String line, Map output, Map metadata) throws InterruptedException { - + public ICommandOutput handleInput(String line) throws InterruptedException { if (line.trim().equals(quit)) { - throw new InterruptedException(quit); + throw new EndOfFileException(); } - else { - try { - handler.getEval().__setInterrupt(false); - IConstructor content = (IConstructor) call(handler, new Type[] { tf.stringType() }, new IValue[] { vf.string(line) }); + try { + handler.getEval().__setInterrupt(false); + IConstructor content = (IConstructor) call(handler, new Type[] { tf.stringType() }, new IValue[] { vf.string(line) }); - if (content.has("id")) { - handleInteractiveContent(output, metadata, content); - } - else { - IConstructor response = (IConstructor) content.get("response"); - switch (response.getName()) { - case "response": - handlePlainTextResponse(output, response); - break; - case "fileResponse": - handleFileResponse(output, response); - break; - case "jsonResponse": - handleJSONResponse(output, response); - } + if (content.has("id")) { + return handleInteractiveContent(content); + } + else { + IConstructor response = (IConstructor) content.get("response"); + switch (response.getName()) { + case "response": + return handlePlainTextResponse(response); + case "fileResponse": + return handleFileResponse(response); + case "jsonResponse": + return handleJSONResponse(response); + default: + return errorResponse("Unexpected constructor: " + response.getName()); } } - catch (IOException e) { - output.put("text/plain", new ByteArrayInputStream(e.getMessage().getBytes())); + } + catch (IOException e) { + return errorResponse(e.getMessage()); + } + catch (Throwable e) { + return errorResponse(e.getMessage()); + } + } + + private ICommandOutput errorResponse(String message) { + return new IErrorCommandOutput() { + @Override + public ICommandOutput getError() { + return () -> new AsciiStringOutputPrinter(message); } - catch (Throwable e) { - output.put("text/plain", new ByteArrayInputStream(e.getMessage() != null ? e.getMessage().getBytes() : e.getClass().getName().getBytes())); + @Override + public IOutputPrinter asPlain() { + return new AsciiStringOutputPrinter(message); } - } + }; } + - private void handleInteractiveContent(Map output, Map metadata, - IConstructor content) throws IOException, UnsupportedEncodingException { + private ICommandOutput handleInteractiveContent(IConstructor content) throws IOException, UnsupportedEncodingException { String id = ((IString) content.get("id")).getValue(); Function callback = liftProviderFunction(content.get("callback")); REPLContentServer server = contentManager.addServer(id, callback); - String URL = "http://localhost:" + server.getListeningPort(); - - produceHTMLResponse(id, URL, output, metadata); + return produceHTMLResponse(id, URIUtil.assumeCorrect("http", "localhost:" + server.getListeningPort(), "")); } + + abstract class NotebookWebContentOutput implements INotebookOutput, IWebContentOutput {} - private void produceHTMLResponse(String id, String URL, Map output, Map metadata) throws UnsupportedEncodingException{ - String html; - if (metadata.containsKey("origin") && metadata.get("origin").equals("notebook")) - html = " \n
\n
"; - else - html = ""; - - metadata.put("url", URL); + private ICommandOutput produceHTMLResponse(String id, URI URL) throws UnsupportedEncodingException{ + return new NotebookWebContentOutput() { + @Override + public IOutputPrinter asNotebook() { + return new IOutputPrinter() { + @Override + public void write(PrintWriter target, boolean unicodeSupported) { + target.println("