Christophe Weblog Wiki Code Publications Music
give us a chance with utf-8
[swankr.git] / swank.R
diff --git a/swank.R b/swank.R
index 0de35f74997d9128489df019e7dec55251da00ce..dea28cf99f2f72cec6ccadc13d1266b88b571c78 100644 (file)
--- a/swank.R
+++ b/swank.R
 ### latest version of the GNU General Public Licence is available at
 ### <http://www.gnu.org/licenses/gpl.txt>.
 
+### KLUDGE: this assumes that we're being sourced with chdir=TRUE.
+### (If not, `swank:swank-require` will work under the circumstances
+### that it used to work anyway -- i.e. the working directory is the
+### swankr directory)
+swankrPath <- getwd() 
+
 swank <- function(port=4005) {
   acceptConnections(port, FALSE)
 }
 
 startSwank <- function(portFile) {
-  acceptConnections(FALSE, portFile)
+  acceptConnections(4005, portFile)
 }
 
 acceptConnections <- function(port, portFile) {
+  if(portFile != FALSE) {
+    f <- file(portFile, open="w+")
+    cat(port, file=f)
+    close(f)
+  }
   s <- socketConnection(host="localhost", server=TRUE, port=port, open="r+b")
   on.exit(close(s))
   serve(s)
@@ -51,7 +62,7 @@ dispatch <- function(slimeConnection, event, sldbState=NULL) {
 sendToEmacs <- function(slimeConnection, obj) {
   io <- slimeConnection$io
   payload <- writeSexpToString(obj)
-  writeChar(sprintf("%06x", nchar(payload)), io, eos=NULL)
+  writeChar(sprintf("%06x", nchar(payload, type="bytes")), io, eos=NULL)
   writeChar(payload, io, eos=NULL)
   flush(io)
 }
@@ -210,7 +221,14 @@ readSexpFromString <- function(string) {
     } else if(grepl("^[0-9]+\\.[0-9]+$", token)) {
       as.double(token)
     } else {
-      as.name(token)
+      name <- as.name(token)
+      if(name == quote(t)) {
+        TRUE
+      } else if(name == quote(nil)) {
+        FALSE
+      } else {
+        name
+      }
     }
   }
   readToken <- function() {
@@ -269,6 +287,7 @@ printToString <- function(val) {
 `swank:connection-info` <- function (slimeConnection, sldbState) {
   list(quote(`:pid`), Sys.getpid(),
        quote(`:package`), list(quote(`:name`), "R", quote(`:prompt`), "R> "),
+       quote(`:encoding`), list(quote(`:coding-systems`), list("utf-8-unix")),
        quote(`:lisp-implementation`), list(quote(`:type`), "R",
                                            quote(`:name`), "R",
                                            quote(`:version`), paste(R.version$major, R.version$minor, sep=".")))
@@ -276,7 +295,7 @@ printToString <- function(val) {
 
 `swank:swank-require` <- function (slimeConnection, sldbState, contribs) {
   for(contrib in contribs) {
-    filename <- sprintf("%s.R", as.character(contrib))
+    filename <- sprintf("%s/%s.R", swankrPath, as.character(contrib))
     if(file.exists(filename)) {
       source(filename)
     }
@@ -306,11 +325,15 @@ sendReplResultFunction <- sendReplResult
 `swank:listener-eval` <- function(slimeConnection, sldbState, string) {
   ## O how ugly
   string <- gsub("#\\.\\(swank:lookup-presented-object-or-lose([^)]*)\\)", ".(`swank:lookup-presented-object-or-lose`(slimeConnection, sldbState,\\1))", string)
-  expr <- parse(text=string)[[1]]
-  ## O maybe this is even uglier
-  lookedup <- do.call("bquote", list(expr))
-  value <- eval(lookedup, envir = globalenv())
-  sendReplResultFunction(slimeConnection, value)
+  for(expr in parse(text=string)) {
+    expr <- expr
+    ## O maybe this is even uglier
+    lookedup <- do.call("bquote", list(expr))
+    tmp <- withVisible(eval(lookedup, envir = globalenv()))
+    if(tmp$visible) {
+      sendReplResultFunction(slimeConnection, tmp$value)
+    }
+  }
   list()
 }
 
@@ -401,21 +424,32 @@ computeRestartsForEmacs <- function (sldbState) {
 `swank:frame-locals-and-catch-tags` <- function(slimeConnection, sldbState, index) {
   frame <- sldbState$frames[[1+index]]
   objs <- ls(envir=frame)
+  if(identical(frame, globalenv())) {
+    objs <- c()
+  }
   list(lapply(objs, function(name) { list(quote(`:name`), name,
                                           quote(`:id`), 0,
-                                          quote(`:value`), printToString(eval(parse(text=name), envir=frame))) }),
+                                          quote(`:value`),
+                                          tryCatch({
+                                            printToString(eval(parse(text=name), envir=frame))
+                                          }, error=function(c) {
+                                            sprintf("error printing object")
+                                          }))}),
        list())
 }
 
 `swank:simple-completions` <- function(slimeConnection, sldbState, prefix, package) {
-  ## fails multiply if prefix contains regexp metacharacters
-  matches <- apropos(sprintf("^%s", prefix), ignore.case=FALSE)
+  literal2rx <- function(string) {
+    ## list of ERE metacharacters from ?regexp
+    gsub("([.\\|()[{^$*+?])", "\\\\\\1", string)
+  }
+  matches <- apropos(sprintf("^%s", literal2rx(prefix)), ignore.case=FALSE)
   nmatches <- length(matches)
   if(nmatches == 0) {
     list(list(), "")
   } else {
     longest <- matches[order(nchar(matches))][1]
-    while(length(grep(sprintf("^%s", longest), matches)) < nmatches) {
+    while(length(grep(sprintf("^%s", literal2rx(longest)), matches)) < nmatches) {
       longest <- substr(longest, 1, nchar(longest)-1)
     }
     list(as.list(matches), longest)
@@ -474,17 +508,35 @@ withRetryRestart <- function(description, expr) {
 
 `swank:interactive-eval` <-  function(slimeConnection, sldbState, string) {
   withRetryRestart("retry SLIME interactive evaluation request",
-                   value <- eval(parse(text=string), envir=globalenv()))
-  prin1ToString(value)
+                   tmp <- withVisible(eval(parse(text=string), envir=globalenv())))
+  if(tmp$visible) {
+    prin1ToString(tmp$value)
+  } else {
+    "# invisible value"
+  }
 }
 
 `swank:eval-and-grab-output` <- function(slimeConnection, sldbState, string) {
   withRetryRestart("retry SLIME interactive evaluation request",
                    { output <-
-                       capture.output(value <- eval(parse(text=string),
-                                                    envir=globalenv())) })
+                       capture.output(tmp <- withVisible(eval(parse(text=string),
+                                                              envir=globalenv()))) })
   output <- paste(output, sep="", collapse="\n")
-  list(output, prin1ToString(value))
+  if(tmp$visible) {
+    list(output, prin1ToString(tmp$value))
+  } else {
+    list(output, "# invisible value")
+  }
+}
+
+`swank:interactive-eval-region` <- function(slimeConnection, sldbState, string) {
+  withRetryRestart("retry SLIME interactive evaluation request",
+                   tmp <- withVisible(eval(parse(text=string), envir=globalenv())))
+  if(tmp$visible) {
+    prin1ToString(tmp$value)
+  } else {
+    "# invisible value"
+  }
 }
 
 `swank:find-definitions-for-emacs` <- function(slimeConnection, sldbState, string) {
@@ -683,11 +735,21 @@ emacsInspect.numeric <- function(numeric) {
 }
 
 `swank:load-file` <- function(slimeConnection, sldbState, filename) {
-  source(filename, local=FALSE)
+  source(filename, local=FALSE, keep.source=TRUE)
   TRUE
 }
 
 `swank:compile-file-for-emacs` <- function(slimeConnection, sldbState, filename, loadp, ...) {
-  times <- system.time(parse(filename))
+  times <- system.time(parse(filename, srcfile=srcfile(filename)))
+  if(loadp) {
+    ## KLUDGE: inelegant, but works.  It might be more in the spirit
+    ## of things to keep the result of the parse above around to
+    ## evaluate.
+    `swank:load-file`(slimeConnection, sldbState, filename)
+  }
   list(quote(`:compilation-result`), list(), TRUE, times[3], substitute(loadp), filename)
 }
+
+`swank:quit-lisp` <- function(slimeConnection, sldbState) {
+  quit()
+}