diff --git a/etc/manual.ltx b/etc/manual.ltx index 1194d9494..0eb863d02 100644 --- a/etc/manual.ltx +++ b/etc/manual.ltx @@ -1,7 +1,7 @@ \documentclass{article} \usepackage[utf8]{inputenc} - +\usepackage{graphics} \usepackage{amsmath} \newenvironment{mylisting} @@ -655,7 +655,19 @@ Emacs starts the ENSIME server using the server.sh script in the \emph{bin} fold \subsection{The Swank Protocol} -The Emacs ENSIME client communicates with the server using the Swank protocol. This protocol was originally developed for the SLIME lisp environment for Emacs and is therefore very 'lispy'. A socket connection is maintained for the duration of the session. The client and server exchange messages encoded as S-Expressions, separated by hex-encoded message lengths. See the E-Lisp function \emph{ensime-net-send} in ensime.el for details on how messages are sent from Emacs, and the functions readMessage and writeMessage in org.ensime.protocol.SwankProtocol for the Scala side of the conversation. What follows is a commented excerpt from the initialization of an ENSIME session. These SExps were copied from the \emph{*ensime-events*} buffer in Emacs which logs all protocol events (useful for learning the protocol!). Server messages are indented. Comments prefixed with \#. +The Emacs ENSIME client communicates with the server using the Swank protocol. This protocol was originally developed for the SLIME lisp environment for Emacs. A socket connection is maintained for the duration of the session. The client and server exchange s-expressions. At the wire level, these messages are encoded as sequences of ASCII bytes. Each message is prepended with a fixed-size header denoting its length. + +To send an s-expression, determine its ASCII length and then encode that integer as a padded six-digit hexadecimal value. Write this value to the output socket first, then followed by the ASCII form of the s-expression. On the receiving side, the reader loop should read six ASCII characters into a buffer and convert that into an integer, then read that number of ASCII characters from the socket, parsing the result into an s-expression.\\ + +\vspace{5 mm} + +\includegraphics{wire_protocol.png} \\ + +\vspace{7 mm} + +See the E-Lisp function \emph{ensime-net-send} in ensime.el for details on how messages are sent from Emacs, and the function \emph{ensime-net-encode-length} for the implementation of the header encoding. See the functions readMessage and writeMessage in org.ensime.protocol.SwankProtocol to see how the messaging is handled in Scala. + +At the application level, the s-expressions encode RPC calls, RPC responses, and events. RPC calls are used to make requests of the server. Events are generally used for the server to communicate un-prompted, asynchronous activity to the client -- such as an error or warning generated during compilation. What follows is a commented excerpt from the initialization of an ENSIME session. These s-expressions were copied from the \emph{*ensime-events*} buffer in Emacs which logs all protocol events (useful for learning the application protocol!). Server messages are indented. Comments prefixed with \#. \begin{mylisting} \begin{verbatim} diff --git a/etc/wire_protocol.png b/etc/wire_protocol.png new file mode 100644 index 000000000..94366e3d8 Binary files /dev/null and b/etc/wire_protocol.png differ diff --git a/etc/wire_protocol.svg b/etc/wire_protocol.svg new file mode 100644 index 000000000..018d88e8d --- /dev/null +++ b/etc/wire_protocol.svg @@ -0,0 +1,321 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + 17 ASCII bytes (:return (:ok t)) + + + + + + '17' (hex-encoded, padded to 6 bytes) 000011 + diff --git a/project/Build.scala b/project/Build.scala index 6f130aca7..b7b9d8583 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -128,10 +128,11 @@ object EnsimeBuild extends Build { val cwd = Some(new File("etc")) doSh("pdflatex manual.ltx", cwd)!!log doSh("cat manual_head.html > " + target, cwd)!!log - doSh("tth -r -u -Lmanual < manual.ltx >> " + target, cwd)!!(log) + doSh("tth -r -u -e2 -Lmanual < manual.ltx >> " + target, cwd)!!(log) doSh("cat manual_tail.html >> " + target, cwd)!!log log.info("Publishing manual to web...") doSh("scp " + target + " www@aemon.com:~/public/aemon/file_dump/", cwd)!!(log) + doSh("scp wire_protocol.png www@aemon.com:~/public/aemon/file_dump/", cwd)!!(log) None } diff --git a/src/main/elisp/ensime.el b/src/main/elisp/ensime.el index 7bcb36879..184ce18f2 100644 --- a/src/main/elisp/ensime.el +++ b/src/main/elisp/ensime.el @@ -161,11 +161,17 @@ argument is supplied) is a .scala or .java file." (when file (integerp (string-match "\\(?:\\.scala$\\|\\.java$\\)" file))))) +(defun ensime-java-file-p (f) + (string-match "\\.java$" f)) + +(defun ensime-scala-file-p (f) + (string-match "\\.scala$" f)) + (defun ensime-visiting-java-file-p () - (string-match "\\.java$" buffer-file-name)) + (ensime-java-file-p buffer-file-name)) (defun ensime-visiting-scala-file-p () - (string-match "\\.scala$" buffer-file-name)) + (ensime-scala-file-p buffer-file-name)) (defun ensime-scala-mode-hook () "Conveniance hook function that just starts ensime-mode." @@ -1455,9 +1461,10 @@ overrides `ensime-buffer-connection'.") (catch 'return (dolist (p ensime-net-processes) (let* ((config (ensime-config p)) - (dir (plist-get config :root-dir))) - (when (ensime-file-in-directory-p file dir) - (throw 'return p))))) + (source-roots (plist-get config :source-roots))) + (dolist (dir source-roots) + (when (ensime-file-in-directory-p file dir) + (throw 'return p)))))) )) @@ -1956,7 +1963,7 @@ This idiom is preferred over `lexical-let'." (setf (ensime-scala-compiler-notes (ensime-connection)) nil)) ((equal lang 'java) (setf (ensime-java-compiler-notes (ensime-connection)) nil))) - (ensime-clear-note-overlays)) + (ensime-clear-note-overlays lang)) (defun ensime-make-overlay-at (file line b e msg face) @@ -1998,26 +2005,27 @@ any buffer visiting the given file." (when (eq beg end) (setq beg (- beg 1))) - (cond - ((equal severity 'error) - (progn - (when-let (ov (ensime-make-overlay-at - file nil - (+ beg ensime-ch-fix) - (+ end ensime-ch-fix) - msg 'ensime-errline-highlight)) - (push ov ensime-note-overlays)) - )) - - (t (progn - (when-let (ov (ensime-make-overlay-at - file nil - (+ beg ensime-ch-fix) - (+ end ensime-ch-fix) - msg 'ensime-warnline-highlight)) - (push ov ensime-note-overlays)) - )) - )))) + (let ((lang + (cond + ((ensime-java-file-p file) 'java) + ((ensime-scala-file-p file) 'scala) + (t 'scala))) + (face + (cond + ((equal severity 'error) + 'ensime-errline-highlight) + (t + 'ensime-warnline-highlight)))) + + (when-let (ov (ensime-make-overlay-at + file nil + (+ beg ensime-ch-fix) + (+ end ensime-ch-fix) + msg face)) + (overlay-put ov 'lang lang) + (push ov ensime-note-overlays)) + + )))) (defun ensime-refresh-all-note-overlays () @@ -2079,10 +2087,16 @@ any buffer visiting the given file." ovs) )) -(defun ensime-clear-note-overlays () - "Delete the existing note overlays." - (mapc #'delete-overlay ensime-note-overlays) - (setq ensime-note-overlays '())) +(defun ensime-clear-note-overlays (&optional lang) + "Delete note overlays language. If lang is nil, delete all + overlays." + (let ((revised '())) + (dolist (ov ensime-note-overlays) + (if (or (null lang) + (equal lang (overlay-get ov 'lang))) + (delete-overlay ov) + (setq revised (cons ov revised)))) + (setq ensime-note-overlays revised))) (defun ensime-next-note-in-current-buffer (notes forward) (let ((best-note nil) diff --git a/src/main/scala/org/ensime/config/ExternalConfigInterface.scala b/src/main/scala/org/ensime/config/ExternalConfigInterface.scala index 1b21d8f87..2e9ac710e 100644 --- a/src/main/scala/org/ensime/config/ExternalConfigInterface.scala +++ b/src/main/scala/org/ensime/config/ExternalConfigInterface.scala @@ -13,9 +13,9 @@ import java.util.Properties case class ExternalConfig( val projectName: Option[String], val sourceRoots: Iterable[CanonFile], - val runtimeDepJars: Iterable[CanonFile], - val compileDepJars: Iterable[CanonFile], - val testDepJars: Iterable[CanonFile], + val runtimeDepFiles: Iterable[CanonFile], + val compileDepFiles: Iterable[CanonFile], + val testDepFiles: Iterable[CanonFile], val target: Option[CanonFile]) {} trait ExternalConfigurator { diff --git a/src/main/scala/org/ensime/config/ProjectConfig.scala b/src/main/scala/org/ensime/config/ProjectConfig.scala index 08e888589..d07a82311 100644 --- a/src/main/scala/org/ensime/config/ProjectConfig.scala +++ b/src/main/scala/org/ensime/config/ProjectConfig.scala @@ -153,8 +153,8 @@ object ProjectConfig { case Right(ext) => { projectName = ext.projectName sourceRoots ++= ext.sourceRoots - runtimeDeps ++= ext.runtimeDepJars - compileDeps ++= ext.compileDepJars + runtimeDeps ++= ext.runtimeDepFiles + compileDeps ++= ext.compileDepFiles target = ext.target } case Left(except) => { @@ -378,6 +378,7 @@ class ProjectConfig( def compilerArgs = List( "-classpath", compilerClasspath, + "-sourcepath", sourcepath, "-verbose") def builderArgs = List( diff --git a/src/main/scala/org/ensime/config/Sbt.scala b/src/main/scala/org/ensime/config/Sbt.scala index 16fa1d669..463ad3641 100644 --- a/src/main/scala/org/ensime/config/Sbt.scala +++ b/src/main/scala/org/ensime/config/Sbt.scala @@ -82,8 +82,11 @@ object Sbt extends ExternalConfigurator { def parseSettingStr(input: String): Option[String] = { val m = singleLineSetting.matcher(input); - if (m.find()) Some(m.group(1)) - else None + var result: Option[String] = None + while (m.find()) { + result = Some(m.group(1)) + } + result } def evalNoop()(implicit shell: Spawn): Unit = { @@ -196,7 +199,7 @@ object Sbt extends ExternalConfigurator { parseAttributedFilesList(showSetting("runtime:unmanaged-classpath")) ) val sourceRoots = parseAttributedFilesList(showSetting("source-directories")) - val target = CanonFile(showSetting("target")) + val target = CanonFile(showSetting("class-directory")) shell.send("exit\n") shell.expectClose() @@ -204,13 +207,13 @@ object Sbt extends ExternalConfigurator { import FileUtils._ - val compileDepJars = maybeFiles(compileDeps, baseDir).filter(isValidJar) - val testDepJars = maybeFiles(testDeps, baseDir).filter(isValidJar) - val runtimeDepJars = maybeFiles(runtimeDeps, baseDir).filter(isValidJar) + val compileDepFiles = maybeFiles(compileDeps, baseDir) + val testDepFiles = maybeFiles(testDeps, baseDir) + val runtimeDepFiles = maybeFiles(runtimeDeps, baseDir) val sourceRootFiles = maybeDirs(sourceRoots, baseDir) Right(ExternalConfig(Some(name), sourceRootFiles, - runtimeDepJars, compileDepJars, testDepJars, + runtimeDepFiles, compileDepFiles, testDepFiles, Some(target))) } @@ -250,15 +253,15 @@ object Sbt extends ExternalConfigurator { shell.stop() import FileUtils._ - val compileDepJars = maybeFiles(compileDeps, baseDir).filter(isValidJar) - val testDepJars = maybeFiles(testDeps, baseDir).filter(isValidJar) - val runtimeDepJars = maybeFiles(runtimeDeps, baseDir).filter(isValidJar) + val compileDepFiles = maybeFiles(compileDeps, baseDir) + val testDepFiles = maybeFiles(testDeps, baseDir) + val runtimeDepFiles = maybeFiles(runtimeDeps, baseDir) val sourceRootFiles = maybeDirs(sourceRoots, baseDir) val f = new File(baseDir, target) val targetDir = if (f.exists) { Some(toCanonFile(f)) } else { None } Right(ExternalConfig(Some(name), sourceRootFiles, - runtimeDepJars, compileDepJars, testDepJars, + runtimeDepFiles, compileDepFiles, testDepFiles, targetDir)) } catch { diff --git a/src/main/scala/org/ensime/config/SbtConfig.scala b/src/main/scala/org/ensime/config/SbtConfig.scala deleted file mode 100644 index 432b46202..000000000 --- a/src/main/scala/org/ensime/config/SbtConfig.scala +++ /dev/null @@ -1,3 +0,0 @@ -package org.ensime.config - -class SbtConfig(val buildScalaVersion:String) extends NotNull{} diff --git a/src/main/scala/org/ensime/server/JavaCompiler.scala b/src/main/scala/org/ensime/server/JavaCompiler.scala index 260234166..d4b86ba14 100644 --- a/src/main/scala/org/ensime/server/JavaCompiler.scala +++ b/src/main/scala/org/ensime/server/JavaCompiler.scala @@ -98,7 +98,7 @@ class JavaCompiler(config: ProjectConfig, val reportHandler: ReportHandler, val } - private def classpath = config.compilerClasspathFilenames ++ ProjectConfig.javaBootJars.map(_.getPath) + private def classpath = config.compilerClasspathFilenames ++ ProjectConfig.javaBootJars.map(_.getPath) ++ config.target.map(t => Set[String](t.getAbsolutePath)).getOrElse(Set[String]()) private val nameProvider = new NameProvider(classpath.toArray) @@ -160,7 +160,7 @@ class JavaCompiler(config: ProjectConfig, val reportHandler: ReportHandler, val } def compileFile(f: File) = { - reportHandler.clearJavaNotes(List(f.getCanonicalPath)) + reportHandler.clearAllJavaNotes() addFile(f) try { for (u <- javaUnitForFile.get(f.getCanonicalPath)) { diff --git a/src/main/scala/org/ensime/util/Reporter.scala b/src/main/scala/org/ensime/util/Reporter.scala index 2081a2e95..ac0b7ea4c 100644 --- a/src/main/scala/org/ensime/util/Reporter.scala +++ b/src/main/scala/org/ensime/util/Reporter.scala @@ -46,8 +46,9 @@ class PresentationReporter(handler:ReportHandler) extends Reporter { try { if (pos.isDefined) { val source = pos.source + val f = source.file.absolute.path val note = new Note( - source.file.absolute.path, + f, formatMessage(msg), severity.id, pos.startOrPoint,