package org.rascalmpl.eclipse.repl; import static org.rascalmpl.debug.AbstractInterpreterEventTrigger.newInterpreterEventTrigger; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.tm.internal.terminal.emulator.VT100Emulator; import org.eclipse.tm.internal.terminal.emulator.VT100TerminalControl; import org.eclipse.tm.internal.terminal.provisional.api.ISettingsStore; import org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl; import org.eclipse.tm.internal.terminal.provisional.api.TerminalState; import org.rascalmpl.debug.AbstractInterpreterEventTrigger; import org.rascalmpl.debug.DebugHandler; import org.rascalmpl.debug.IRascalEventListener; import org.rascalmpl.debug.IRascalRuntimeInspection; import org.rascalmpl.eclipse.Activator; import org.rascalmpl.eclipse.debug.core.model.RascalDebugTarget; import org.rascalmpl.eclipse.nature.ModuleReloader; import org.rascalmpl.eclipse.nature.ProjectEvaluatorFactory; import org.rascalmpl.eclipse.nature.WarningsToPrintWriter; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.result.ICallableValue; import org.rascalmpl.interpreter.result.IRascalResult; import org.rascalmpl.interpreter.result.Result; import org.rascalmpl.repl.BaseRascalREPL; import org.rascalmpl.repl.RascalInterpreterREPL; import io.usethesource.vallang.IValue; import jline.Terminal; @SuppressWarnings("restriction") public class RascalTerminalConnector extends SizedTerminalConnector { private BaseRascalREPL shell; private REPLPipedInputStream stdIn; private OutputStream stdInUI; protected String project; protected String module; protected String mode; private ILaunch launch; private WarningsToPrintWriter warnings; private ModuleReloader reloader; private int terminalHeight = 24; private int terminalWidth = 80; @Override public OutputStream getTerminalToRemoteStream() { return stdInUI; } @Override public boolean isLocalEcho() { return false; } @Override public void load(ISettingsStore store) { this.project = store.get("project"); this.module = store.get("module"); this.mode = store.get("mode"); } protected File getHistoryFile() throws IOException { File home = new File(System.getProperty("user.home")); File rascal = new File(home, ".rascal"); if (!rascal.exists()) { rascal.mkdirs(); } File historyFile = new File(rascal, ".repl-history-rascal"); if (!historyFile.exists()) { historyFile.createNewFile(); } return historyFile; } @Override public void connect(ITerminalControl control) { super.connect(control); Terminal tm = configure(control); stdIn = new REPLPipedInputStream(); stdInUI = new REPLPipedOutputStream(stdIn); control.setState(TerminalState.CONNECTING); RascalTerminalRegistry.getInstance().register(this); Thread t = new Thread() { public void run() { try { shell = constructREPL(control, stdIn, stdInUI, tm); control.setState(TerminalState.CONNECTED); shell.run(); } catch (IOException | URISyntaxException e) { Activator.log("terminal not connected", e); } finally { control.setState(TerminalState.CLOSED); if (reloader != null) { reloader.destroy(); } try { if (debug()) { launch.getDebugTarget().terminate(); launch.removeDebugTarget(launch.getDebugTarget()); } ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); launchManager.removeLaunch(launch); } catch (DebugException e) { Activator.log("problem disconnecting from debugger", e); } } } }; t.setName("Rascal REPL Runner"); t.start(); } private Terminal configure(ITerminalControl control) { VT100TerminalControl vtControl = (VT100TerminalControl) control; Terminal tm = new TMTerminalTerminal(vtControl, this); vtControl.setVT100LineWrapping(false); VT100Emulator text = vtControl.getTerminalText(); text.setCrAfterNewLine(true); vtControl.setConnectOnEnterIfClosed(false); vtControl.setBufferLineLimit(10_000); try { control.setEncoding(StandardCharsets.UTF_8.name()); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF8 not available???", e); } vtControl.addMouseListener(new RascalLinkMouseListener()); return tm; } @Override protected void doDisconnect() { try { super.doDisconnect(); if (shell != null) { try { stdIn.close(); } catch (IOException e) { e.printStackTrace(); } shell.stop(); shell = null; } } finally { RascalTerminalRegistry.getInstance().unregister(this); } } public void setFocus() { ((VT100TerminalControl)fControl).setFocus(); RascalTerminalRegistry.getInstance().setActive(this); } @Override public String getSettingsSummary() { return project != null ? "Rascal Terminal [project: " + project + "]" : "Rascal Terminal [no project]"; } public String getProject() { return project; } public void queueCommand(String cmd) { shell.queueCommand(cmd); } private boolean debug() { return "debug".equals(mode); } @Override public void setTerminalSize(int newWidth, int newHeight) { super.setTerminalSize(newWidth, newHeight); terminalHeight = newHeight; terminalWidth = newWidth; } public int getHeight() { return terminalHeight; } public int getWidth() { return terminalWidth; } protected BaseRascalREPL constructREPL(ITerminalControl control, REPLPipedInputStream stdIn, OutputStream stdInUI, Terminal tm) throws IOException, URISyntaxException { return new RascalInterpreterREPL(stdIn, control.getRemoteToTerminalOutputStream(), true, true, getHistoryFile(), tm) { private AbstractInterpreterEventTrigger eventTrigger; private DebugHandler debugHandler; @Override protected Evaluator constructEvaluator(Writer stdout, Writer stderr) { IProject ipr = project != null ? ResourcesPlugin.getWorkspace().getRoot().getProject(project) : null; if (ipr != null && !ipr.isOpen()) { ipr = null; } Evaluator eval = ProjectEvaluatorFactory.getInstance().createProjectEvaluator(ipr, stderr, stdout); // TODO: this is a workaround to get access to a launch, but we'd rather // just get it from the terminal's properties launch = RascalTerminalRegistry.getInstance().getLaunch(); warnings = new WarningsToPrintWriter(new PrintWriter(stderr)); reloader = new ModuleReloader(ipr, eval, warnings); if (debug()) { initializeRascalDebugMode(eval); connectToEclipseDebugAPI(eval); eventTrigger.fireSuspendByClientRequestEvent(); } if (module != null) { eval.doImport(null, module); Result<IValue> mainFunc = eval.getCurrentEnvt().getFrameVariable("main"); // do not move this queue before the mainFunc initializer super.queueCommand("import " + module + ";"); if (mainFunc != null && mainFunc instanceof ICallableValue) { super.queueCommand("main()"); } } return eval; } @Override protected IRascalResult evalStatement(String statement, String lastLine) throws InterruptedException { try { if (debug()) { synchronized(eval) { eventTrigger.fireResumeByClientRequestEvent(); } } Job job = new Job("Reloading modules") { @Override protected IStatus run(IProgressMonitor monitor) { reloader.updateModules(monitor, warnings, Collections.emptySet()); return Status.OK_STATUS; } }; job.schedule(); job.join(); return super.evalStatement(statement, lastLine); } finally { if (debug() && !":quit".equals(statement.trim())) { synchronized(eval) { eventTrigger.fireSuspendByClientRequestEvent(); } } } } private void connectToEclipseDebugAPI(IRascalRuntimeInspection eval) { try { RascalDebugTarget debugTarget = new RascalDebugTarget(eval, launch, eventTrigger, debugHandler); launch.addDebugTarget(debugTarget); debugTarget.breakpointManagerEnablementChanged(true); } catch (CoreException e) { Activator.log("could not connect to debugger", e); // otherwise no harm done, can continue } } private void initializeRascalDebugMode(Evaluator eval) { eventTrigger = newInterpreterEventTrigger(this, new CopyOnWriteArrayList<IRascalEventListener>()); debugHandler = new DebugHandler(); debugHandler.setEventTrigger(eventTrigger); debugHandler.setTerminateAction(new Runnable() { @Override public void run() { doDisconnect(); } }); eval.addSuspendTriggerListener(debugHandler); } @Override public void queueCommand(String command) { super.queueCommand(command); try { // let's flush it stdInUI.write(new byte[]{(byte)ctrl('K'),(byte)ctrl('U'),(byte)'\n'}); } catch (IOException e) { } } }; } }