Permalink
Browse files

initial check-in

  • Loading branch information...
0 parents commit b2a8e6a9ec11bd152a4b328a9c631080eec9be4a @TiarkRompf committed Oct 3, 2010
@@ -0,0 +1,51 @@
+Scala REPL HTML Interface
+=========================
+
+This project provides the Scala REPL with a GUI inspired by Mathematica worksheets. Commands can be edited and deleted, and new commands can be inserted anywhere. Whole worksheets can be re-evaluated with one click. Sessions can be saved and restored.
+
+The frontend is just an HTML page that communicates with a backend servlet via web sockets. The backend servlet does the actual command execution. The servlet is not multi-user save; the program is meant to be run on a single computer.
+
+**CAVEAT**: Right now, this is all highly experimental.
+
+How to Run it
+-------------
+
+Use SBT to run the REPL servlet within an embedded Jetty server:
+
+ sbt run
+
+Then open the html file that contains the frontend in your browser:
+
+ src/webapp/index.html
+
+
+
+Accessing Application Classes
+-----------------------------
+
+The available classes are defined by two system properties, `replhtml.class.path` and `replhtml.extra.class.path`. By default, the former contains the Scala library and compiler jars while the latter can be freely used to load additional code.
+
+For example, the following command:
+
+ sbt 'set replhtml.extra.class.path /Users/myself/projects/MyGreatApp/classes/' run
+
+will start the REPL with your application's classes loaded from the specified path, much like `scala -cp` would.
+
+With the backend running, point your browser to
+
+ src/webapp/index.html
+
+and enjoy!
+
+
+Things to Try
+-------------
+
+Use the - and + buttons to remove and insert commands. Hit tab and shift-tab to navigate up and down. Turn on completion (this will display available members but not actually complete your typing). Save and restore sessions.
+
+There is some limited support for interacting with the HTML environment. Text output that begins with `<js>` will be interpreted as JavaScript code. The following code adds a button to the HTML page that displays an alert box when clicked:
+
+ def js(code: String) = println("<js>"+code)
+ js("$('#container').append($('<button>Click me!</button>').click(function() { alert('hey!') }))")
+
+The `:power` mode is enabled by default. Use `repl` to access the interpreter object, or `servlet` to access the servlet.
@@ -0,0 +1,9 @@
+#Project properties
+#Sun Oct 03 23:23:22 CEST 2010
+project.organization=epfl.lamp
+project.name=replhtml
+sbt.version=0.7.4
+project.version=1.0
+replhtml.extra.class.path=arrr
+build.scala.versions=2.8.0
+project.initialize=false
@@ -0,0 +1,30 @@
+import sbt._
+
+class Project(info: ProjectInfo) extends DefaultWebProject(info)
+{
+ val jetty7 = "org.eclipse.jetty" % "jetty-webapp" % "7.0.2.RC0" % "provided"
+ val jetty7webSocket = "org.eclipse.jetty" % "jetty-websocket" % "7.0.2.RC0" % "provided"
+ val servlet = "javax.servlet" % "servlet-api" % "2.5" % "provided"
+
+ //override def unmanagedClasspath = super.unmanagedClasspath +++ ("lib2" / "scala-compiler.jar")
+ val scalac = "org.scala-lang" % "scala-compiler" % "2.8.0" % "provided"
+ val scala = "org.scala-lang" % "scala-library" % "2.8.0" % "provided"
+
+ override def mainClass = Some("ch.epfl.lamp.replhtml.ReplMain")
+
+ lazy val propRunClassPath = systemOptional[String]("replhtml.class.path",
+ (runClasspath +++ Path.fromFile(buildScalaInstance.compilerJar) +++
+ Path.fromFile(buildScalaInstance.libraryJar)).absString)
+
+ override def jettyRunAction = {
+ System.setProperty("replhtml.class.path", propRunClassPath.get.get)
+ super.jettyRunAction
+ }
+
+
+ // repositories
+ val scalaToolsSnapshots = "Scala Tools Repository" at "http://nexus.scala-tools.org/content/repositories/snapshots/"
+ val sonatypeNexusSnapshots = "Sonatype Nexus Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
+ val sonatypeNexusReleases = "Sonatype Nexus Releases" at "https://oss.sonatype.org/content/repositories/releases"
+ val fuseSourceSnapshots = "FuseSource Snapshot Repository" at "http://repo.fusesource.com/nexus/content/repositories/snapshots"
+}
@@ -0,0 +1,30 @@
+package ch.epfl.lamp.replhtml
+
+//import javax.servlet.http._
+import org.eclipse.jetty.server.Server
+import org.eclipse.jetty.servlet.ServletContextHandler
+import org.eclipse.jetty.servlet.ServletHolder
+import org.eclipse.jetty.servlet.DefaultServlet
+
+object ReplMain {
+ def main(args: Array[String]) {
+ val server = new Server(8080)
+
+ val context = new ServletContextHandler(ServletContextHandler.SESSIONS)
+ context.setContextPath("/")
+ server.setHandler(context)
+
+ context.addServlet(new ServletHolder(new ReplServlet()),"/socket/*")
+ context.addServlet(new ServletHolder(new DefaultServlet()),"/*")
+
+ server.start()
+ println(">>> embedded jetty server started. press any key to stop.")
+ while (System.in.available() == 0) {
+ Thread.sleep(1500)
+ }
+ System.in.read()
+ println(">>> stopping...")
+ server.stop()
+ server.join()
+ }
+}
@@ -0,0 +1,103 @@
+package ch.epfl.lamp.replhtml
+
+import scala.collection.mutable.Set
+import java.io.{ OutputStream, PrintStream }
+import javax.servlet.http._
+import org.eclipse.jetty.websocket._
+import org.eclipse.jetty.websocket.WebSocket.Outbound
+
+import scala.tools.nsc._
+import scala.tools.nsc.interpreter._
+
+class ReplServlet extends WebSocketServlet {
+ val clients = Set.empty[ReplWebSocket]
+
+ var classpath = System.getProperty("replhtml.class.path")
+ assert(classpath ne null, "System property replhtml.class.path is not set. Repl needs a class path to operate.")
+ classpath += ":" + System.getProperty("replhtml.extra.class.path", "")
+ println(classpath)
+ println("EXTRA:"+System.getProperty("replhtml.extra.class.path"))
+
+ val cmd = new InterpreterCommand(Nil, println)
+ val settings = cmd.settings//new Settings
+ settings.classpath.value = classpath
+// settings.usejavacp.value = true
+//settings.Ycompletion.value = true
+ val interpreter = new Interpreter(settings) {
+ override def reset() = { super.reset; unleash() }
+ override def unleash() = { super.unleash; bind("servlet", "ch.epfl.lamp.replhtml.ReplServlet", ReplServlet.this) }
+ }
+ interpreter.unleash()
+ val completion = new Completion(interpreter)
+
+ override def doGet(req: HttpServletRequest, res: HttpServletResponse) =
+ getServletContext.getNamedDispatcher("default").forward(req, res)
+
+ override def doWebSocketConnect(req:HttpServletRequest, protocol:String ) =
+ new ReplWebSocket
+
+ class WebSocketPrintStream(cls: Set[ReplWebSocket]) extends PrintStream(new OutputStream { def write(b: Int) = {} }) {
+
+ override def print(message: String) = {
+ cls.foreach { c => c.outbound.sendMessage(0:Byte, message) }
+ }
+/*
+ override def println(message: String) = {
+ clients.foreach { c => c.outbound.sendMessage(0:Byte, message+"\n\r") }
+ }
+ override def println() = {
+ clients.foreach { c => c.outbound.sendMessage(0:Byte, "\n\r") }
+ }
+*/
+ }
+
+
+ class ReplWebSocket extends WebSocket {
+
+ var outbound:Outbound = _
+
+ override def onConnect(outbound:Outbound) = {
+ this.outbound = outbound
+ clients += this
+ }
+
+ override def onMessage(frame:Byte, data:Array[Byte], offset:Int, length:Int) = {}
+
+ override def onMessage(frame:Byte, data:String) = {
+ val idx = data.indexOf(":")
+ val key = if (idx > 0) data.substring(0,idx) else ""
+ var source = if (idx >= 0) data.substring(idx+1) else data
+ key match {
+ case "complete" =>
+
+ val out = new WebSocketPrintStream(Set(this))
+
+ val idx = source.indexOf(":")
+ val ipos = Integer.parseInt(source.substring(0,idx))
+ source = source.substring(idx+1)
+
+ if (ipos <= source.length) {
+ val tokens = source.substring(0,ipos).split("""[\ \,\;\(\)\{\}]""") // could tokenize on client
+ //println("try to complete: " + tokens.mkString(","))
+ val cmpl = completion.topLevelFor(Parsed.dotted(tokens.last, ipos) withVerbosity 4) // (?)
+ out.println("<completion>:"+ipos+"\n"+cmpl.mkString("\n"))
+ } else {
+ out.println("<completion>:"+ipos+"\n")
+ }
+ //interpreter.requestFromLine(source)
+
+ case _ =>
+ Console.withOut(new WebSocketPrintStream(Set(this))) {
+ interpreter.interpret(source) match {
+ case InterpreterResults.Error => println("<done:error>")
+ case InterpreterResults.Success => println("<done:success>")
+ case InterpreterResults.Incomplete => println("<done:incomplete>")
+ }
+ }
+ }
+ }
+
+ override def onDisconnect = clients -= this
+
+ }
+}
@@ -0,0 +1,21 @@
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ version="2.5">
+ <servlet>
+ <servlet-name>Repl</servlet-name>
+ <servlet-class>ch.epfl.lamp.replhtml.ReplServlet</servlet-class>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>Repl</servlet-name>
+ <url-pattern>/socket/*</url-pattern>
+ </servlet-mapping>
+
+ <servlet-mapping>
+ <servlet-name>default</servlet-name>
+ <url-pattern>/images/*</url-pattern>
+ <url-pattern>/css/*</url-pattern>
+ <url-pattern>/js/*</url-pattern>
+ </servlet-mapping>
+</web-app>
Oops, something went wrong.

0 comments on commit b2a8e6a

Please sign in to comment.