Commits

Stephen Weeks committed 31e3de8 Merge

auto merge

  • Participants
  • Parent commits fe8910a, ab31b2a

Comments (0)

Files changed (30)

 
 # ignore Omakefile because it's different for my machine and
 # the Jane Street setup
-OMakefile 
+OMakefile
 OMakeroot
 
 *.old.*
+
+changes
+=========
+
+- There is a big change regarding window behavior.
+
+  The following variables were removed
+     Omake.show-buffer-for-next-error-in
+     Omake.set-standard-windows
+     Omake.use-display-buffer
+
+  They were replaced by
+     Omake.split-frame-horizontally
+     Omake.show-error-buffer-in-top-or-left-window
+
+  See the info documentation for the new behavior.  This change was done
+  as a uniform method for supporting the various different ways of using
+  dedicated windows vs. automatic window selection.
+
+  Non-experts should not have to do anything (though they might want to
+  remove the variables that are no longer used from their .emacs.)
+
+new stuff
+=========
+
+- Omake.set-dedicated-{code,error}-frame
+- Omake.clear-dedicated-windows
+
+--------------------------------------------------------------------------------
+
+new stuff
+=========
+- new handling of whitespace.
+   * highlighting whitespace only happens in certain modes (c-mode, tuareg-mode)
+   * characters beyond column 90 are highlighted
+   * trailing whitespace is not highlighted by default, but can be configured by
+     setting jane-highlight-whitespace-style.
+   * trailing whitespace is cleaned up on file save, only in certain modes
+      (c-mode tuareg-mode)
+
+bug fixes
+=========
+
+changes
+=======
+
+- Made elisp-for-ocaml-programmers use lexical scope for higher-order functions
+  using the macros `defunl', `letl', `letl*'.  These should be used whenever
+  you are defining a higher-order function (instead of `defun', `let', `let*')
+
+------------------------------------------------------------------------------------------
+
+new stuff
+=========
+
+- added config variable Omake.use-display-buffer, which uses emacs's built-in
+  display-buffer for showing code and error buffers, rather than code and error
+  windows (and complicated code to figure out what those windows are).
+
+new stuff
+=========
+
+- new configuration variable, Omake.prompt-before-killing-project, which is true by
+  default, but if set to false (i.e. nil), then omake-mode won't prompt when you run
+  Omake.kill-project or when you kill the [Errors] buffer.
+
+- Added three file-finding functions:
+
+    | C-c f p | Jane.find-file-in-repo       |
+    | C-c f r | Jane.find-file-in-project    |
+    | C-c f l | Jane.find-library-in-project |
+
+  The first two of these functions allow one to find a file by its name first, and path
+  second.  The range of available files is either the containing hg repo (for
+  Jane.find-file-in-repo) or the containing OMake project (for Jane.find-file-in-project).
+  The third function is similar, except rather than finding a particular file name, one
+  finds the directory containing an OCaml library name, as defined by the
+  OCamlMakeLibPackage declaration in that directory's OMakefile.
+
+  All three functions prompt in the minibuffer and support completion, so that one can
+  start entering a file name or library name, and tab-complete to see the possibilities.
+
+  The first time any of the functions is run in a repo (or project), it builds an index,
+  which may take some time (up to a few seconds for a clone of jane on a local disk).
+  Subsequent calls to the function in the same repo (or project) will reuse the index,
+  and will run instantly.
+
+- Added Omake.shutdown-and-remove-hooks.  This is an emergency command to use
+  when omake-mode goes horribly wrong, and you can't kill a buffer, or even
+  Emacs itself.  If you need to use this command, there's a bug.  Please
+  report it.  Thanks!
+
+- C-c g runs Omake.toggle-env.  This is a faster way to toggle the values
+  of environment variables.
+
+- Added an (incomplete) texinfo file, the beginning of decent documentation
+  for omake-server.
+
+- Added `Emacs.protect-from-quit' that will create a critical section
+  that can not be interrupted by `keyboard-quit'
+
+bug fixes
+=========
+
+- Discovered the `inhibit-quit' variable which allows me to protect
+  critical sections from C-g, which was helping omake-mode get into
+  broken states.
+
+- #63, #50: Reduce needless prompts.
+
+- Bug tracker cleanup
+
+^^^ in progress ^^^
+
+vvv old stuff vvv
+
+new stuff
+=========
+
+- added edit-server.el, for edit-with-emacs so we can modify it as we need
+- toggle-full-screen, does what it says on the tin
+- cr-grep, supports new cr and (I think) most use cases
+- More complete error messages in the error buffer when there's an omake error
+
+bug fixes
+=========
+
+- Fixed another permissions bug with /tmp/omake-server.  It still wasn't being
+  created with the right permissions.  It now is.
+
+- Fixed a bug where the current error would leap into the unvisited section. (#77)
+  Th e problem was that I was using the generic sxhash on errors, and a boolean
+  inside the error telling whether the error was expanded or not was thus
+  considered in the equality test.
+
+- Fixed a bug where killing emacs was difficult (#72)
+  The problem was whenever I would send a message from emacs to the server, I'd
+  ensure the server was running.  In this case I sent the "kill" message to
+  a dead server, which had the effect of attempting to restart the server.
+  But it couldn't restart because the other one was running.  Sending messages
+  to a dead server now do nothing except log to [omake-mode-log]
+
+changes
+=======
+
+- new micro-feature, Jane.omake-js, for enabling the new omake-mode.  This is enabled by
+  default in js-common, so everyone gets it.  There is another micro-feature,
+  Jane.omake-old, for the old compile behavior.
+- Jane.multiframe no longer binds C-o and has smarter frame killing
+
+bug fixes
+=========
+
+- big performance improvement
+  In a large build with dozens of errors, omake-mode was using a quadratic comparison
+  to update the error buffer.  This could peg the cpu doing error comparisons.  I fixed
+  this by hashing the errors in each error-ring, thus making the comparisons almost
+  linear, and by limiting the number of errors sent by omake-server to 50.
+  A smaller improvement was achieved by cacheing system calls to readlink that were
+  occurrring tens of thousands of times for the same argument.
+
+- "No files processeed yet"
+  If you were almost finished with a build and omake finished without emiting
+  "targets up-to-date...", omake-mode would display
+  "progress: no files processed yet, status: Success".  This is fixed by grabbing
+  the status bar if there is one.  If you build the project in an already
+  completed directory, it still shows the message, as there is no way to tell
+  how many targets there really were.
+
+bugs examined but not addressed
+===============================
+
+- flickering of C-x in minibuffer
+  This is an emacs bug that I reproduced with a very simple program using a process
+  output filter function.  I submitted an emacs bug report.
+
+- #65: omake-server mistakenly thought emacs was dead
+  I can't reproduce this, though it happened to both Stephen and Charles.
+  The code is dead simple, and I'm not sure how I could have messed it up.
+  omake-server calls 'kill -0 EMACS-PID' to make sure the emacs that started
+  the server is alive.  If that process returns non-zero, it kills the server.
+  I've asked Stephen to look at the relevant section of the code.
+
+  2012-03-28 sweeks: I changed how we send the signal, to see if that changes behavior or
+  gives more info when the badness happens.
+
+changes
+=======
+- deleted the deprecated js-compile
+- deprecated Jane.whitespace-fancy and Jane.whitespace-simple.  There remains only
+  Jane.whitespace.
+- added Jane.prefer-other-visible-frame
+- added Jane.pages for more obvious ^L
+- for OCaml code, made underscore configurable as a word boundary,
+  with Js.underscore-is-{word,punctuation}.
+- Omake mode
+  * now configurable whether to use dark or light faces, via Omake.use-{dark,light}-faces.
+  * no longer shows Error-enabled warnings.
+  * now sorts errors by line number within a file.
+  * [Errors] buffer now includes the path to the compilation dir.
+  * [Raw] buffer is now read-only.
+  * now has a configuration parameter to get the old window setup. Add to your .emacs:
+
+    (setq Omake.set-standard-windows t)
+
+    ---------------------------------------
+    |                  |                  |
+    |                  |                  |
+    |                  |                  |
+    |      CODE        |     ERRORS       |
+    |                  |                  |
+    |                  |                  |
+    |                  |                  |
+    ---------------------------------------
+
+bug fixes
+=========
+- fixed Jane.desktop
+- cr-todo wasn't working with next-error
+- #34 [Errors] buffers appears to be spinning, but omake is stopped
+- #44 /tmp/omake-server has bad permissions
+  omake now makes /tmp/omake-server with permissions 775
+- #56 emacsclient can kill connection between emacs and omake_server
+  There was a situation where an emacs daemon killed the server process.
+  Fixed by handling sigterm in the server.
+
+elisp changes
+=============
+- created elisp-for-ocaml-programmers.el, which has all of the ocaml-style utility
+  functions
+- switching from using ;;---------- lines to ^L to indicate pages
+- revived js-examples.el
+- re-alphabetized micro-features
+
+================================================================================
+2012-02-19 Rolled rev 0bf96ab189ff to production
+================================================================================
+
 changes
 =======
 - allowed Omake.setenv to run in a project that is building
 	$(OCAMLOPT) -c $<
 
 omake_server.exe : process.cmx omake.cmx omake_server.cmx
-	$(OCAMLOPT) -linkpkg -o omake_server.exe $^
+	$(OCAMLOPT) -linkpkg -o $@ $^
 
 # Check whether the files compile without warnings or errors
 

File OMakefile.inside-jane-street

   core
   core_extended
   pcre
+  version_util
 
 EXES[] =
   omake_server
+  window_selection
 
 FILES[] =
   $(EXES)

File RELEASE_PROCEDURE

+-*- mode:org -*-
+
+Tests
+=====
+
+Compile
+--------
+
+* C-cC-c (Omake.compile) in a dired buffer
+* C-cC-c in a file
+* Repeatedly run C-cC-c and C-g for stress test
+* C-cC-c in an [Errors] buffer
+* C-cC-c in another directory that is not a subdir of a running
+  session, to make sure it will kill the other running session.
+
+Next-error
+----------
+
+* Make sure errors from the file you're visiting show up first
+* Cycle through all the errors and make sure you get what you expect
+
+Variables
+---------
+
+* Try getenv, setenv, and toggle with restarts to make sure the variables
+  are being set properly
+
+* Window selection
+
+F1   = F1 contents before next-error
+F2   = F2 contents before next-error
+n-e  = frame in which I ran next-error
+F1'  = F1 contents after next-error
+F2'  = F2 contents after next-error
+s-w' = the selected window after next-error
+X    = I think disagrees with the spec
+
+Experiments with two frames
+
+ | F1        | F2        | n-e | F1'       | X | F2'       | X | s-w' | X |
+ |-----------+-----------+-----+-----------+---+-----------+---+------+---|
+ | code      | *scratch* | F1  | split     |   | *scratch* |   | code |   |
+ | *scratch* | code      | F2  | *scratch* |   | split     |   | code |   |
+ | code      | [Errors]  | F1  | code      |   | [Errors]  |   | code |   |
+ | code      | [Errors]  | F2  | code      |   | [Errors]  |   | code |   |
+ | [Errors]  | code      | F1  | [Errors]  |   | code      |   | code |   |
+ | [Errors]  | code      | F2  | [Errors]  |   | code      |   | code |   |
+ | code      | code      | F1  | split     |   | code      |   | code |   |
+ | code      | code      | F2  | code      |   | split     |   | code |   |
+ | [Errors]  | [Errors]  | F1  | split     |   | [Errors]  |   | code |   |
+
+Experiments with a single frame
+
+ | W1        | W2        | n-e | W1'      | X | W2'      | X | s-w' | X |
+ |-----------+-----------+-----+----------+---+----------+---+------+---|
+ | code      | <none>    | W1  | code     |   | [Errors] |   | code |   |
+ | code      | *scratch* | W1  | code     |   | [Errors] |   | code |   |
+ | *scratch* | code      | W2  | [Errors] |   | code     |   | code |   |
+ | code      | [Errors]  | W1  | code     |   | [Errors] |   | code |   |
+ | code      | [Errors]  | W2  | code     |   | [Errors] |   | code |   |
+ | [Errors]  | code      | W1  | [Errors] |   | code     |   | code |   |
+ | [Errors]  | code      | W2  | [Errors] |   | code     |   | code |   |
+ | code      | code      | W1  | code     |   | [Errors] |   | code |   |
+ | code      | code      | W2  | [Errors] |   | code     |   | code |   |
+
+

File deprecated/deprecated.el

Empty file added.

File deprecated/deprected.el

-
-(defun hg-resolve-file ()
-  (interactive)
-  (Hg.resolve-file)
-  (message "hg-resolve-file is deprecated.  Use Hg.resolve-file"))
-
-(defun hg-resolve-file-dired ()
-  (interactive)
-  (Hg.resolve-file-dired)
-  (message "hg-resolve-file-dired is deprecated.  Use Hg.resolve-file-dired"))

File deprecated/js-omake-deprecated.el

-
-;; Known issues:
-;;
-;; - You can't run js-compile for a new project when in an existing
-;;   compilation buffer.  This is because we call `compile' which
-;;   prompts and then kills the current process.  Fixing this requires
-;;   either hijacking `compilation-start' or re-implementing a lot
-;;   of nice stuff `compile' gives you.
-
-;(require 'js-lib)
-(require 'compile)
-
-;;----------------------------------------------------------------------------;;
-;; Util                                                                       ;;
-;;----------------------------------------------------------------------------;;
-
-(defun List.nth (n l) (elt l n))
-
-;;----------------------------------------------------------------------------;;
-;; Varibles                                                                   ;;
-;;----------------------------------------------------------------------------;;
-
-(defcustom js-next-error-program
-  "/mnt/global/base/bin/omake-next-error.exe"
-  "location of the executable program to find the next error"
-  :group 'js
-  :type '(alist :key-type symbol :value-type string))
-;;(setq js-next-error-program "/mnt/global/dev/bin/omake-next-error.exe")
-
-(defcustom js-omake-command
-  "jomake -j 12 -P -w"
-  "default omake command"
-  :group 'js
-  :type 'string)
-;;(setq js-omake-command "jomake -j 12 -P -w")
-
-(defcustom js-confirm-command
-  nil
-  "Show the command that will be executed to compile before starting"
-  :group 'js
-  :type 'boolean)
-
-;;(setq js-confirm-command t)
-
-;;----------------------------------------------------------------------------;;
-;; Faces                                                                      ;;
-;;----------------------------------------------------------------------------;;
-
-(defface js-omake-error-face
-  '((t (:foreground "red")))
-  "Face for error highlighting"
-  :group 'js)
-
-(defface js-omake-success-face
-  '((t (:foreground "green")))
-  "Face for success highlighting"
-  :group 'js)
-
-(defface js-omake-waiting-face
-  '((t (:foreground "yellow")))
-  "Face for success highlighting"
-  :group 'js)
-
-(defface js-omake-marker-face
-  '((t (:foreground "purple")))
-  "Face for RUNNING/DONE in the menu bar"
-  :group 'js)
-
-(defface js-omake-finished-face
-  '((t (:foreground "cyan")))
-  "Face for RUNNING/DONE in the menu bar"
-  :group 'js)
-
-;;----------------------------------------------------------------------------;;
-;; Util                                                                       ;;
-;;----------------------------------------------------------------------------;;
-
-;; If someone calls next error from a place without an OMakeroot,
-;; call it from the last known place with an OMakeroot
-(setq js-last-known-directory-with-omakeroot nil)
-
-(defun js-omake-root ()
-  "Get the omake root directory (the one with OMakeroot).  Returns
-nil if there is no such directory."
-  (interactive)
-  (let ((root (locate-dominating-file default-directory "OMakeroot")))
-    (when root (setq js-last-known-directory-with-omakeroot root))
-    root))
-
-(defun js-infer-directory ()
-  (interactive)
-  (let* ((root (js-omake-root))
-         (root
-          (if root root
-            js-last-known-directory-with-omakeroot)))
-    (if root root
-      (error "Can not locate omakeroot"))))
-
-(defun js-compilation-buffer ()
-  (concat "*compilation-" (js-infer-directory) "*"))
-
-(defun js-compilation-window ()
-  "Find the compilation window, even if it's in another frame."
-  (let* ((buf (js-compilation-buffer))
-         (f (List.find (lambda (f) (get-buffer-window buf f)) (frame-list))))
-    (when f
-      (get-buffer-window buf f))))
-
-(defun js-recenter-error-window ()
-  "center the error window at js-mark"
-  (let ((window (js-compilation-window)))
-    (set-window-point window (js-mark))
-    (condition-case _
-        (save-selected-window
-          (select-window window)
-          (recenter))
-      (error nil))))
-
-(defun delete-overlay-soon (overlay)
-  "Set an overlay for some period of time"
-  (unwind-protect
-      (sit-for 60)
-    (delete-overlay overlay)))
-
-;;----------------------------------------------------------------------------;;
-;; Polling                                                                    ;;
-;;----------------------------------------------------------------------------;;
-
-(defun js-polling-p ()
-  "True iff omake is finished compiling"
-  (let* ((orig-pt (point))
-         (_ (goto-char (point-max)))
-         (res (search-backward "*** omake: polling for filesystem changes" nil t))
-         (res (when res
-                (forward-line 1)
-                (not (search-forward "*** omake: " nil t)))))
-    (goto-char orig-pt)
-    res))
-
-(defun js-poll-compile-status ()
-  "Set the compilation status of omake in the mode line"
-  (interactive)
-  ;; omake is always 'Compiling', so leave it out of the
-  ;; mode line
-  (setq compilation-in-progress nil)
-  ;; (message "polling for compilation status...")
-  (when (js-compilation-buffer)
-    (with-current-buffer (js-compilation-buffer)
-      (let* ((polling (js-polling-p))
-             (msg (if polling ":DONE" ":RUNNING")))
-        (setq mode-line-process (propertize msg 'face 'js-omake-marker-face))))))
-
-;;(cancel-function-timers 'js-poll-compile-status)
-
-;;----------------------------------------------------------------------------;;
-;; Projects                                                                   ;;
-;;----------------------------------------------------------------------------;;
-
-(defun js-compile ()
-  "Compile the current directory."
-  (interactive)
-  (js-set-mark (point-min))
-  (catch 'exit
-    (let* ((comp-name (js-compilation-buffer))
-           (buf (get-buffer comp-name))
-           (compilation-buffer-name "*compilation*"))
-      (when buf
-        (let ((kill (y-or-n-p (format "A compilation process for %s is already running.  Do you wish to kill it? " comp-name))))
-          (if kill (kill-buffer buf)
-            (throw 'exit t))))
-      (let ((command
-             (if js-confirm-command
-                 (read-from-minibuffer "command: " js-omake-command)
-               js-omake-command)))
-        (compile command))
-      (with-current-buffer compilation-buffer-name
-        (rename-buffer comp-name)
-        (setq mode-line-process nil)
-        (js-set-next-error-function))
-      ;; Start an asyncronous polling loop
-      (run-with-timer 0 1 'js-poll-compile-status)
-      )))
-
-;;----------------------------------------------------------------------------;;
-;; Marker                                                                     ;;
-;;----------------------------------------------------------------------------;;
-
-(defvar js-marker (make-marker)
-  "There is a single marker in the current compilation buffer that
-tracks where the user is.  I don't want to use the normal mark
-since it can be changed by other things")
-
-(defun js-mark ()
-  (interactive)
-  (Option.value (marker-position js-marker) 0))
-
-(defun js-set-mark (char)
-  ;;(message (format "setting js-mark: old=%s, new=%s" (js-mark) char))
-  (set-marker js-marker char))
-
-;;----------------------------------------------------------------------------;;
-;; Versioning                                                                 ;;
-;;----------------------------------------------------------------------------;;
-
-;; set a timestamp when js-omake is loaded.  This is used to determine
-;; if it should be reloaded if the next-error function is newer than
-;; the library.  We can't use defvar, as when the library is reloaded
-;; the value won't change.
-(setq js-omake-timestamp (float-time (current-time)))
-
-(defun js-maybe-reload-library ()
-  "Reload this library if the modification time of the executable is
-older than the library"
-  (let* (;; The 5th field is the modification time returned by file-attributes
-         (mod-time-field 5)
-         (mod-time
-          (float-time
-           (List.nth mod-time-field (file-attributes js-next-error-program)))))
-    (when (< js-omake-timestamp mod-time)
-      (message "Warning: js-next-error-program is newer than the js-omake
-library load time.  Reloading library...")
-      (load-library "js-omake"))))
-
-;;----------------------------------------------------------------------------;;
-;; Next error                                                                 ;;
-;;----------------------------------------------------------------------------;;
-
-(defvar js-result nil "The value that gets set by omake-ocaml")
-
-(defun js-format-int (num)
-  "We need to pass negative numbers -N as ~N so the argument is
-not interpreted as a flag"
-  (if (< num 0)
-      (format "~%d" (- num))
-    (format "%d" num)))
-
-(defun remove-multi-byte-chars (str)
-  "Pcre falls over when there are multi-byte chars in the omake buffer"
-  (let ((multi-map '(("‘" . "'")
-                     ("’" . "'")
-                     )))
-    (reduce 
-     (lambda (s m)
-       (let* ((rex (car m))
-              (rep (cdr m)))
-         (replace-regexp-in-string rex rep s)))
-     multi-map
-     :initial-value str)))
-
-;; (replace-regexp-in-string "‘" "'" "abc‘def‘")
-;; (remove-multi-byte-chars "abc‘def‘")
-
-(defun js-call-next-error-program (num)
-  "Write the contents of the compilation buffer to a temp
-file FILE, call js-next-error-program on the file, which
-generates FILE-result which sets some variables.
-Load the generated file."
-  (interactive "P")
-  (js-maybe-reload-library)
-  (let* ((root (js-omake-root))
-         (buf (js-compilation-buffer))
-         (buf-contents (with-current-buffer buf (buffer-substring (point-min) (point-max))))
-         (buf-contents (remove-multi-byte-chars buf-contents))
-         (mark (format "%d" (js-mark)))
-         (num (js-format-int num))
-         (prefix "/tmp/omake-emacs-")
-         (buffer-file (make-temp-file prefix))
-         (result-file (concat buffer-file ".result")))
-    (unwind-protect
-        (progn
-          (with-temp-buffer
-            ;; Grab the compilation buffer
-            (insert buf-contents)
-            (goto-char (point-max))
-            ;; Write to disk
-            (write-region (point-min) (point-max) buffer-file t))
-          ;; Run the ocaml program
-          (let ((res (call-process js-next-error-program nil nil nil "main" buffer-file root mark num)))
-            (if (equal res 0)
-                (load-file result-file)
-              (error "js-next-error-program failed"))))
-          ;; Uncomment this when I don't need the files for debugging
-          ;;(call-process "rm" nil nil nil "-f" buffer-file result-file))
-          buffer-file)))
-
-(defun js-handle-tag (rex msg face)
-  "`js-next-error-program' returns a tag giving the next action emacs
-should take.  Handle this tag here."
-  (with-current-buffer (js-compilation-buffer)
-    (remove-overlays (point-min) (point-max))
-    (goto-char (point-max))
-    (search-backward-regexp rex nil t)
-    (beginning-of-line)
-    (let* ((left (point))
-           (right (point-max))
-           (overlay (make-overlay left right (current-buffer))))
-      (overlay-put overlay 'face face)
-      (js-set-mark (point-max))
-      (js-recenter-error-window)
-      (message msg))))
-
-(defun js-success ()
-  (js-handle-tag
-   "\\*\\*\\* omake: done"
-   "Compilation was successful!"
-   'js-omake-success-face))
-
-(defun js-waiting ()
-  (js-handle-tag
-   "\\*\\*\\* omake: rebuilding"
-   "Waiting for omake to locate compilation errors"
-  'js-omake-waiting-face))
-
-(defun js-finished ()
-  (js-handle-tag
-   "\\*\\*\\* omake: polling for filesystem changes"
-   "No new errors"
-   'js-omake-finished-face))
-
-(defun js-omake-errors ()
-  (js-handle-tag
-   "\\*\\*\\* omake: polling for filesystem changes"
-   "There were omake errors"
-   'js-omake-error-face))
-
-(defun js-handle-js-result ()
-  "Do the right thing with the value of js-result, set
-by omake-next-error.exe"
-  (case js-result
-    (nil (error "js-result is not set"))
-    ('successful (js-success))
-    ('omake-errors (js-omake-errors))
-    ('finished (js-finished))
-    ('waiting (js-waiting))
-    (otherwise
-     (let* ((buffer-position (List.nth 0 js-result))
-            (file (List.nth 1 js-result))
-            (line (List.nth 2 js-result))
-            (char-beg (List.nth 3 js-result))
-            (char-end (List.nth 4 js-result)))
-       ;;(message (format "buffer-position: %d" buffer-position))
-       ;; Goto compilation buffer
-       ;;(setq buffer-position 6455)
-       (with-current-buffer (js-compilation-buffer)
-         (remove-overlays (point-min) (point-max))
-         ;; emacs starts counting positions at 1
-         (goto-char (1+ buffer-position))
-         ;; Highlight the error
-         (save-excursion
-           (end-of-line)
-           (let* ((right (point))
-                  (overlay (make-overlay buffer-position right (current-buffer))))
-             (overlay-put overlay 'face 'js-omake-error-face)))
-         ;; Goto error line
-         (forward-line 1)
-         (js-set-mark (point))
-         (js-recenter-error-window))
-       ;; Open the file with the error
-       (find-file file)
-       ;; Go to the error position
-       (goto-char (point-min))
-       (forward-line (1- line))
-       (forward-char char-beg)
-       ;; Add an overlay
-       (remove-overlays (point-min) (point-max))
-       (let* ((left (point))
-              (right (+ left (- char-end char-beg)))
-              (overlay (make-overlay left right (current-buffer))))
-         (overlay-put overlay 'face 'js-omake-error-face)
-         (delete-overlay-soon overlay))))))
-
-(defun js-next-error-function (num &optional reset)
-  (interactive)
-  (when reset (js-set-mark (point-min)))
-  (js-call-next-error-program num)
-  (js-handle-js-result))
-
-(defun js-set-next-error-function ()
-  "Set the next-error function in the compilation buffer.
-
-Note: We're making this interactive because sometimes the next-error
-function gets reset back to `compilation-next-error'.  We can't
-understand why.  As a workaround, call this function to reset the
-variable in the compilation buffer"
-  (interactive)
-  (with-current-buffer (js-compilation-buffer)
-    (setq next-error-function 'js-next-error-function)))
-
-(add-hook 'compilation-mode-hook
-          (lambda ()
-            ;; Turn off the spell checker
-            (flyspell-mode -1)
-            ;; compilation-buffer-name-function must be nil
-            (setq compilation-buffer-name-function nil)
-            ;; This keeps next-error from recentering the error region.
-            (setq next-error-recenter nil)))
-
-(defun js-compilation-other-window ()
-  (interactive)
-  (set-window-buffer (next-window) (js-compilation-buffer)))
-
-(provide 'js-omake-deprecated)

File info/Makefile

+omake-server.info: omake-server.texi
+	makeinfo $<
+	if [ -n "$$(hg st -m $@)" ]; then hg com -m 'rebuilt $@' $@; fi
+
+clean :
+	-rm omake-server.info
+This is the file .../info/dir, which contains the topmost node of the
+Info hierarchy.  The first time you invoke Info you start off
+looking at that node, which is (dir)Top.
+
+File: dir	Node: Top	This is the top of the INFO tree
+
+  This (the Directory node) gives a menu of major topics.
+  Typing "q" exits, "?" lists all Info commands, "d" returns here,
+  "h" gives a primer for first-timers,
+  "mEmacs<Return>" visits the Emacs topic, etc.
+
+  In Emacs, you can click mouse button 2 on a menu item or cross reference
+  to select it.
+
+* Menu:
+
+Jane
+
+* OMake server: (omake-server).

File info/omake-server.info

Binary file added.

File info/omake-server.texi

+
+@c -----------------------------------------------------------------------------
+@c  Header
+@c -----------------------------------------------------------------------------
+
+\input texinfo
+
+@setfilename omake-server.info
+@settitle OMake Server Reference Manual
+
+@set VERSION 3.0
+@set EMACSVER 23.3
+@set DATE April 2012
+
+@c Combine indices.
+@synindex cp fn
+@syncodeindex vr fn
+@syncodeindex ky fn
+@syncodeindex pg fn
+
+@dircategory OMake Server
+@direntry
+* Omake Server: (omake-server). The OMake Server Manual.
+@end direntry
+
+@titlepage
+@title An OMake interface for Emacs
+@subtitle For Emacs Version @value{EMACSVER}
+@subtitle Revision @value{VERSION}, @value{DATE}
+@author by Sean McLaughlin and Stephen Weeks
+@page
+@vskip 0pt plus 1filll
+@end titlepage
+
+@c Output the table of contents at the beginning.
+@summarycontents
+@contents
+
+@c -----------------------------------------------------------------------------
+@c  Top
+@c -----------------------------------------------------------------------------
+
+@node Top
+@top OMake Server
+
+OMake (http://omake.metaprl.org/index.html) is a compilation manager
+used, among other things, to build large OCaml programs.  OMake Server
+is a combined OCaml/Emacs-Lisp program that manages groups of OMake
+compilations and provides an Emacs interface to the server.
+
+@menu
+
+Getting started
+* Quick Start:: How to begin compiling quickly.
+
+Important General Concepts
+* Projects:: A project corresponds to an OMakeroot file
+* Buffers:: Specialized buffers for Omake-mode
+* Windows:: Where buffers are displayed
+
+User Interface
+* User Interface:: Variables and functions
+* Keybindings:: For commonly used functions
+
+Appendices
+* Index:: Index including concepts, functions, variables, and other terms.
+* Acknowledgments:: Major contributors to omake-server.
+
+@end menu
+
+@c -----------------------------------------------------------------------------
+@c  Index
+@c -----------------------------------------------------------------------------
+
+@node Index
+@unnumbered Index
+@printindex fn
+
+@c -----------------------------------------------------------------------------
+@c  Keybindings
+@c -----------------------------------------------------------------------------
+
+@node Keybindings
+@unnumbered Keybindings
+@itemize
+@item @kbd{C-c C-c} @kbd{Omake.compile}
+@item @kbd{C-c C-d} @kbd{Omake.set-dedicated-error-window}
+@item @kbd{C-c C-e} @kbd{Omake.show-error-buffer}
+@item @kbd{C-c C-h} @kbd{Omake.toggle-expanded-error}
+@item @kbd{C-c C-l} @kbd{Omake.next-error}
+@item @kbd{C-c C-r} @kbd{Omake.show-raw-buffer}
+@item @kbd{C-c C-t} @kbd{Omake.toggle-env}
+@item @kbd{C-c C-v} @kbd{Omake.toggle-verbose}
+@end itemize
+
+@c -----------------------------------------------------------------------------
+@c  Quick Start
+@c -----------------------------------------------------------------------------
+
+@node Quick Start
+@unnumbered Quick Start
+
+To start a compilation, open Emacs and open the dired buffer of
+the directory you want to compile.  Then do @kbd{C-c C-c} to run the
+command @kbd{M-x Omake.compile}.  This will start a compilation in that
+directory, and create a new window which shows the @ref{Errors Buffer}.
+
+@c -----------------------------------------------------------------------------
+@c  User Interface
+@c -----------------------------------------------------------------------------
+
+@node User Interface
+@unnumbered User Interface
+
+@section Common commands
+
+@itemize
+@item @kbd{M-x Omake.compile}
+@kindex C-c C-c
+
+Compile a project.  Open a file in (or dired buffer of) the directory you want to compile
+and do @kbd{M-x Omake.compile} (or @kbd{C-c C-c})
+
+@item @kbd{M-x Omake.show-error-buffer}.
+
+Show the error buffer in the dedicated error window.
+
+@item @kbd{M-x Omake.toggle-verbose}.
+
+Show verbose data in the error buffer.
+
+@item @kbd{M-x Omake.shutdown}.
+
+Shutdown the OMake server.
+
+@end itemize
+
+@section Managing environment variables
+
+@itemize
+
+@item @kbd{M-x Omake.setenv}.
+
+Set an OMake environment variable.
+
+@item @kbd{M-x Omake.getenv}.
+
+Display the value of an OMake environment variable.
+
+@item @kbd{M-x Omake.toggle-env}.
+
+Toggle an OMake environment variable.
+
+@end itemize
+
+@section Customization
+
+@itemize
+
+@item @kbd{M-x Omake.set-dedicated-error-window}.
+
+Set the Emacs window where errors are displayed.
+
+@item @kbd{M-x Omake.set-standard-windows}.
+
+Split a single frame into two side-by-side windows, with code on the
+left and errors on the right.
+
+@item @kbd{M-x Omake.set-server-program}.
+
+Customize the omake program that compiles your code.
+
+@item @kbd{M-x Omake.use-light-faces}.
+
+Use faces for a light background.
+
+@item @kbd{M-x Omake.use-dark-faces}.
+
+Use faces for a dark background.
+
+@end itemize
+
+@section Occasionally useful
+
+@itemize
+
+@item @kbd{M-x Omake.reset-visited}.
+
+Reset the visited error ring.
+
+@item @kbd{M-x Omake.toggle-expanded-error}.
+
+Toggle whether large errors are expanded or not.
+
+@item @kbd{M-x Omake.notify-maintainer-of-error}.
+
+Submit a bug report.
+
+@end itemize
+
+@section Debugging
+
+@itemize
+
+@item @kbd{M-x Omake.show-elisp-buffer}.
+
+Show the last elisp code from omake-server that was evaled by Emacs.
+
+@item @kbd{M-x Omake.show-server-log}.
+
+Show the omake-server global log.
+
+@item @kbd{M-x Omake.show-project-log-buffer}.
+
+Show the omake-server log for a given project.
+
+@item @kbd{M-x Omake.show-raw-buffer}.
+
+Show the raw buffer.  Used when omake-mode can't extract an error.
+
+@item @kbd{M-x Omake.kill-project}.
+
+Kill a project.  Normally you should just kill the Errors buffer.
+
+@end itemize
+
+@node Projects
+@unnumbered Projects
+
+@node Buffers
+@chapter Buffers
+
+@menu
+* Errors Buffer::
+* Raw Buffer::
+@end menu
+
+@node Errors Buffer
+@section Errors Buffer
+
+@node Raw Buffer
+@section Raw Buffer
+
+@c -----------------------------------------------------------------------------
+@c  Windows
+@c -----------------------------------------------------------------------------
+
+@node Windows
+@chapter Windows
+
+In this section we describe the algorithm for determining in which windows
+to show the various omake-server buffers.  By ``windows'' we mean
+Emacs windows. (@ref{Windows, , , emacs})  This is in contrast to X windows, which
+correspond to Emacs frames. (@ref{Frames, , , emacs})  The
+@emph{selected window} is the window that currently has the cursor in
+it.  The @emph{selected frame} is the frame that currently has the cursor.
+
+@section Dedicated windows and frames
+
+There are a number of ways to display the error and code buffers.
+If you always want them to appear in the same window
+(resp. frame), use the following functions.
+
+@defun Omake.set-dedicated-error-window
+Always show errors in this window.
+@end defun
+
+@defun Omake.set-dedicated-code-window
+Always show code in this window.
+@end defun
+
+@defun Omake.set-dedicated-error-frame
+Always show errors in some window in this frame.
+@end defun
+
+@defun Omake.set-dedicated-code-frame
+Always show code in some window in this frame.
+@end defun
+
+@noindent
+(Note that if you set a dedicated window (frame), and subsequently
+delete it, there is no longer a dedicated window.)  Depending on
+personal preference, there are reasons for setting a dedicated frame
+rather than a dedicated window and vice versa.  Frames have multiple
+windows which makes windows more precise, but frames are created and
+deleted much less frequently than windows, which means they will
+disappear less often.
+
+@noindent
+We use the following abbreviations to describe the algorithm that
+follows.
+@verbatim
+DCW       = dedicated code window
+DCF       = dedicated code frame
+DEW       = dedicated error window
+DEF       = dedicated error frame
+SW        = selected window
+SF        = selected frame
+EB        = errors buffer
+CB        = code buffer
+frame(W)  = the frame of window W
+# CR sweeks: window(B) is ill-defined.  There can be multiple windows showing a buffer.
+window(B) = the window of buffer B
+buffer(W) = the buffer of window W
+split(W)  = the new window resulting from the split of existing window W
+lru(F)    = least recently used window of frame F
+EF        = final frame where EB is shown
+CF        = final frame where CB is shown
+EW        = final window where EB is shown
+CW        = final window where CB is shown
+@end verbatim
+
+The purpose of this section is to determine EW and CW from DCW, DCF,
+ECW, ECF, SW, SF.  Throughout this discussion, we assume that the DEW is
+distinct from the DCW.  (This is enforced by omake-server.)  We do not
+assume that buffer(DCW) <> EB.  It is easy
+to imagine that someone manually switched to EB from DCW.
+
+When we write ``split(W)'' we are always splitting a frame with
+a single window, and we split it according to the variable
+@code{Omake.split-window-horizontally}.  We assure that EW<>CW.
+
+@section Choosing the frame
+
+Choosing the correct frames for CW and EW is straightforward.
+
+@subsection Choosing CF
+
+If there is a DCW, CF=frame(DCW). @*
+If there is a DCF, CF=DCF. @*
+Otherwise CF=SF.
+
+@subsection Choosing EF
+
+If there is a DEW, EF=frame(DEW). @*
+If there is a DEF, EF=DEF. @*
+Otherwise EF=SF.
+
+@subsection Choosing CW and EW
+
+Assume EF and CF are determined as above.
+@verbatim
+If EF and CF are distinct, we can select EW and CW independently.
+
+Case: EF and CF are distinct.
+  [EW]
+  Case: DEW exists
+    EW=DEW
+  Case: There exists W in EF such that buffer(W) = EB
+    EW=W
+  Case:
+    EW=lru(EF)
+
+  [CW]
+  Case: DCW exists
+    EW=DCW
+  Case: SW in CF.
+    EW=SW
+  Case:
+    EW=lru(CF)
+
+If EF=CF, we have to make sure the choices are not interfering
+with each other to maintain CW<>EW.
+
+Case: EF=CF(=F)
+  Case: DEW and DCW exist (DCW <> DEW by assumption)
+     EW=DEW
+     CW=DCW
+  Case: DEW exists
+     EW=DEW
+     Case: SW<>DEW in F
+        CW=SW
+     Case: There exists W <> DEW in F
+        CW=W
+     Case: F is a single window, DEW.
+        CW=split(DEW).
+  Case: DCW exists
+     CW=DCW
+     Case: There exists W <> DCW in F such that buffer(W)=EB.
+        EW=W
+     Case: There exists W <> DCW in F
+        EW=W
+     Case: F is a single window, DCW
+        EW=split(DCW).
+  Case: There exists W in F such that buffer(W)=EB.
+     EW=W
+     Case: SW<>W in F
+        CW=SW
+     Case: There exists W' <> W in F
+        CW=W'
+     Case: F is a single window, W
+        CW=split(W)
+  Case: SW in F (no dedicated or error windows)
+     CW=SW
+     Case: There exists W <> SW in F
+        EW=W
+     Case: F is a single window, SW
+        EW=split(SW)
+  Case: True (no dedicated, selected, or error windows)
+     CW=lru(F)
+     Case: There exists W <> CW in F
+       EW=W
+     Case: F is a single window, CW
+       EW=split(CW)
+@end verbatim
+
+In the case that the frame F contains exactly two windows, one showing
+the error buffer and the other showing the code buffer, and if no
+dedicated windows exist, then arrange the windows according to the
+variable @code{Omake.show-error-buffer-in-top-or-left-window} as follows.
+
+@subsection Errors buffer
+
+@defopt Omake.split-window-horizontally
+  If nil, omake-server will split the window vertically.  Otherwise
+it will split it horizontally.
+@end defopt
+
+@defopt Omake.show-error-buffer-in-top-or-left-window
+  When omake-server splits the window, it must choose whether to
+put the errors in the left (resp. top) or the right (resp. bottom)
+window.  If nil, show the errors in the bottom (resp. right) window.
+Otherwise show the errors in the top (resp. left) window.
+@end defopt
+
+@noindent For example, suppose in your .emacs you have:
+
+@lisp
+(setq Omake.split-window-horizontally t)
+(setq Omake.show-error-buffer-in-top-or-left-window nil)
+@end lisp
+
+@noindent and have the following window before the split:
+@verbatim
+|-----------------|
+|                 |
+|     foo.ml      |
+|                 |
+|-----------------|
+@end verbatim
+
+@noindent After Omake.next-error you should see the following:
+@verbatim
+|--------|--------|
+|        |        |
+| foo.ml | errors |
+|        |        |
+| -------|--------|
+@end verbatim
+
+@noindent The settings
+
+@lisp
+(setq Omake.split-window-horizontally nil)
+(setq Omake.show-error-buffer-in-top-or-left-window t)
+@end lisp
+
+@noindent would show
+@verbatim
+|-----------------|
+|     errors      |
+|-----------------|
+|     foo.ml      |
+| ----------------|
+@end verbatim
+
+@c -----------------------------------------------------------------------------
+@c  Acknowledgments
+@c -----------------------------------------------------------------------------
+
+@node Acknowledgments
+@unnumbered Acknowledgments
+
+This manual was written by Sean McLaughlin and Stephen Weeks.
+Dmitry Astapov,
+Sam Erlichman,
+David House,
+Yaron Minsky,
+Elnatan Reisner,
+and Martin Willensdorfer
+made valuable comments and improvements.
+
+@bye
     read
 fi
 
+make -C info
+
 exe=omake_server.exe
 
 echo "Installing $exe for mode: $mode"
 
 case $mode in
     prod)
-        loc="hkg,ldn,nyc"
         installed_exe=$exe
         ;;
     test)
-        loc="nyc"
         installed_exe=omake_server_test.exe
         ;;
 esac
 
 hg push roll-$mode
-jadmin install to-merge -loc $loc $exe /mnt/global/base/bin/$installed_exe
+jadmin install to-merge -loc 'hkg,ldn,nyc' $exe /mnt/global/base/bin/$installed_exe
+
+;; This file contains utilities for emacs-lisp programming with the
+;; function naming conventions of OCaml code at Jane Street.  The most
+;; important features are `defunl' and `letl' for defining lexically
+;; scoped functions.
+
+;;; NOTES:
+;;
+;; Conventions due to dynamic scope hell.
+;;
+;; We must be careful with function arguments and local variables due to dynamic scope.
+;; (defun List.iter (f l)
+;;   (mapc f l)
+;;   nil)
+;;
+;; (defun List.iteri (f l)
+;;   (let ((ctr 0))
+;;     (List.iter (lambda (b)
+;;                  (funcall f ctr b)
+;;                  (setq ctr (+ ctr 1))) l)))
+;;
+;; This definition of List.iteri fails.  In (List.iteri g l)
+;; `f' is bound to g in the beginning of the body of List.iteri, but when List.iter
+;; is called, f gets rebound to (lambda (b) ...).  Thus the funcall fails
+;; because it expects only 1 argument.
+;;
+;; For example
+;;
+;; (defun List.iteri (f l)
+;;   (let ((ctr 0))
+;;     (List.iter (lambda (x)
+;;                  (funcall f ctr x)
+;;                  (setq ctr (+ ctr 1))) l)))
+;;
+;; (let ((ctr 0))
+;;   (List.iteri (lambda (i x) (setq ctr (+ ctr i x))) '(1 1 1 1 1))
+;;   (Jane.test ctr 15))
+;;
+;; This test fails, as `ctr' is rebound in the body of List.iteri.
+;;
+;; To avoid this, for any higher-order function we use `letl'
+;; to define local variables and `defunl' to define the function.
+
+;; Don't show warnings that cl is being used at runtime
+;; Using the cl package at runtime is considered bad style among
+;; purist emacs-lisp hackers.
+;; http://www.gnu.org/software/emacs/manual/html_node/cl/Overview.html
+;; In particular:
+;;
+;;   Please note: the CL functions are not standard parts of the Emacs Lisp name
+;;   space, so it is legitimate for users to define them with other, conflicting
+;;   meanings. To avoid conflicting with those user activities, we have a policy that
+;;   packages installed in Emacs must not load CL at run time. (It is ok for them to
+;;   load CL at compile time only, with eval-when-compile, and use the macros it
+;;   provides.) If you are writing packages that you plan to distribute and invite
+;;   widespread use for, you might want to observe the same rule.
+;;
+;; I don't think there's any reason to reimplement efficient folds just
+;; to avoid importing cl.  It seems to be another internecine war
+;; among Emacs factions.
+
+(require 'bytecomp)
+(setq byte-compile-warnings (remove 'cl-functions byte-compile-warning-types))
+(require 'cl)
+
+
+;; Util
+
+(defun Jane.test (e1 e2)
+  (assert (equal e1 e2))
+  t)
+
+(defmacro Jane.with-current-directory (dir &rest form)
+  (declare (indent defun))
+  (declare (debug (stringp body)))
+  ;; We [expand-file-name] so that a relative [dir] will work.  We use a trailing "/"
+  ;; because otherwise the last arc in the path is dropped.
+  `(let ((default-directory (format "%s/" (expand-file-name ,dir))))
+     ,@form))
+
+(defmacro Emacs.protect-from-quit (&rest form)
+  (declare (indent defun))
+  (declare (debug (body)))
+  `(let ((inhibit-quit t))
+     ,@form))
+
+(defun nequal (x y) (not (equal x y)))
+
+
+;; Lexical defun and let
+
+(defmacro defunl (name arglist &rest body)
+  "Lexical defun.  Not for use with interactive functions"
+  (declare (indent defun))
+  (let* (;; remove &optional and &rest
+         (args (remove-if
+                (lambda (s) (equal (substring (symbol-name s) 0 1) "&"))
+                arglist))
+         (args (mapcar (lambda (arg) (list arg arg)) args))
+         ;; put the doc string before the lexical-let
+         (doc_body (if (and (> (length body) 1)
+                            (stringp (car body)))
+                       `(,(car body) . ,(cdr body))
+                     `(nil . ,body)))
+         (doc (car doc_body))
+         (body (cdr doc_body)))
+    `(defun ,name ,arglist
+       ,(if doc doc)
+       (lexical-let
+           ,args
+         ,@body))))
+;; (macroexpand '(defunl f (x y) (+ x y)))
+;; (macroexpand '(defunl f (x y) "abc" (+ x y)))
+
+(def-edebug-spec defunl
+  (&define name lambda-list
+           [&optional stringp]   ; Match the doc string, if present.
+           [&optional ("interactive" interactive)]
+           def-body))
+
+(defmacro letl (varlist &rest body)
+  (declare (indent 1))
+  `(lexical-let ,varlist ,@body))
+;; (macroexpand '(letl ((x 5)) (+ x x)))
+;; (letl ((x 5)) (+ x x))
+
+(def-edebug-spec letl
+  ((&rest
+    &or symbolp (gate symbolp &optional form))
+   body))
+
+(defmacro letl* (varlist &rest body)
+  (declare (indent 1))
+  (declare (debug (form &rest form)))
+  `(lexical-let* ,varlist ,@body))
+
+(def-edebug-spec letl*
+  ((&rest
+    &or symbolp (gate symbolp &optional form))
+   body))
+
+(font-lock-add-keywords
+ 'emacs-lisp-mode
+ '(("\\<\\(defunl\\)" 1 font-lock-keyword-face)
+   ("\\<\\(letl\\*?\\)" 1 font-lock-keyword-face))
+ t)
+
+
+;; Options
+
+(defun Option.value (x default) (if x x default))
+
+
+;; Lists
+
+;; Ahh, the beauty of dynamic scoping.  Uniquify variable names by appending
+;; the defining function name.  Blech...
+
+(defunl List.foldr (f b l)
+  (reduce f l :initial-value b :from-end t))
+(Jane.test (List.foldr 'concat "" '("a" "b" "c")) "abc")
+(Jane.test (List.foldr 'cons nil '(1 2 3)) '(1 2 3))
+(Jane.test (List.foldr (lambda (x y) (- x y)) 0 '(1 2 3)) 2)
+
+(defunl List.foldl (f b l)
+  (reduce f l :initial-value b))
+(Jane.test (List.foldl 'concat "" '("a" "b" "c")) "abc")
+(Jane.test (List.foldl 'cons nil '("a" "b" "c")) '(((nil . "a") . "b") . "c"))
+(Jane.test (List.foldl (lambda (x y) (- x y)) 0 '(1 2 3)) -6)
+
+(defunl List.filter (f l) (remove-if-not f l))
+(Jane.test (List.filter (lambda (x) (> x 3)) '(1 2 3 4 5 6)) '(4 5 6))
+
+(defunl List.find (f l)
+  (if (null l) nil
+    (let ((hd (car l))
+          (tl (cdr l)))
+      (if (funcall f hd) hd (List.find f tl)))))
+(Jane.test (List.find (lambda (x) (> x 3)) '()) nil)
+(Jane.test (List.find (lambda (x) (> x 3)) '(1 2 3 4 5 6)) 4)
+
+(defunl List.exists (p l)
+  (if (List.find p l) t nil))
+(Jane.test (List.exists (lambda (x) (< x 10)) '(1 2 3 4 5)) t)
+(Jane.test (List.exists (lambda (x) (> x 10)) '(1 2 3 4 5)) nil)
+
+(defunl List.mem (l x)
+  (not (null (List.find (lambda (y) (equal x y)) l))))
+(Jane.test (List.mem '(1 2 3) 4) nil)
+(Jane.test (List.mem '(1 2 3 4) 4) t)
+
+(defunl List.assoc (x l)
+  (let ((res (assoc x l)))
+    (when res (cdr res))))
+(Jane.test (List.assoc 5 '((5 . 6) (1 . 3))) 6)
+(Jane.test (List.assoc 5 '((5 6) (1 3))) '(6))
+
+(defunl List.iter (f l) (mapc f l) nil)
+(let ((ctr 0))
+  (List.iter (lambda (x) (setq ctr (+ ctr x))) '(1 2 3 4 5))
+  (Jane.test ctr 15))
+
+(defunl List.iteri (f l)
+  (letl ((ctr 0))
+    (List.iter (lambda (x)
+                 (funcall f ctr x)
+                 (setq ctr (+ ctr 1))) l)))
+(let ((ctr 0))
+  (List.iteri (lambda (i x) (setq ctr (+ ctr i x))) '(1 1 1 1 1))
+  (Jane.test ctr 15))
+
+(defunl List.concat (l) (apply 'append l))
+(Jane.test (List.concat '((1 2) (3 4) (5 6))) '(1 2 3 4 5 6))
+
+(defunl List.map (f l) (mapcar f l))
+(Jane.test (List.map '1+ '(1 2 3)) '(2 3 4))
+
+(defunl List.mapi (f l)
+  (letl ((i 0))
+    (mapcar (lambda (x)
+              (letl ((y (funcall f i x)))
+                (setq i (1+ i))
+                y)) l)))
+(Jane.test (List.mapi (lambda (i _) i) '(0 0 0)) '(0 1 2))
+(Jane.test (List.mapi (lambda (a b) (+ a b)) '(1 2 3)) '(1 3 5))
+
+(defunl List.upto-aux (n from acc)
+  (if (< n from) acc
+    (List.upto-aux (1- n) from (cons n acc))))
+
+(defunl List.upto (n &optional from)
+  (let ((from (if from from 0)))
+    (if (< n from) nil (List.upto-aux n from nil))))
+
+(Jane.test (List.upto 3) '(0 1 2 3))
+(Jane.test (List.upto 3 2) '(2 3))
+
+(defunl List.concat-map (f l)
+  (apply 'append (List.map f l)))
+(Jane.test (List.concat-map 'List.upto '(0 1 2)) '(0 0 1 0 1 2))
+
+(defunl List.filter-map (f l)
+  (List.foldr (lambda (x acc)
+                (let ((y (apply f (list x))))
+                  (if y (cons y acc) acc)))
+              () l))
+(Jane.test (List.filter-map (lambda (x) (if (< x 4) (1+ x) nil)) '(1 2 3 4 5)) '(2 3 4))
+
+(defunl List.partition (p l)
+  (List.foldr (lambda (x acc)
+                (let ((yes (car acc))
+                      (no  (cdr acc))
+                      (res (funcall p x)))
+                  (if res
+                      (cons (cons x yes) no)
+                    (cons yes (cons x no)))))
+              (cons nil nil) l))
+(Jane.test (List.partition (lambda (x) (< x 4)) '(1 2 3 4 5)) '((1 2 3) . (4 5)))
+
+(defunl List.inter (l1 l2)
+  (List.filter (lambda (x) (List.mem l2 x)) l1))
+(Jane.test (List.inter '(1 2 3) '(2 3 4)) '(2 3))
+(Jane.test (List.inter '(1 2 3) '()) '())
+
+(defunl List.intersperse (sep l)
+  (if (null l) l
+    (cons (car l)
+          (apply 'append (mapcar (lambda (x) (list sep x)) (cdr l))))))
+(Jane.test (List.intersperse 5 '()) '())
+(Jane.test (List.intersperse 5 '(1)) '(1))
+(Jane.test (List.intersperse 5 '(1 2)) '(1 5 2))
+(Jane.test (List.intersperse 5 '(1 2 3)) '(1 5 2 5 3))
+
+(defunl List.last (l)
+  (car (last l)))
+(Jane.test (List.last '()) nil)
+(Jane.test (List.last '(1 2 3)) 3)
+
+(defunl List.take (l n)
+  (cond
+   ((equal n 0) nil)
+   ((equal l nil) nil)
+   (t (cons (car l) (List.take (cdr l) (1- n))))))
+(Jane.test (List.take '(1 2 3 4) 2) '(1 2))
+(Jane.test (List.take '(1 2 3 4) 6) '(1 2 3 4))
+
+(defunl List.drop (l n)
+  (cond
+   ((equal n 0) l)
+   ((equal l nil) nil)
+   (t (List.drop (cdr l) (1- n)))))
+(Jane.test (List.drop '(1 2 3 4) 2) '(3 4))
+(Jane.test (List.drop '(1 2 3 4) 6) '())
+
+(defunl List.butlast (l)
+  (let ((zero-or-one (lambda (l) (or (null l) (null (cdr l))))))
+  (cond
+   ((null l) nil)
+   ((null (cdr l)) nil)
+   (t (cons (car l) (List.butlast (cdr l)))))))
+(Jane.test (List.butlast '(1 2 3 4)) '(1 2 3))
+
+
+;; Strings
+
+(defun String.strip-newlines (s)
+  (if (null s) nil
+    (assert (string-or-null-p s) "strip-newlines: %s" s)
+    (replace-regexp-in-string "\n" "" s)))
+(Jane.test (String.strip-newlines nil) nil)
+(Jane.test (String.strip-newlines "a\nb\nc") "abc")
+
+(defun String.truncate (s n)
+  (let ((k (length s)))
+    (if (< k n) s
+      (substring s 0 n))))
+(Jane.test (String.truncate "abcdef" 3) "abc")
+(Jane.test (String.truncate "abcdef" 300) "abcdef")
+
+(defun String.lines (s)
+  (split-string s "\n"))
+(Jane.test (String.lines "a\nb\nc") '("a" "b" "c"))
+(Jane.test (String.lines "abc") '("abc"))
+(Jane.test (String.lines "\n") '("" ""))
+
+(defun String.strip (s)
+  "Replace space before and after a string"
+  (let ((s (replace-regexp-in-string "^[[:space:]]*" "" s)))
+    (replace-regexp-in-string "[[:space:]]*$" "" s)))
+(Jane.test (String.strip " abc def ") "abc def")
+
+(defun String.eval (s)
+  "Eval a string as code"
+  (with-temp-buffer (insert s) (eval-buffer)))
+;; Can't test.  Always returns nil.
+
+(defun String.escaped (s)
+  "Escape newlines and quotes"
+  (let ((tab '(("\n" . "\\\\n")
+               ;;("\"" . "\\\\\"")
+               )))
+    (List.foldl (lambda (s p)
+                  (let ((l (car p))
+                        (r (cdr p)))
+                    (replace-regexp-in-string l r s)))
+                s tab)))
+(Jane.test (String.escaped "abc\ndef\"ghi\"") "abc\\ndef\"ghi\"")
+(Jane.test (String.escaped "abc") "abc")
+
+
+;; Hash tables
+
+(setq tbl (make-hash-table :test 'equal))
+(puthash 'a 5 tbl)
+(puthash 'b 6 tbl)
+(puthash 'c 7 tbl)
+
+(defun Hashtbl.to-alist (tbl)
+  (let ((data nil))
+    (maphash (lambda (k v) (setq data (cons `(,k ,v) data))) tbl)
+    (reverse data)))
+(Jane.test (Hashtbl.to-alist tbl) '((a 5) (b 6) (c 7)))
+
+(defun Hashtbl.keys (tbl)
+  (List.map 'car (Hashtbl.to-alist tbl)))
+(Jane.test (Hashtbl.keys tbl) '(a b c))
+
+(defun Hashtbl.data (tbl)
+  (List.map 'cadr (Hashtbl.to-alist tbl)))
+(Jane.test (Hashtbl.data tbl) '(5 6 7))
+
+(defunl Hashtbl.iter (f tbl)
+  (maphash f tbl))
+(let ((ctr 0))
+  (Hashtbl.iter (lambda (_ x) (setq ctr (+ ctr x))) tbl)
+  (Jane.test ctr 18))
+
+
+;; Shell
+
+(defun Shell.readlink (path)
+  "Canonize the path by eliminating symlinks and dots"
+  (if (not path) nil
+    (let ((res (String.strip-newlines
+                (shell-command-to-string (format "readlink -f %s" path)))))
+      (if (equal res "") nil res))))
+;; (Shell.readlink "/usr/../home")
+;; (Shell.readlink "/usr/local//share")
+;; (Shell.readlink "/..")
+;; (Shell.readlink "/...")
+;; (Shell.readlink "/../.")
+;; (Shell.readlink nil)
+
+(defun Shell.dirname (path)
+  (if (not path) nil
+    (let ((res (String.strip-newlines
+                (shell-command-to-string (format "dirname %s" path)))))
+      (if (equal res "") nil res))))
+;; (Shell.dirname nil)
+;; (Shell.dirname "/")
+;; (Shell.dirname "/a/b")
+;; (Shell.dirname "/../b")
+
+(defun Shell.basename (path)
+  (if (not path) nil
+    (let ((res (String.strip-newlines
+                (shell-command-to-string (format "basename %s" path)))))
+      (if (equal res "") nil res))))
+;; (Shell.basename "/a/b/c")
+;; (Shell.basename "/")
+;; (Shell.basename "/../b")
+
+(defun Shell.mail (subject addr body)
+  (shell-command-to-string (format "echo \"%s\" | mail -s \"%s\" \"%s\"" body subject addr))
+  nil)
+;; (Shell.mail "test" "smclaughlin@janestreet.com" "abc")
+
+
+;; Buffers
+
+(defun Buffer.name (buffer-or-name)
+  "Allow the (string) name of a buffer to be passed to buffer-name"
+  (if (bufferp buffer-or-name) (buffer-name buffer-or-name) buffer-or-name))
+;; (Buffer.name nil)
+;; (Buffer.name "abc")
+;; (Buffer.name (get-buffer "*shell*"))
+
+(defun Buffer.safe-get (buffer-or-name)
+  "Allow the (string) name of a buffer to be passed to buffer-name"
+  (if (bufferp buffer-or-name) (buffer-name buffer-or-name) buffer-or-name))
+
+(defun Buffer.get (buffer-or-name)
+  "get-buffer that returns nil on a nil argument"
+  (if (null buffer-or-name) nil (get-buffer buffer-or-name)))
+
+(defun Buffer.kill (buffer-or-name)
+  "Kill a buffer.  Don't raise an exception of there is no such buffer.
+Just return nil.  This is the spec of `kill-buffer', but the former
+raises an exception."
+  (condition-case nil
+      (kill-buffer buffer-or-name)
+    (error nil)))
+;;(Buffer.kill "abc")
+;;(Buffer.kill "abcccc")
+
+(defun Buffer.kill-no-query-no-hooks (&optional buffer-or-name)
+  (interactive)
+  "Kill a buffer without any questions.  If there's no such buffer, just
+return nil"
+  (let ((buffer-or-name (if buffer-or-name buffer-or-name (current-buffer)))
+        (funs kill-buffer-query-functions)
+        (hook kill-buffer-hook))
+    (unwind-protect
+        (progn
+          (setq kill-buffer-query-functions nil)
+          (setq kill-buffer-hook nil)
+          (Buffer.kill buffer-or-name))
+      (setq kill-buffer-query-functions funs)
+      (setq kill-buffer-hook hook))))
+;;(Buffer.kill-no-query "*Backtrace*")
+
+(defun Buffer.num-lines (buf)
+  (save-excursion
+    (with-current-buffer buf
+      (count-lines (point-min) (point-max)))))
+;; (Buffer.num-lines path)
+
+(defun Buffer.clear (buffer)
+  "Remove all contents of a given buffer"
+  (with-current-buffer buffer
+    (delete-region (point-min) (point-max))))
+
+
+;; Filename
+
+(defun Filename.strip-final-slash (path)
+  "/a/b/c/ ---> /a/b/c"
+  (let ((n (length path)))
+    (if (equal (elt path (- n 1)) ?/)
+        (substring path 0 (- n 1))
+      path)))
+;; (Filename.strip-final-slash "/a/b/c")
+;; (Filename.strip-final-slash "/a/b/c/")
+
+(defun Filename.root-dir-p (path) (equal (Shell.readlink path) "/"))
+;; (Filename.root-dir-p nil)
+;; (Filename.root-dir-p "/")
+;; (Filename.root-dir-p "/usr/local/../../")
+
+(defun Filename.up1-dir (dir)
+  "Go up 1 directory.  The input must be an existing directory."
+  (if (null dir) (Emacs.error "up1-dir: %s" dir)
+    (assert (file-directory-p dir))
+    (Shell.readlink (concat dir "/.."))))
+;; (Filename.up1-dir nil)
+;; (Filename.up1-dir "/")
+;; (Filename.up1-dir "/doesnt-exist")
+;; (Filename.up1-dir "/usr/local")
+
+(defun Filename.directory-of (file-or-dir)
+  "Get the directory of a path.  If a file, give the enclosing dir.
+If a dir, return the dir"
+  (if (null file-or-dir) (Emacs.error "directory-of: %s" file-or-dir)
+    (let ((res (if (file-directory-p file-or-dir)
+                   file-or-dir
+                 (Shell.dirname file-or-dir))))
+      (assert (file-directory-p res))
+      (Shell.readlink res))))
+;; (Filename.directory-of nil)
+;; (Filename.directory-of "/usr/local")
+;; (Filename.directory-of "~/tmp")
+;; (Filename.directory-of "/usr/local/a.txt")
+
+(defun Filename.default-directory ()
+  (Filename.strip-final-slash default-directory))
+;; (Filename.strip-final-slash "/home/sweeks/jane/base/core/lib_test/")
+
+
+;; Windows
+
+(defun Window.select-another-window (&optional w)
+  "Select a window other than w.  Return nil if there is no such window."
+  (let ((w (if w w (selected-window))))
+    (get-window-with-predicate (lambda (w1) (not (equal w w1))) nil t)))
+
+(defun Window.all-visible ()
+  "Return all visible windows in all visible frames."
+  (apply 'append (mapcar (lambda (f) (window-list f "no-mini")) (frame-list))))
+;; (Window.all-visible)
+
+
+;; Timers
+
+(defunl Timer.handle-exn (f handler)
+  (assert (functionp f))
+  (assert (functionp handler))
+  (condition-case err
+      (funcall f)
+    ((error arith-error)
+     (funcall handler err))))
+
+(defunl Timer.run (secs repeat f handler)
+  (lexical-let*
+      ((f f)
+       (handler handler)
+       (fn (lambda () (Timer.handle-exn f handler))))
+    (run-with-timer secs repeat fn)))
+
+;; (defun tmp () (error "bug"))
+;; (defun tmp-handler (err)
+;; (tmp)
+;; (tmp-handler nil)
+;; (setq timer (Timer.run 1 1 'tmp 'tmp-handler))
+;; (Timer.handle-exn 'tmp 'tmp-handler)
+;; (Timer.handle-exn (lambda () (error "bug"))
+;;                   (lambda (err) (message "tmp raised an exn: %s" err)))
+;; (setq timer
+;;       (Timer.run 1 1
+;;                  (lambda () (error "bug"))
+;;                  (lambda (err) (message "tmp raised an exn: %s" err))))
+;; (cancel-timer timer)
+
+
+;; Logging
+;; inspired by log-buffer.el Cedric Lallain <kandjar76@hotmail.com>
+
+(defun Log.create (buffer-name)
+  "Create a new log buffer, called BUFFER-NAME.
+If BUFFER-NAME already exist, the function will just set the read-only flag.
+The log buffer is returned as a result of this call."
+  (let ((buffer (get-buffer-create buffer-name)))
+    (with-current-buffer buffer (toggle-read-only 1))
+    buffer))
+
+(defconst Log.buffer-max-size 100000
+  "Max size of the log buffer in characters")
+
+(defun Log.printf(log-buffer format-string &rest args)
+  "Display the text in the log buffer at the very end of it."
+  (let ((inhibit-read-only t) ;; the log is probably read-only
+	(auto-window-vscroll t)
+	(kill-whole-line t) ;; kill the newline as well as the line contents
+        (fmt (concat "[%s] " format-string "\n"))
+        (time (format-time-string "%y/%m/%d %H:%M:%S" (current-time)))
+	(user-buffer (current-buffer))
+	(user-window (selected-window))
+	(log-window (get-buffer-window log-buffer)))
+    (with-current-buffer log-buffer
+      (let* ((marker (point-marker))
+             (log-point (point))
+             (at-bottom (= log-point (point-max))))
+        ;; insert the log message on the last line
+        (goto-char (point-max))
+        (insert (apply 'format (cons fmt (cons time args))))
+        ;; maybe delete the first line of the log
+        (when (> (buffer-size log-buffer) Log.buffer-max-size)
+          (let ((beg (point-min)))
+            (goto-char (point-min))
+            (forward-line 1)
+            (delete-region beg (point))))
+        ;; restore window point
+        (when log-window
+          (if at-bottom
+              (progn (select-window log-window)
+                     (goto-char (point-max))
+                     (recenter (1- (window-body-height log-window))))
+            (goto-char (marker-position marker)))
+          (select-window user-window))
+        (set-marker marker nil)))))
+;; (Omake.Server.logf "sean: %s %d" "abc" 8)
+
+
+;; Misc commands
+
+(defun Emacs.grab-line ()
+  "Grab the line of point"
+  (interactive)
+  (save-excursion
+    (end-of-line)
+    (let ((end (point)))
+      (beginning-of-line)
+      (buffer-substring-no-properties (point) end))))
+
+(defun Buffer.last-lines (buffer-or-name n)
+  "return the last N lines of a buffer"
+  (save-excursion
+    (with-current-buffer buffer-or-name
+      (goto-char (point-max))
+      (let* ((aux nil) ;; dummy for compilation warning
+             (aux (lambda (k)
+                    (if (equal k 0) nil
+                      (let* ((l (Emacs.grab-line))
+                             (res (forward-line -1))
+                             (rest (when (equal res 0) (funcall aux (- k 1)))))
+                        (cons l rest))))))
+        (reverse (funcall aux n))))))
+;; (length (Buffer.last-lines "*Help*" 10))
+;; (with-current-buffer "tmp" (goto-char (point-min) (Emacs.grab-line)))
+
+(defun Buffer.goto-line (n)
+  (goto-char (point-min))
+  (forward-line (1- n)))
+
+(defun Emacs.error (str &rest args)
+  (error (concat "[ERROR] " str) args))
+
+(defun Emacs.window-buffers ()
+  "Get all buffers being displayed in a window"
+  (List.map 'window-buffer (window-list)))
+
+(defun Emacs.window-files ()
+  "Get the file names of all buffers being displayed in a window"
+  (List.filter-map 'buffer-file-name (Emacs.window-buffers)))
+
+(defun Emacs.add-hook-append (hook f) (add-hook hook f t))
+
+(defun Emacs.insert (s)
+  "Don't raise an exception when s is nil"
+  (when s (insert s)))
+
+(defun Emacs.color (s face) (propertize s 'face face))
+
+(defun Overlay.delete-soon (overlay)
+  "Set an overlay for some period of time"
+  (unwind-protect
+      (sit-for 60)
+    (delete-overlay overlay)))
+
+(defun Emacs.y-or-n-p (prompt)
+  (let ((result (y-or-n-p prompt)))
+    ;; The [(message nil)] is necessary to clear the minibuffer.
+    (message nil)
+    result))
+
+(defun Emacs.insert-object (x) (insert (prin1-to-string x)))
+;; (Emacs.insert-object "sean")
+
+(defun File.byte-compile-check ()
+  (interactive)
+  (let ((file (read-file-name "File: ")))
+    (byte-compile-file file)
+    (message "removing compiled file")
+    (remove-file (concat file "c"))))
+
+
+;; Hg
+
+(defun hg-resolve-file ()
+  (interactive)
+  "Run 'hg resolve -m' on the current file"
+  (let* ((file (buffer-file-name (current-buffer))))
+    (shell-command (format "hg resolve -m %s" file))))
+
+(require 'dired)
+(defun hg-resolve-file-dired ()
+  (interactive)
+  "Run 'hg resolve -m' on the current file in a dired buffer"
+  (let* ((file (dired-file-name-at-point)))
+    (shell-command (format "hg resolve -m %s" file))))
+
+(provide 'jane-lib)

File js-common.el

-
-;;----------------------------------------------------------------------------;;
-;; Path setup and unobjectionable customizations.                             ;;
-;; Every emacs user should load this file.                                    ;;
-;;----------------------------------------------------------------------------;;
-
+;; Path setup and unobjectionable customizations.
+;; Every emacs user should load this file.
 
 (require 'cl)
 
 ;; js libraries
 (require 'js-micro-features)
 (require 'js-ocaml)
+(require 'omake)
 (require 'js-cr)
-(progn
-  (require 'omake)
-  (when (equal js-elisp-dir (expand-file-name "js-test" js-site-lisp-prod))
-    (Omake.set-server-program "/mnt/global/base/bin/omake_server_test.exe")))
-(require 'js-omake-deprecated)
 
-;; Make sure "jomake" is called by omake-server
-(setq Omake.omake-command "jomake")
+;; so we can enable or disable bits of this file for individual users
+(defun i-am (users)
+  "returns true if user-login-name is passed"
+  (if (listp users)
+      (member (user-login-name) users)
+    (string= users (user-login-name))))
 
-;; Delete trailing whitespace
-(add-to-list 'write-file-functions 'delete-trailing-whitespace)
+;; 2012-03-29 sweeks: I put (Jane.omake-js) in js-common rather than js-defaults, because
+;; I want it on by default for everyone, even people who just require js-common rather
+;; than js-defaults.  People can still call (Jane.omake-old) too if they want.
+(unless (i-am "cfalls") (Jane.omake-js))
+
+;; 2012-04-24 sweeks: I want everyone to get the auto-modes.  I don't consider them
+;; optional.
+(Jane.auto-modes)
+
+;; 2012-04-30 sweeks: I don't why Jane.find should be optional.
+(Jane.find)
 
 ;; font-lock = colors
 (global-font-lock-mode 1)
  '(large-file-warning-threshold (* 100 1000 1000))
  )
 
-;;----------------------------------------------------------------------------;;
-;; Misc small commands                                                        ;;
-;;----------------------------------------------------------------------------;;
+
+;; Misc small commands
 
 (defvar align-dwim-command "/mnt/global/dev/bin/line_up_words.exe")
 
   (dired "*.ml{,i}"))
 
 (defun ml-grep (directory-to-grep regexp-to-grep)
-  "Grep through .ml files in the current hg root or directory"
+  "Grep through .ml files in chosen directory"
   (interactive "DDir: \nsRegexp: ")
   (grep (concat "find " directory-to-grep " -regex '.*\\.mli?$' | xargs grep --line-number --with-filename --no-messages --perl-regexp -- \"" regexp-to-grep "\"")))
 
   :group 'js)
 
 (defcustom js-background "black"
-  "Default blackground.  If t, background is black, otherwise white."
+  "Default blackground."
   :group 'js)
 
 (defcustom js-foreground "white"
-  "Default blackground.  If t, background is black, otherwise white."
+  "Default blackground."
     :group 'js)
 
 (defun set-default-face (size face)
       (find-alternate-file (concat "/sudo::" buffer-file-name))
       (error "Buffer %s does not visit a file" (current-buffer))))
 
-(defcustom jane-untabify
-  '(tuareg-mode c-mode c++-mode ruby-mode shell-script-mode python-mode)
-  "*The list of modes whose files should be untabified before saving."
-  :group 'js)
+(defun toggle-full-screen ()
+  "Toggle between full screen and partial screen display on X11;
+    courtesy of http://www.emacswiki.org/cgi-bin/wiki/FullScreen"
+  (interactive)
+  (x-send-client-message nil 0 nil "_NET_WM_STATE" 32
+                         '(2 "_NET_WM_STATE_FULLSCREEN" 0)))
 
-(defun jane-untabify ()
-  (when (memq major-mode jane-untabify)
-    (save-restriction (untabify (point-min) (point-max)))))
+(setq Info-directory-list
+      (list (format "%s/info/" js-elisp-dir)
+            "/usr/share/info/"))
 
-;;----------------------------------------------------------------------------;;
-;; End                                                                        ;;
-;;----------------------------------------------------------------------------;;
-
+
+(progn
+  ;; The code below deals with [whitespace-style], which affects both highlighting of
+  ;; whitespace via [whitespace-mode] and with cleanup of whitespace via
+  ;; [whitespace-cleanup] when a file is saved.  We attempt to achieve the following:
+  ;;
+  ;;   * characters beyond column 90 are highlighted
+  ;;   * trailing whitespace is not highlighted by default, but can be configured
+  ;;   * trailing whitespace is cleaned up on file save
+  ;;