Skip to content
Browse files

Converts most of the SExpr messages to JSON. Does NOT compile yet

(rebased by Marc Weber)
  • Loading branch information...
1 parent 25eca6d commit bcb7c9042bc920a0142393ca9372a5fbe9c23538 @casualjim casualjim committed with
Showing with 637 additions and 0 deletions.
  1. +1 −0 project/build/Ensime.scala
  2. +636 −0 src/main/scala/org/ensime/protocol/SwankJsonProtocol.scala
View
1 project/build/Ensime.scala
@@ -18,6 +18,7 @@ class EnsimeProject(info: ProjectInfo) extends DefaultProject(info){
// val refactoring = "org.scala-refactoring" % "org.scala-refactoring.library" % "0.2.0-SNAPSHOT"%"compile;runtime;test"
val asm = "asm" % "asm" % "3.2"
val asmCommons = "asm" % "asm-commons" % "3.2"
+ val liftJson = "net.liftweb" % "lift-json_2.8.0" % "2.1"
//override def compileOptions = compileOptions("-g:vars") ++ super.compileOptions.toList
View
636 src/main/scala/org/ensime/protocol/SwankJsonProtocol.scala
@@ -0,0 +1,636 @@
+package org.ensime.protocol
+
+import java.io._
+import org.ensime.config.{ ProjectConfig, DebugConfig, ReplConfig }
+import org.ensime.debug.{ DebugUnit, DebugSourceLinePairs }
+import org.ensime.model._
+import org.ensime.server._
+import org.ensime.util._
+import org.ensime.util.SExp._
+import scala.actors._
+import scala.tools.nsc.util.{ Position }
+import scala.tools.refactoring.common.Change
+import scala.util.parsing.input
+import net.liftweb.json.JsonDSL._
+import net.liftweb.json.JsonAST._
+import net.liftweb.json.{Printer, JsonParser}
+
+object SwankJsonProtocol extends SwankJsonProtocol {}
+
+trait SwankJsonProtocol extends Protocol {
+ implicit def jObject2WireFormat(obj: JObject): WireFormat = new WireFormat {
+ def toWireString = Printer.compact(render(obj))
+ }
+
+ import SwankProtocol._
+ import ProtocolConst._
+
+ val PROTOCOL_VERSION: String = "0.0.1"
+
+ val SERVER_NAME: String = "ENSIMEserver"
+
+ private var outPeer: Actor = null;
+ private var rpcTarget: RPCTarget = null;
+
+ def peer = outPeer
+
+ def setOutputActor(peer: Actor) { outPeer = peer }
+
+ def setRPCTarget(target: RPCTarget) { this.rpcTarget = target }
+
+ // Handle reading / writing of messages
+
+ def writeMessage(value: WireFormat, out: Writer) {
+ val data: String = value.toWireString
+ val header: String = String.format("%06x", int2Integer(data.length))
+ val msg = header + data
+ println("Writing: " + msg)
+ out.write(msg)
+ out.flush()
+ }
+
+ private def fillArray(in: java.io.Reader, a: Array[Char]) {
+ var n = 0
+ var l = a.length
+ var charsRead = 0;
+ while (n < l) {
+ charsRead = in.read(a, n, l - n)
+ if (charsRead == -1) {
+ throw new EOFException("End of file reached in socket reader.");
+ } else {
+ n += charsRead
+ }
+ }
+ }
+
+ private val headerBuf = new Array[Char](6);
+
+ def readMessage(in: java.io.Reader): WireFormat = {
+ fillArray(in, headerBuf)
+ val msglen = Integer.valueOf(new String(headerBuf), 16).intValue()
+ if (msglen > 0) {
+ JsonParser.parse(in)
+ } else {
+ throw new IllegalStateException("Empty message read from socket!")
+ }
+ }
+
+ def sendBackgroundMessage(code: Int, detail:Option[String]) {
+ val msg: JObject = ("background-message" -> ("code" -> code) ~ ("detail" -> detail.getOrElse("")))
+ sendMessage(msg)
+ }
+
+ def handleIncomingMessage(msg: Any) {
+ msg match {
+ case json: JValue => handleMessageForm(json)
+ case _ => System.err.println("WTF: Unexpected message: " + msg)
+ }
+ }
+
+ private def handleMessageForm(json: JObject) {
+ json match {
+ case JObject(List(JField("swank-rpc", JObject(List(JField(_, JInt(callId)), _))))) => {
+ handleEmacsRex(form, callId)
+ }
+ case _ => {
+ sendProtocolError(ErrUnrecognizedForm, Some(Printer.pretty(render(json))))
+ }
+ }
+ }
+
+ val emacsCharOffset = 1
+
+ private def handleEmacsRex(form: JObject, callId: Int) {
+ form match {
+ case SExpList(SymbolAtom(name) :: rest) => {
+ try {
+ handleRPCRequest(name, form, callId)
+ } catch {
+ case e: Throwable =>
+ {
+ e.printStackTrace(System.err)
+ sendRPCError(ErrExceptionInRPC, Some(e.getMessage), callId)
+ }
+ }
+ }
+ case _ => {
+ sendRPCError(
+ ErrMalformedRPC,
+ Some("Expecting leading symbol in: " + form),
+ callId)
+ }
+ }
+ }
+
+ private def handleRPCRequest(callType: String, form: SExp, callId: Int) {
+
+ println("\nHandling RPC: " + form)
+
+ def oops = sendRPCError(ErrMalformedRPC, Some("Malformed " + callType + " call: " + form), callId)
+
+ callType match {
+ case "swank:connection-info" => {
+ sendConnectionInfo(callId)
+ }
+ case "swank:init-project" => {
+ form match {
+ case SExpList(head ::(conf: SExpList) :: body) => {
+ val config = ProjectConfig.fromSExp(conf)
+ rpcTarget.rpcInitProject(config, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:peek-undo" => {
+ rpcTarget.rpcPeekUndo(callId)
+ }
+ case "swank:exec-undo" => {
+ form match {
+ case SExpList(head ::(IntAtom(id)) :: body) => {
+ rpcTarget.rpcExecUndo(id, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:repl-config" => {
+ rpcTarget.rpcReplConfig(callId)
+ }
+ case "swank:builder-init" => {
+ rpcTarget.rpcBuilderInit(callId)
+ }
+ case "swank:builder-add-files" => {
+ form match {
+ case SExpList(head :: SExpList(filenames) :: body) => {
+ val files = filenames.map(_.toString)
+ rpcTarget.rpcBuilderAddFiles(files, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:builder-update-files" => {
+ form match {
+ case SExpList(head :: SExpList(filenames) :: body) => {
+ val files = filenames.map(_.toString)
+ rpcTarget.rpcBuilderUpdateFiles(files, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:builder-remove-files" => {
+ form match {
+ case SExpList(head :: SExpList(filenames) :: body) => {
+ val files = filenames.map(_.toString)
+ rpcTarget.rpcBuilderRemoveFiles(files, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:debug-config" => {
+ rpcTarget.rpcDebugConfig(callId)
+ }
+ case "swank:debug-unit-info" => {
+ form match {
+ case SExpList(head :: StringAtom(sourceName) :: IntAtom(line) :: StringAtom(packPrefix) :: body) => {
+ rpcTarget.rpcDebugUnitInfo(sourceName, line, packPrefix, callId)
+ }
+ case _ => oops
+ }
+ }
+
+ case "swank:debug-class-locs-to-source-locs" => {
+ form match {
+ case SExpList(head :: SExpList(pairs) :: body) => {
+ val nameLinePairs = pairs.flatMap {
+ case SExpList((classname: StringAtom) ::(line: IntAtom) :: body) => {
+ Some(classname.toString, line.value)
+ }
+ case _ => Some("", -1)
+ }
+ rpcTarget.rpcDebugClassLocsToSourceLocs(nameLinePairs, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:typecheck-file" => {
+ form match {
+ case SExpList(head :: StringAtom(file) :: body) => {
+ rpcTarget.rpcTypecheckFile(file, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:typecheck-all" => {
+ rpcTarget.rpcTypecheckAll(callId)
+ }
+ case "swank:scope-completion" => {
+ form match {
+ case SExpList(head :: StringAtom(file) :: IntAtom(point) :: StringAtom(prefix) :: BooleanAtom(constructor) :: body) => {
+ rpcTarget.rpcScopeCompletion(file, point, prefix, constructor, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:type-completion" => {
+ form match {
+ case SExpList(head :: StringAtom(file) :: IntAtom(point) :: StringAtom(prefix) :: body) => {
+ rpcTarget.rpcTypeCompletion(file, point, prefix, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:import-suggestions" => {
+ form match {
+ case SExpList(head :: StringAtom(file) :: IntAtom(point) :: SExpList(names) :: body) => {
+ rpcTarget.rpcImportSuggestions(file, point,
+ names.map(_.toString).toList, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:package-member-completion" => {
+ form match {
+ case SExpList(head :: StringAtom(path) :: StringAtom(prefix) :: body) => {
+ rpcTarget.rpcPackageMemberCompletion(path, prefix, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:inspect-type-at-point" => {
+ form match {
+ case SExpList(head :: StringAtom(file) :: IntAtom(point) :: body) => {
+ rpcTarget.rpcInspectTypeAtPoint(file, point, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:inspect-type-by-id" => {
+ form match {
+ case SExpList(head :: IntAtom(id) :: body) => {
+ rpcTarget.rpcInspectTypeById(id, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:symbol-at-point" => {
+ form match {
+ case SExpList(head :: StringAtom(file) :: IntAtom(point) :: body) => {
+ rpcTarget.rpcSymbolAtPoint(file, point, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:type-by-id" => {
+ form match {
+ case SExpList(head :: IntAtom(id) :: body) => {
+ rpcTarget.rpcTypeById(id, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:type-by-name" => {
+ form match {
+ case SExpList(head :: StringAtom(name) :: body) => {
+ rpcTarget.rpcTypeByName(name, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:type-by-name-at-point" => {
+ form match {
+ case SExpList(head :: StringAtom(name) :: StringAtom(file) :: IntAtom(point) :: body) => {
+ rpcTarget.rpcTypeByNameAtPoint(name, file, point, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:call-completion" => {
+ form match {
+ case SExpList(head :: IntAtom(id) :: body) => {
+ rpcTarget.rpcCallCompletion(id, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:type-at-point" => {
+ form match {
+ case SExpList(head :: StringAtom(file) :: IntAtom(point) :: body) => {
+ rpcTarget.rpcTypeAtPoint(file, point, callId)
+ }
+ case _ => oops
+ }
+ }
+ case "swank:inspect-package-by-path" => {
+ form match {
+ case SExpList(head :: StringAtom(path) :: body) => {
+ rpcTarget.rpcInspectPackageByPath(path, callId)
+ }
+ case _ => oops
+ }
+ }
+
+ case "swank:perform-refactor" => {
+ form match {
+ case SExpList(head :: IntAtom(procId) :: SymbolAtom(tpe) ::(params: SExp) :: body) => {
+ rpcTarget.rpcPerformRefactor(Symbol(tpe), procId,
+ listOrEmpty(params).toSymbolMap, callId)
+ }
+ case _ => oops
+ }
+ }
+
+ case "swank:exec-refactor" => {
+ form match {
+ case SExpList(head :: IntAtom(procId) :: SymbolAtom(tpe) :: body) => {
+ rpcTarget.rpcExecRefactor(Symbol(tpe), procId, callId)
+ }
+ case _ => oops
+ }
+ }
+
+ case "swank:cancel-refactor" => {
+ form match {
+ case SExpList(head :: IntAtom(procId) :: body) => {
+ rpcTarget.rpcCancelRefactor(procId, callId)
+ }
+ case _ => oops
+ }
+ }
+
+ case "swank:format-source" => {
+ form match {
+ case SExpList(head :: SExpList(filenames) :: body) => {
+ val files = filenames.map(_.toString)
+ rpcTarget.rpcFormatFiles(files, callId)
+ }
+ case _ => oops
+ }
+ }
+
+ case other => {
+ sendRPCError(
+ ErrUnrecognizedRPC,
+ Some("Unknown :swank-rpc call: " + other),
+ callId)
+ }
+ }
+ }
+
+ def listOrEmpty(list: SExp): SExpList = {
+ list match {
+ case l: SExpList => l
+ case _ => SExpList(List())
+ }
+ }
+
+ def sendRPCAckOK(callId: Int) {
+ sendRPCReturn(true, callId)
+ }
+
+ def sendRPCReturn(value: WireFormat, callId: Int) {
+ value match {
+ case sexp: SExp =>
+ {
+ sendMessage(SExp(
+ key(":return"),
+ SExp(key(":ok"), sexp),
+ callId))
+ }
+ case _ => throw new IllegalStateException("Not a SExp: " + value)
+ }
+ }
+
+ def sendRPCError(code: Int, detail: Option[String], callId: Int) {
+ sendMessage(SExp(
+ key(":return"),
+ SExp(key(":abort"),
+ code,
+ detail.map(strToSExp).getOrElse(NilAtom())
+ ),
+ callId))
+ }
+
+ def sendProtocolError(code: Int, detail: Option[String]){
+ sendMessage(
+ SExp(
+ key(":reader-error"),
+ code,
+ detail.map(strToSExp).getOrElse(NilAtom())
+ ))
+ }
+
+ /*
+ * A sexp describing the server configuration, per the Swank standard.
+ */
+ def sendConnectionInfo(callId: Int) = {
+ val inf = ("pid" -> null) ~
+ ("server-implementation" -> ("name" -> SERVER_NAME)) ~
+ ("machine" -> null) ~
+ ("features" -> null) ~
+ ("version" -> PROTOCOL_VERSION)
+ sendRPCReturn(inf, callId)
+ }
+
+ def sendCompilerReady() = sendMessage(JObject(List(JField("compiler-ready", JBool(true)))))
+
+ def sendTypeCheckResult(notelist: NoteList) = {
+ sendMessage(JObject(List(JField("typecheck-result", toWF(notelist)))))
+ }
+
+ object JValueConversion {
+
+ implicit def posToJValue(pos: Position): JValue = {
+ if (pos.isDefined) {
+ ("file" -> pos.source.path) ~ ("offset" -> pos.point + emacsCharOffset)
+ } else {
+ JNull
+ }
+ }
+
+ }
+
+ import JValueConversion._
+
+ def toWF(config: ProjectConfig): JValue = {
+ ("project-name" -> config.name) ~ ("source-roots" -> config.sourceRoots.map(_.getPath))
+ }
+
+ def toWF(config: ReplConfig): JValue = {
+ ("classpath" -> config.classpath)
+ }
+
+ def toWF(config: DebugConfig): JValue = {
+ ("classpath" -> config.classpath) ~ ("sourcepath" -> config.sourcepath)
+ }
+
+ def toWF(unit: DebugUnit): JValue = {
+ ("full-name" -> unit.classQualName) ~
+ ("package" -> unit.packageName) ~
+ ("start-line" -> unit.startLine) ~
+ ("end-line" -> unit.endLine)
+ }
+
+// def toWF(value: Boolean): JValue = {
+// if (value) TruthAtom()
+// else NilAtom()
+// }
+//
+// def toWF(value: Null): JValue = {
+// NilAtom()
+// }
+//
+// def toWF(value: String): JValue = {
+// StringAtom(value)
+// }
+
+ def toWF(value: DebugSourceLinePairs): JValue = {
+
+ JArray(value.pairs.map( p => JObject(List(JField(p._1, p._2)))).toList)
+ }
+
+ def toWF(note: Note): JValue = {
+ ("severity" -> note.friendlySeverity) ~
+ ("msg" -> note.msg) ~
+ ("beg" -> note.beg + emacsCharOffset) ~
+ ("end" -> note.end + emacsCharOffset) ~
+ ("line" -> note.line) ~
+ ("col" -> note.col) ~
+ ("file" -> note.file)
+ }
+
+ def toWF(notelist: NoteList): JValue = {
+ val NoteList(isFull, notes) = notelist
+ ("is-full" -> isFull) ~ ("notes" -> notes.map(toWF))
+ }
+
+// def toWF(values: Iterable[WireFormat]): JValue = {
+// SExpList(values.map(ea => ea.asInstanceOf[SExp]))
+// }
+
+ def toWF(value: SymbolInfoLight): JValue = {
+ ("name" -> value.name) ~
+ ("type-sig" -> value.tpeSig) ~
+ ("type-id" -> value.tpeId) ~
+ ("is-callable" -> value.isCallable)
+ }
+
+ def toWF(value: PackageMemberInfoLight): JValue = {
+ ("name" -> value.name)
+ }
+
+ def toWF(value: SymbolInfo): JValue = {
+ ("name" -> value.name) ~
+ ("type" -> toWF(value.tpe)) ~
+ ("decl-pos" -> value.declPos) ~
+ ("is-callable" -> value.isCallable)
+ }
+
+ def toWF(value: NamedTypeMemberInfoLight): JValue = {
+ ("name" -> value.name) ~
+ ("type-sig" -> value.tpeSig) ~
+ ("type-id" -> value.tpeId) ~
+ ("is-callable" -> value.isCallable)
+ }
+
+ def toWF(value: NamedTypeMemberInfo): JValue = {
+ ("name" -> value.name) ~
+ ("type" -> toWF(value.tpe)) ~
+ ("pos" -> value.pos) ~
+ ("decl-as" -> value.declaredAs)
+ }
+
+ def toWF(value: EntityInfo): JValue = {
+ value match {
+ case value: PackageInfo => toWF(value)
+ case value: TypeInfo => toWF(value)
+ case value: NamedTypeMemberInfo => toWF(value)
+ case value: NamedTypeMemberInfoLight => toWF(value)
+ case value => throw new IllegalStateException("Unknown EntityInfo: " + value)
+ }
+ }
+
+ def toWF(value: TypeInfo): JValue = {
+ value match {
+ case value: ArrowTypeInfo =>
+ {
+ ("name" -> value.name) ~
+ ("type-id" -> value.id) ~
+ ("arrow-type" -> true) ~
+ ("result-type" -> toWF(value.resultType)) ~
+ ("params-sections" -> value.paramsSection.map(sect => sect.map(toWF).toList).toList)
+ }
+ case value: TypeInfo =>
+ {
+ ("name" -> value.name) ~
+ ("type-id" -> value.id) ~
+ ("full-name" -> value.fullName) ~
+ ("decl-as" -> value.declaredAs) ~
+ ("type-args" -> value.args.map(toWF).toList) ~
+ ("members" -> value.members.map(toWF).toList) ~
+ ("pos" -> value.pos) ~
+ ("outer-type-id" ~ value.outerTypeId.map(JInt(_)).getOrElse(JNull))
+ }
+ case value => throw new IllegalStateException("Unknown TypeInfo: " + value)
+ }
+ }
+
+ def toWF(value: PackageInfo): JValue = {
+ ("name" -> value.name) ~ ("info-type" -> "package") ~ ("full-name" -> value.fullName) ~ ("members" -> value.members.map(toWF).toList)
+ }
+
+ def toWF(value: CallCompletionInfo): JValue = {
+ ("result-type" -> value.resultType) ~
+ ("params-sections" -> (value.paramSections.map { sect =>
+ sect.map(pair => JObject(List(JField(pair._1, toWF(pair._2))))).toList
+ }).toList)
+ }
+
+ def toWF(value: InterfaceInfo): JValue = {
+ ("type" -> toWF(value.tpe)) ~ ("via-view" -> value.viaView.getOrElse(""))
+ }
+
+ def toWF(value: TypeInspectInfo): JValue = {
+ ("type" -> toWF(value.tpe)) ~
+ ("info-type" -> "typeInspect") ~
+ ("companion-id" -> value.companionId.map(JInt(_)).getOrElse(JNull))
+ ("interfaces" -> value.supers.map(toWF).toList)
+ }
+
+ def toWF(value: RefactorFailure): JValue = {
+ ("procedure-id" -> value.procedureId) ~
+ ("status" -> "failure") ~
+ ("reason" -> value.message)
+ }
+
+ def toWF(value: RefactorEffect): JValue = {
+ ("procedure-id" -> value.procedureId) ~
+ ("refactor-type" -> value.refactorType) ~
+ ("status" -> "success") ~
+ ("changes" -> value.changes.map(changeToWF).toList)
+ }
+
+ def toWF(value: RefactorResult): JValue = {
+ ("procedure-id" -> value.procedureId) ~
+ ("refactor-type" -> value.refactorType) ~
+ ("touched-files" -> value.touched.map(_.getAbsolutePath).toList)
+ }
+
+ def toWF(value: ImportSuggestions): JValue = {
+ JArray(value.symLists.map(l -> JArray(l.map(toWF).toList)).toList)
+ }
+
+ def toWF(value: Undo): JValue = {
+ ("id" -> value.id) ~ ("changes" -> value.changes.map(changeToWF)) ~ ("summary" -> value.summary)
+ }
+
+ def toWF(value: UndoResult): JValue = {
+ ("id" -> value.id) ~ ("touched-files" -> value.touched.toList)
+ }
+
+ private def changeToWF(ch: Change): JValue = {
+ ("file" -> ch.file.path) ~
+ ("text" -> ch.text) ~
+ ("from" -> ch.from.emacsCharOffset) ~
+ ("to" -> ch.to + emacsCharOffset)
+ }
+
+}

0 comments on commit bcb7c90

Please sign in to comment.
Something went wrong with that request. Please try again.