DomTermBackend.java 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. package kawa;
  2. import org.domterm.*;
  3. import org.domterm.util.DomTermErrorWriter;
  4. import org.domterm.util.StyleSheets;
  5. import org.domterm.util.Utf8WriterOutputStream;
  6. import org.domterm.DomHttpServer;
  7. import gnu.expr.*;
  8. import gnu.mapping.*;
  9. import gnu.kawa.io.*;
  10. import java.awt.Desktop;
  11. import java.io.*;
  12. import java.net.UnknownHostException;
  13. import java.net.URI;
  14. /** An implementation of DomTerm's Backend that runs a Kawa REPL. */
  15. public class DomTermBackend extends Backend implements Runnable {
  16. Thread thread;
  17. public static class Server extends DomHttpServer {
  18. private static Server instance = null;
  19. Backend pendingBackend;
  20. public Server(int port) throws UnknownHostException, IOException {
  21. super(port, new String[0]);
  22. }
  23. @Override
  24. protected Backend createBackend() {
  25. if (pendingBackend != null) {
  26. Backend b = pendingBackend;
  27. pendingBackend = null;
  28. return b;
  29. }
  30. return new DomTermBackend();
  31. }
  32. public synchronized static Server getInstance() throws IOException {
  33. if (instance == null) {
  34. try {
  35. instance = new Server(0);
  36. instance.pendingBackend = new DomTermBackend();
  37. } catch (UnknownHostException ex) {
  38. throw new RuntimeException(ex);
  39. }
  40. instance.start();
  41. }
  42. return instance;
  43. }
  44. public static int getInstancePort() throws IOException {
  45. return getInstance().getPort();
  46. }
  47. }
  48. public static String startDomTermConsole(String command) throws Throwable {
  49. int htport = 0;
  50. if (command.startsWith("serve=")) {
  51. String portArg = command.substring(6);
  52. try {
  53. htport = Integer.parseInt(portArg);
  54. } catch (NumberFormatException ex) {
  55. return "bad port specifier in -w"+command+" option";
  56. }
  57. }
  58. if ("google-chrome".equals(command)
  59. || "chrome".equals(command))
  60. command = "browser="+DomHttpServer.chromeCommand()+" --app=%U";
  61. else if ("browser=google-chrome".equals(command)
  62. || "browser=chrome".equals(command))
  63. command = "browser="+DomHttpServer.chromeCommand()+" %U";
  64. if ("firefox".equals(command)
  65. || "browser=firefox".equals(command))
  66. command = "browser="+DomHttpServer.firefoxCommand()+" %U";
  67. boolean exitOnClose = ! command.startsWith("serve");
  68. int port = Server.getInstancePort();
  69. DomHttpServer.setExitOnClose(exitOnClose);
  70. String url = "http://localhost:"+port+"/domterm/#ajax";
  71. if (command.equals("browser")) {
  72. if (! Desktop.isDesktopSupported())
  73. return "using default desktop browser not supported";
  74. Desktop.getDesktop().browse(new URI(url));
  75. return null;
  76. } else if (command.startsWith("browser=")) {
  77. String cmd = command.substring(8);
  78. if (cmd.indexOf('%') < 0)
  79. cmd = cmd + " %U";
  80. cmd = cmd.replace("%U", url).replace("%W", Integer.toString(port));
  81. Runtime.getRuntime().exec(cmd);
  82. return null;
  83. }
  84. else if (command.startsWith("serve")) {
  85. return null;
  86. }
  87. return "unrecognized -w subcommand '"+command+"'";
  88. }
  89. Language language;
  90. QueueReader inIn;
  91. Appendable inOut;
  92. OutputStream inOutS;
  93. boolean usingJLine;
  94. TtyInPort in_p;
  95. PipedInputStream inPipe; // only if usingJLine
  96. java.lang.reflect.Method setSizeMethod;
  97. public static final ThreadLocation<DomTermBackend> instance
  98. = new ThreadLocation<DomTermBackend>("domterm-backend");
  99. public DomTermBackend(Language language, Environment penvironment,
  100. boolean shared) {
  101. this.language = language;
  102. }
  103. public DomTermBackend() {
  104. this(Language.getDefaultLanguage(), Environment.getCurrent(), false);
  105. thread = new Thread(this);
  106. }
  107. @Override
  108. public void reportEvent(String name, String str) {
  109. if (name.equals("KEY") && str.equals("-3 \"\\u0003\"") // ctrl-C
  110. && in_p.sigIntHandler != null)
  111. in_p.sigIntHandler.run();
  112. else if (name.equals("KEY") && str.equals("-4 \"\\u0004\"") // ctrl-D
  113. && inIn != null) {
  114. inIn.appendEOF();
  115. } else
  116. super.reportEvent(name, str);
  117. }
  118. public void run() {
  119. Writer errWriter = new DomTermErrorWriter(termWriter);
  120. OutPort outp = new OutPort(termWriter, true, true,
  121. Path.valueOf("/dev/stdout"));
  122. Path inPath = Path.valueOf("/dev/stdin");
  123. int useJLine = CheckConsole.useJLine();
  124. instance.set(this);
  125. if (useJLine >= 0) {
  126. try {
  127. inPipe = new PipedInputStream();
  128. inOutS = new PipedOutputStream(inPipe);
  129. Class JLineClass = Class.forName("gnu.kawa.io.JLineInPort");
  130. in_p = (TtyInPort)
  131. JLineClass
  132. .getConstructor(java.io.InputStream.class,
  133. gnu.kawa.io.Path.class,
  134. java.io.OutputStream.class,
  135. gnu.kawa.io.OutPort.class)
  136. .newInstance(inPipe, (Path) inPath, (OutputStream) (new Utf8WriterOutputStream(outp)), (OutPort) outp);
  137. setSizeMethod =
  138. JLineClass.getMethod("setSize", Integer.TYPE, Integer.TYPE);
  139. usingJLine = true;
  140. } catch (Throwable ex) {
  141. inOutS = null;
  142. if (useJLine > 0) {
  143. // FIXME error in this case only
  144. ex.printStackTrace();
  145. }
  146. }
  147. }
  148. if (in_p == null)
  149. {
  150. QueueReader inQ = new QueueReader() {
  151. @Override
  152. public void checkAvailable() {
  153. //checkingPendingInput(); // See ReplDocument
  154. };
  155. };
  156. inIn = inQ;
  157. inOut = inQ;
  158. in_p = new TtyInPort(inIn, inPath, outp);
  159. }
  160. in_p.setInDomTerm(true);
  161. InPort.setInDefault(in_p);
  162. OutPort.setOutDefault(outp);
  163. OutPort errp = new OutPort(errWriter, true, true,
  164. Path.valueOf("/dev/stderr"));
  165. outp.setDomTerm(true);
  166. errp.setDomTerm(true);
  167. OutPort.setErrDefault(errp);
  168. Environment env = Environment.getCurrent();
  169. /*
  170. if (shared)
  171. env.setIndirectDefines();
  172. environment = env;
  173. */
  174. try {
  175. sendInputMode(usingJLine ? 'c' : 'p');
  176. termWriter.write("\033[20;1h"); // treat "\n" output as "\r\n"
  177. termWriter.write("\033]0;Kawa\007");
  178. } catch (Throwable ex) { ex.printStackTrace(); }
  179. if (this.nrows >= 0)
  180. setWindowSize(nrows, ncols, pixh, pixw);
  181. Shell.run(language, env);
  182. try {
  183. termWriter.close();
  184. } catch (Throwable ex) {
  185. // ignore
  186. }
  187. }
  188. public volatile int nrows = -1, ncols = -1, pixw, pixh;
  189. public void run(Writer out) throws Exception {
  190. this.termWriter = out;
  191. //addVersionInfo(???");
  192. if (thread == null)
  193. thread = new Thread(this);
  194. thread.start();
  195. }
  196. public void processInputCharacters(String text) {
  197. try {
  198. if (inOutS != null) {
  199. // FIXME should probably not use getBytes
  200. inOutS.write(text.getBytes());
  201. inOutS.flush();
  202. }
  203. else if (inOut != null)
  204. inOut.append(text, 0, text.length());
  205. } catch (IOException ex) {
  206. throw new RuntimeException(ex);
  207. }
  208. }
  209. @Override
  210. public void setWindowSize(int nrows, int ncols, int pixh, int pixw) {
  211. this.nrows = nrows;
  212. this.ncols = ncols;
  213. this.pixw = pixw;
  214. this.pixh = pixh;
  215. if (in_p != null && setSizeMethod != null) {
  216. try {
  217. setSizeMethod.invoke(in_p, ncols, nrows);
  218. } catch (Throwable ex) {
  219. // ignore
  220. }
  221. }
  222. }
  223. @Override public void close(boolean isLast) {
  224. if (inOutS != null) {
  225. try {
  226. // Kludge - jline doesn't seem to treat close as EOF
  227. // when using ExternalTerminal.
  228. inOutS.write(4); // ctrl-D
  229. inOutS.close();
  230. } catch (Throwable ex) {
  231. // ignore
  232. }
  233. }
  234. else
  235. inIn.appendEOF();
  236. }
  237. public static void loadStyleSheet(String name, String fname)
  238. throws IOException {
  239. String command = StyleSheets.loadStyleSheetRequest(name, fname);
  240. Writer commandWriter;
  241. InPort breader;
  242. DomTermBackend backend = instance.get(null);
  243. if (backend != null) {
  244. breader = backend.usingJLine ? new BinaryInPort(backend.inPipe)
  245. : backend.in_p;
  246. commandWriter = backend.termWriter;
  247. } else {
  248. OutPort outDefault = OutPort.getSystemOut();
  249. InPort inDefault = InPort.inDefault();
  250. if (! outDefault.isDomTerm() || ! (inDefault instanceof TtyInPort))
  251. return; // ERROR
  252. commandWriter = outDefault;
  253. breader = inDefault;
  254. }
  255. commandWriter.write(command);
  256. commandWriter.flush();
  257. breader.mark(1);
  258. int ch = breader.read();
  259. if (ch == 0x9D) {
  260. String response = breader.readLine();
  261. //System.err.println("loadStyleSheet response: "+response);
  262. }
  263. if (ch >= 0)
  264. breader.reset();
  265. //System.err.println("(no response received)");
  266. }
  267. }