seanmcl avatar seanmcl committed 9dadd05

moved files

Comments (0)

Files changed (4)

notes/window_selection.ml

+open Core.Std
+
+module F (M : sig
+  type window
+
+  module Code_and_error : sig
+    type 'a t =
+      { code : 'a;
+        error : 'a;
+      }
+  end
+
+  module Buffer : sig
+    type t
+  end
+
+  module Frame : sig
+    type t
+
+    val selected_window : t -> window
+    (* [split] will only be called on a frame that has one window, and encapsulates
+       emacs-config options [Omake.split-window-horizontally] and
+       [Omake.show-error-buffer-in-top-or-left-window]. *)
+    val split : t -> window Code_and_error.t
+    val windows : t -> window list
+  end
+
+  module Window : sig
+    type t = window
+
+    val buffer : t -> Buffer.t
+    val frame : t -> Frame.t
+    val lru : t list -> t
+  end
+
+  module Kind : sig
+    type t =
+      { buffer : Buffer.t;
+        dedicated_window : Window.t option;
+        dedicated_frame : Frame.t option;
+      }
+
+  end
+end) : sig
+  open M
+
+  val choose_windows
+    :  code:Kind.t
+    -> error:Kind.t
+    -> selected_window:Window.t
+    -> visible_windows:Window.t list
+    -> Window.t Code_and_error.t
+
+end = struct
+  open M
+  open Code_and_error
+  open Kind
+
+  let choose_windows ~code ~error ~selected_window ~visible_windows =
+    (* Requirements that emacs is expected to guarantee. *)
+    assert (code.buffer <> error.buffer);
+    assert (code.dedicated_window <> error.dedicated_window);
+    (* The algorithm. *)
+    let choose_frame_and_maybe_window kind ~other =
+      match kind.dedicated_window with
+      | Some w -> Window.frame w, Some w
+      | None ->
+        let try_windows, or_else_frame =
+          match kind.dedicated_frame with
+          | Some frame -> (Frame.windows frame, frame)
+          | None -> (visible_windows, Window.frame selected_window)
+        in
+        match
+          List.find try_windows ~f:(fun window ->
+            Window.buffer window = kind.buffer
+            && Some window <> other.dedicated_window)
+        with
+        | Some w -> Window.frame w, Some w
+        | None -> or_else_frame, None
+    in
+    let code_frame, code_window_opt =
+      choose_frame_and_maybe_window code ~other:error
+    in
+    let error_frame, error_window_opt =
+      choose_frame_and_maybe_window error ~other:code
+    in
+    let choice =
+      if code_frame = error_frame then begin
+        let frame = code_frame in
+        let selected_window = Frame.selected_window frame in
+        let windows = Frame.windows frame in
+        match List.filter windows ~f:(fun w -> w <> selected_window) with
+        | [] -> Frame.split frame
+        | unselected_windows ->
+          let lru = Window.lru unselected_windows in
+          let other_than w = if w <> selected_window then selected_window else lru in
+          match code_window_opt, error_window_opt with
+          | Some code, Some error -> { code; error }
+          | Some code, None -> { code; error = other_than code}
+          | None, Some error -> { code = other_than error; error }
+          | None, None -> { code = selected_window; error = lru }
+      end else begin
+        let or_selected window_opt frame =
+          Option.value window_opt ~default:(Frame.selected_window frame)
+        in
+        { code  = or_selected code_window_opt  code_frame ;
+          error = or_selected error_window_opt error_frame;
+        }
+      end
+    in
+    (* Properties guaranteed by the algorithm. *)
+    (* Error window and code window are distinct. *)
+    assert (choice.error <> choice.code);
+    (* Choices obey dedicated windows and frames. *)
+    let obeys_dedicated kind choice =
+      match kind.dedicated_window with
+      | Some w -> choice = w
+      | None ->
+        match kind.dedicated_frame with
+        | Some f -> Window.frame choice = f
+        | None -> true
+    in
+    assert (obeys_dedicated code  choice.code );
+    assert (obeys_dedicated error choice.error);
+    (* Return *)
+    choice
+  ;;
+end
+;;============================================================================;;
+;; Omake mode                                                                 ;;
+;;============================================================================;;
+
+;; Omake is a useful compilation system, but the output isn't ideal for
+;; users interested in fixing compilation errors.  This module parses
+;; the output of omake and present the compilation errors to the users
+;; in a clean and elegant fashion.
+;;
+;; The relevant files are:
+;; - `jane-lib.el'     Utility functions
+;; - `omake.ml'        Ocaml code that does the actual error parsing
+;; - `omake_server.ml' The ocaml omake server (o-server)
+;; - `omake.el'        Elisp code that calls out to ocaml, formats the
+;;                     result, and implements error navigation
+;;
+;; * The importance of the OMakeroot path
+;;
+;; `omake' supports running multiple concurrent compilation projects
+;; The primary data structure is a 'model'.  There is a model for each
+;; compilation process (called a project).  The id of a model is the full
+;; 'readlinked' path to the omakeroot file above the directory
+;; where the compilation started.  There can be at most one OMakeroot
+;; file in a project, so this path uniquely specifies the project.  The
+;; user is not permitted to start two projects within the same
+;; OMakeroot path.
+;;
+;; * The error buffer
+;;
+;; Each project has an associated error-buffer where errors are shown.
+;; The error buffer is always displayed in a window, chosen using
+;; `Omake.set-dedicated-error-window'
+;;
+;; * Starting a compilation project
+;;
+;; When the user wishes to start their first project, they should pick
+;; a window where they wish to see errors, and run
+;; `Omake.set-dedicated-error-window'.  Then the user begins a
+;; compilation project using `Omake.compile' in the directory they
+;; want compiled.
+;;
+;; * Stopping a project
+;;
+;; Run `Omake.kill-project' in a directory below the Omakeroot file of
+;; the project.
+;;
+;; * Error navigation
+;;
+;; To jump to the next error in the error buffer, the user calls
+;; `Omake.next-error' (C-c C-l)
+;; Some errors are very large (10^5 lines) and are shown as truncated
+;; in the error buffer.  To expand the full text of the current error
+;; into a new buffer, use `Omake.toggle-expanded-error' (C-c C-h).
+;; The same command deletes the buffer and returns to the error buffer.
+;; If the error buffer is hidden for some reason, the user can type
+;; `Omake.show-error-buffer' (C-c C-e)
+;;
+;; Changelog:
+;;  Version 2:
+;;  - Added versioning
+;;  - Support for omake variables
+;;    X_LIBRARY_INLINING, VERSION_UTIL_SUPPORT, LINK_EXECUTABLES
+;;  - More metadata in the error buffer in verbose mode
+;;  Version 7:
+;;  - Pings get ids
+
+;; FIXME: model-done --> model-finished  (grammar annoyance)
+
+;; Invariants (not yet implemented or checked)
+;;  - If the server is not running, the model table is empty
+;;
+
+(require 'autorevert)
+(require 'jane-lib)
+
+;;============================================================================;;
+;; Custom                                                                     ;;
+;;============================================================================;;
+
+(defcustom Omake.Server.program
+  "/mnt/global/base/bin/omake_server.exe"
+  "location of the executable program to find the next error"
+  :group 'omake
+  :type 'string)
+
+(defcustom Omake.error-file-dir "/dropoff/sweeks/omake-mode-bugs"
+  "where to put omake-mode errors as bug reports"
+  :group 'omake
+  :type 'string)
+
+(defcustom Omake.maintainer-email-addr "sweeks@janestreet.com"
+  "who to contact with problems"
+  :group 'omake
+  :type 'string)
+
+(defcustom Omake.omake-command "omake"
+  "How to run omake"
+  :group 'omake
+  :type 'string)
+
+(defcustom Omake.prompt-before-killing-project t
+  "If t, prompt y/n before killing a project"
+  :group 'omake
+  :type 'boolean)
+;; (setq Omake.prompt-before-killing-project nil)
+
+(defcustom Omake.split-frame-horizontally t
+  "See the info documentation for when this variable takes effect."
+  :group 'omake
+  :type 'boolean)
+;; (setq Omake.split-frame-horizontally t)
+;; (setq Omake.split-frame-horizontally nil)
+
+(defcustom Omake.show-error-buffer-in-top-or-left-window nil
+  "See the info documentation for what this is and when it takes effect."
+  :group 'omake
+  :type 'boolean)
+;; (setq Omake.show-error-buffer-in-top-or-left-window t)
+;; (setq Omake.show-error-buffer-in-top-or-left-window nil)
+
+;;============================================================================;;
+;; Faces                                                                      ;;
+;;============================================================================;;
+
+;; Note: If you add a face, add it to omake-{dark,light}-theme.el as well
+
+(defface Omake.Face.error
+  '((t (:foreground "red")))
+  "red"
+  :group 'omake)
+
+(defface Omake.Face.eval
+  '((t (:foreground "orange")))
+  "orange"
+  :group 'omake)
+
+(defface Omake.Face.async
+  '((t (:foreground "yellow")))
+  "yellow"
+  :group 'omake)
+
+(defface Omake.Face.help
+  '((t (:foreground "PaleTurquoise1")))
+  "blue"
+  :group 'omake)
+
+(defface Omake.Face.event
+  '((t (:foreground "cyan")))
+  "blue"
+  :group 'omake)
+
+(defface Omake.Face.debug
+  '((t (:foreground "magenta")))
+  "For debug messages"
+  :group 'omake)
+
+(defface Omake.Face.progress-working
+  '((t (:foreground "yellow")))
+  "Face for progress line of the error buffer"
+  :group 'omake)
+
+(defface Omake.Face.progress-successful
+  '((t (:foreground "green")))
+  "Face for progress line of the error buffer"
+  :group 'omake)
+
+(defface Omake.Face.progress-stopped
+  '((t (:foreground "red")))
+  "Face for progress line of the error buffer"
+  :group 'omake)
+
+(defface Omake.Face.spinner
+  '((t (:foreground "cyan")))
+  "Face for progress line of the error buffer"
+  :group 'omake)
+
+(defface Omake.Face.last-line
+  '((t (:foreground "pink")))
+  "Face for the last line of the error-buffer"
+  :group 'omake)
+
+(defface Omake.Face.verbose
+  '((t (:foreground "light cyan")))
+  "Face for the env line of the error-buffer"
+  :group 'omake)
+
+(defface Omake.Face.error-current
+  '((t (:foreground "orange")))
+  "Face for the current error"
+  :group 'omake)
+
+(defface Omake.Face.error-pending
+  '((t (:foreground "PaleTurquoise1")))
+  "Face for errors"
+  :group 'omake)
+
+(defface Omake.Face.ocaml-error
+  '((t (:foreground "red")))
+  "Face for error layovers in ocaml files"
+  :group 'omake)
+
+(defface Omake.Face.error
+  '((t (:foreground "red")))
+  "Face for error layovers in ocaml files"
+  :group 'omake)
+
+(defface Omake.Face.error-mouse
+  '((t (:foreground "cyan")))
+  "Face for the error when a mouse is over the error"
+  :group 'omake)
+
+;;============================================================================;;
+;; Util                                                                       ;;
+;;============================================================================;;
+
+(defun Omake.date () (format-time-string "%Y-%m-%d"))
+
+;;============================================================================;;
+;; Versioning                                                                 ;;
+;;============================================================================;;
+
+;; When upgrading, there are a couple things to watch out for
+;; - An emacs running an older version of omake-mode tries to
+;;   start a newer o-server.  In this case, the o-server should
+;;   refuse to start and tell the user (via emacsclient) that
+;;   they should (load-library "omake")
+;; - The user loads a new omake-mode version while the old o-server
+;;   is still running.  In this case, we should detect an omake-mode
+;;   upgrade and kill the server when it happens.
+
+;; Detect version changes
+
+(defconst Omake.pre-version 11
+  "We use a version number to synchronize the elisp code the omake server
+To roll a new version of elisp that is incompatible with ocaml or vice
+versa, you must bump the version number.  This prevents old elisp code
+from trying to start a new server.  Please describe the
+changes in the Changelog.")
+
+;; If there has been a version change upon loading this library,
+;; kill the o-server.
+(when (and (boundp 'Omake.version)
+           (nequal Omake.pre-version Omake.version)
+           (Omake.Server.running-p))
+  (let ((res (y-or-n-p
+    "Omake was updated.  Kill your server and reload? ")))
+    (if res (Omake.Server.stop-and-kill-projects)
+      ;; Set the version back to the loaded version
+      (error "Aborting omake-mode library load"))))
+
+(defconst Omake.version Omake.pre-version)
+
+;;============================================================================;;
+;; Windows and frames
+;;============================================================================;;
+
+(defvar Omake.Window.Error nil)
+(defvar Omake.Window.Code nil)
+(defvar Omake.Frame.Error nil)
+(defvar Omake.Frame.Code nil)
+
+(defun Omake.Window.clear-dedicated ()
+  (setq Omake.Window.Error nil)
+  (setq Omake.Window.Code nil)
+  (setq Omake.Frame.Error nil)
+  (setq Omake.Frame.Code nil))
+
+(defmacro Omake.Window.check (w)
+  `(and ,w
+       (not (window-live-p ,w))
+       (setq ,w nil)))
+;; (macroexpand '(Omake.Window.check Omake.Window.Error))
+
+(defmacro Omake.Frame.check (f)
+  `(and ,f
+        (or (not (frame-live-p ,f))
+            (not (frame-visible-p ,f)))
+        (setq ,f nil)))
+;; (macroexpand '(Omake.Frame.check Omake.Frame.Error))
+;; (frame-visible-p Omake.Frame.Error)
+
+(defun Omake.Window.kindp (kind)
+  (case kind
+    ((code error) t)
+    (t nil)))
+;; (Omake.Window.kindp 'code)
+;; (Omake.Window.kindp 'error)
+;; (Omake.Window.kindp 'abc)
+;; (Omake.Window.kindp nil)
+
+(defun Omake.Window.other (kind)
+  (assert (Omake.Window.kindp kind))
+  (case kind
+    (code 'error)
+    (error 'code)
+    (t (error "Impossible"))))
+;; (Omake.Window.other 'error)
+;; (Omake.Window.other 'code)
+;; (Omake.Window.other nil)
+
+(defun Omake.check-dedicated ()
+  (Omake.Window.check Omake.Window.Error)
+  (Omake.Window.check Omake.Window.Code)
+  (Omake.Frame.check Omake.Frame.Error)
+  (Omake.Frame.check Omake.Frame.Code)
+  (assert (or (null Omake.Window.Code)
+              (null Omake.Window.Error)
+              (nequal Omake.Window.Code Omake.Window.Error))))
+
+(defun Frame.show (f)
+  (make-frame-visible ,f)
+  (raise-frame ,f))
+
+(defun Omake.Frame.show (w f)
+  (Omake.check-dedicated)
+  (Frame.show f)
+  (when w (Frame.show (window-frame w))))
+
+(defun Omake.Frame.uniconify (kind)
+  (assert (Omake.Window.kindp kind))
+  (case kind
+    ('error (Omake.Frame.uniconify1 Omake.Window.Error Omake.Frame.Error))
+    ('code (Omake.Frame.uniconify1 Omake.Window.Code Omake.Frame.Code))
+    (t (error "Impossible"))))
+
+(defun Omake.Window.get (kind)
+  (assert (Omake.Window.kindp kind))
+  (case kind
+    ('error Omake.Window.Error)
+    ('code Omake.Window.Code)
+    (t (error "Impossible"))))
+
+(defun Omake.Frame.get (kind)
+  (assert (Omake.Window.kindp kind))
+  (case kind
+    ('error Omake.Frame.Error)
+    ('code Omake.Frame.Code)
+    (t (error "Impossible"))))
+
+(defun Omake.Frame.split ()
+  ;; If we're splitting, there's a single frame with a single window.  If it's
+  ;; dedicated, we have no hope of maintaining it
+  (Omake.Window.clear-dedicated)
+  (let ((right (if Omake.split-frame-horizontally
+                   (split-window-horizontally)
+                 (split-window-vertically)))
+        (left (selected-window)))
+    (if Omake.show-error-buffer-in-top-or-left-window
+        (cons right left)
+      (cons left right))))
+
+(defun Omake.choose-frame-and-maybe-window (kind buffer other-dedicated-window)
+  (assert (Omake.Window.kindp kind))
+  (let ((dw (Omake.Window.get kind))
+        (df (Omake.Frame.get kind)))
+    (if dw (cons (window-frame dw) dw)
+      (let* ((try-windows
+              (if df (window-list df) (Window.all-visible)))
+             ;; Make sure it tries the selected window first
+             ;; to make selection roughly symmetric between frames
+             (try-windows (cons (selected-window) try-windows))
+             (or-else-frame
+              (if df df (window-frame (selected-window))))
+             (final-window
+              (List.find
+               (lambda (w)
+                 (and
+                  buffer
+                  (equal (window-buffer w) buffer)
+                  (or (null other-dedicated-window)
+                      (nequal w other-dedicated-window))))
+               try-windows)))
+        (if final-window
+            (cons (window-frame final-window) final-window)
+          (cons or-else-frame nil))))))
+
+(defun Omake.obeys-dedicated (kind w)
+  (assert (Omake.Window.kindp kind))
+  (assert (window-live-p w))
+  (let ((dw (Omake.Window.get kind))
+        (df (Omake.Frame.get kind)))
+    (cond
+     (dw (equal dw w))
+     (df (equal df (window-frame w)))
+     (t t))))
+
+(defun Omake.choose-windows (id code-buffer-opt)
+  "Return (code-window . error-window) and set the code and error buffers"
+  (assert (Omake.id-p id))
+  (assert (or (null code-buffer-opt) (bufferp code-buffer-opt)))
+  (Omake.check-dedicated)
+  (let* ((model (Omake.Model.get id))
+         (error-buffer (Omake.model-error-buffer model))
+         (_ (assert (nequal code-buffer-opt error-buffer)))
+         (code (Omake.choose-frame-and-maybe-window
+                'code code-buffer-opt Omake.Window.Error))
+         (code-frame (car code))
+         (code-window (cdr code))
+         (err (Omake.choose-frame-and-maybe-window
+               'error error-buffer Omake.Window.Code))
+         (error-frame (car err))
+         (error-window (cdr err))
+         (_ (assert code-frame))
+         (_ (assert error-frame))
+         (or-selected (lambda (w f) (if w w (frame-selected-window f))))
+         (choice
+          (if (equal code-frame error-frame)
+              (let* ((frame code-frame)
+                     (sw (frame-selected-window frame))
+                     (windows (window-list frame))
+                     (unselected-ws (remove-if-not (lambda (w) (nequal w sw)) windows)))
+                (if (null unselected-ws)
+                    (with-selected-frame frame (Omake.Frame.split))
+                  (let* ((lru (when unselected-ws (car unselected-ws)))
+                         ;; Can't get lru from an arbitrary list of windows without digging into C
+                         (other-than (lambda (w) (if (equal w sw) lru sw))))
+                    (cond
+                     ((and code-window error-window) (cons code-window error-window))
+                     (code-window (cons code-window (funcall other-than code-window)))
+                     (error-window (cons (funcall other-than error-window) error-window))
+                     (t (cons sw lru))))))
+            ;; else
+            (cons (funcall or-selected code-window code-frame)
+                  (funcall or-selected error-window error-frame))))
+         (code (car choice))
+         (err (cdr choice)))
+    (assert (nequal code err))
+    (assert code)
+    (assert err)
+    (assert (Omake.obeys-dedicated 'code code))
+    (assert (Omake.obeys-dedicated 'error err))
+    choice))
+
+;;============================================================================;;
+;; Progress bar                                                               ;;
+;;============================================================================;;
+
+(defconst Omake.Progress.bar-size 30)
+
+(defun* Omake.Progress.make-bar (num denom &key full)
+  "if `full' is specified, force the bar to be full with num = denom"
+  (assert (integerp num))
+  (assert (integerp denom))
+  (if (equal denom 0) "No files processed yet"
+    (let* ((num (float num))
+           (num (if full denom num))
+           (denom (float denom))
+           (num-syms (if full Omake.Progress.bar-size
+                       (floor (* (/ num denom) (float Omake.Progress.bar-size)))))
+           (syms (make-string num-syms ?=))
+           (space (make-string (- Omake.Progress.bar-size num-syms) ? ))
+           (bar (format "[%s%s]" syms space)))
+      (format "%s %d / %d" bar num denom))))
+;; (Omake.Progress.make-bar 12239. 16429)
+;; (Omake.Progress.make-bar 12239 16429 :full t)
+
+;;============================================================================;;
+;; Paths                                                                      ;;
+;;============================================================================;;
+
+(defun Omake.Path.ok (path)
+  "A legal path has no spaces and doesn't end with a slash"
+  (assert (stringp path))
+  (let ((legal (progn
+                 (string-match "[.~a-zA-Z0-9/_-]*[.a-zA-Z0-9~_-]" path)
+                 (match-string 0 path))))
+    (equal path legal)))
+
+(progn
+  (Jane.test (Omake.Path.ok "/home/sweeks/live/107.15.00/live/lib") t)
+  (Jane.test (Omake.Path.ok "/home/sweeks/live/107.15.00/live/lib/aSet.ml") t)
+  (Jane.test (Omake.Path.ok "/mnt/local/sda1/smclaughlin/elisp/dev/omake-mode") t)
+  (Jane.test (Omake.Path.ok "/mnt/local.a.b.c/sda1/smclaughlin/elisp/dev/omake-mode") t)
+  (Jane.test (Omake.Path.ok "~/gord-test") t))
+
+(defun Omake.Path.omakeroot-dir (path)
+  "Get the full, unaliased path to the OMakeroot file above the given path.
+Raise an exception if there is no such directory."
+  (assert (stringp path))
+  (let* ((dir (locate-dominating-file path "OMakeroot"))
+         ;; locate-dominating-file leaves the last slash on the path
+         (dir (Filename.strip-final-slash dir)))
+    (if (not dir)
+        (error "Can't determine OMake project: no OMakeroot in %s or any of its ancestors"
+               path))
+    (assert (file-directory-p dir))
+    dir))
+;; (Omake.Path.omakeroot-dir "/mnt/local/sda1/smclaughlin/elisp/dev/omake-mode")
+;; (Omake.Path.omakeroot-dir "~/gord-test")
+;; (Omake.Path.omakeroot-dir "/mnt/local.a.b.c/sda1/smclaughlin/elisp/dev/omake-mode")
+
+;;============================================================================;;
+;; Ids                                                                        ;;
+;;============================================================================;;
+
+(defconst Omake.Id.cache (make-hash-table :test 'equal))
+
+(defvar Omake.Id.cache-misses 0)
+;; (setq Omake.Id.cache-misses 0)
+
+(defstruct
+  (Omake.id
+   (:constructor nil)
+   (:constructor
+    Omake.Id.of-path
+    (path
+     &aux (to-string
+           (let ((cached (gethash path Omake.Id.cache)))
+             (if cached cached
+               (assert (Omake.Path.ok path))
+               (let ((p (Shell.readlink (Omake.Path.omakeroot-dir path))))
+                 (assert p)
+                 (incf Omake.Id.cache-misses)
+                 (puthash path p Omake.Id.cache)
+                 p)))))))
+  (to-string nil :read-only t))
+;; (Omake.id-to-string (Omake.error-id current))
+
+(defun Omake.Id.current ()
+  (let ((path (Filename.default-directory)))
+    (Omake.Id.of-path path)))
+
+;;============================================================================;;
+;; Error                                                                      ;;
+;;============================================================================;;
+
+(defstruct
+  (Omake.error
+   (:constructor nil)
+   (:constructor Omake.Error
+                 (&key
+                  id
+                  relpath
+                  file
+                  line
+                  char-beg
+                  char-end
+                  text
+                  full-text
+                  &aux
+                  (_ (assert (stringp id)))
+                  (id (Omake.Id.of-path id))
+                  (_ (assert (Omake.id-p id)))
+                  (_ (assert (stringp relpath)))
+                  (_ (assert (stringp file)))
+                  (_ (assert (integerp line)))
+                  (_ (assert (integerp char-beg)))
+                  (_ (assert (integerp char-end)))
+                  (_ (assert (stringp text)))
+                  (_ (assert (stringp full-text)))
+                  (full-text-visible-p nil)
+                  )))
+  (id              nil :read-only t)
+  (relpath         nil :read-only t)
+  (file            nil :read-only t)
+  (line            nil :read-only t)
+  (char-beg        nil :read-only t)
+  (char-end        nil :read-only t)
+  (text            nil :read-only t)
+  (full-text       nil :read-only t)
+  full-text-visible-p)
+
+(defun Omake.Error.visible-text (e)
+  (if (Omake.error-full-text-visible-p e)
+      (Omake.error-full-text e)
+    (Omake.error-text e)))
+
+(defun Omake.Error.to-error-buffer-string (e)
+  (let* ((text (Omake.Error.visible-text e))
+         (relpath (Omake.error-relpath e))
+         ;; don't let relpath be too long
+         (relpath-len (length relpath))
+         (relpath-max-len 100) ;; ignore this for now
+         (relpath (if (< relpath-len relpath-max-len) relpath
+                    (concat "..." (substring relpath
+                                             (- relpath-max-len 3)
+                                             relpath-len)))))
+    (replace-regexp-in-string "File \"" (format "File \"%s/" relpath) text)))
+
+(defun Omake.Error.same-error (e1 e2)
+  "It's common for errors at line=1, char_beg=0, char_end=1 in the same file to be different,
+since those locations can mean interface mismatches.  We really have to compare
+the text."
+  (assert (Omake.error-p e1))
+  (assert (Omake.error-p e2))
+  (and
+   (equal (Omake.error-id e1) (Omake.error-id e2))
+   (equal (Omake.error-relpath e1) (Omake.error-relpath e2))
+   (equal (Omake.error-file e1) (Omake.error-file e2))
+   (equal (Omake.error-line e1) (Omake.error-line e2))
+   (equal (Omake.error-char-beg e1) (Omake.error-char-beg e2))
+   (equal (Omake.error-char-end e1) (Omake.error-char-end e2))
+   (equal (Omake.error-full-text e1) (Omake.error-full-text e2))))
+
+(defun Omake.Error.hash (e)
+  (assert (Omake.error-p e))
+  (sxhash (list
+           (Omake.error-full-text e)
+           (Omake.error-id e)
+           (Omake.error-relpath e)
+           (Omake.error-file e)
+           (Omake.error-line e)
+           (Omake.error-char-beg e)
+           (Omake.error-char-end e)
+           (Omake.error-full-text e))))
+;; (Omake.Error.hash current)
+
+(defun Omake.Error.file-path (e)
+  "The fullpath of a file"
+  (assert (Omake.error-p e))
+  (let* ((id (Omake.error-id e))
+         (model (Omake.Model.get id))
+         (root (Omake.model-omakeroot-dir model))
+         (relpath (Omake.error-relpath e))
+         (file (Omake.error-file e)))
+  (format "%s/%s/%s" root relpath file)))
+
+(defun Omake.Error.show (e)
+  (assert (Omake.error-p e))
+  (let* ((id (Omake.error-id e))
+         (model (Omake.Model.get id))
+         (error-buffer (Omake.model-error-buffer model))
+         (file (Omake.Error.file-path e))
+         (code-buffer (find-file-noselect file))
+         (ws (Omake.choose-windows id code-buffer))
+         (cw (car ws))
+         (ew (cdr ws))
+         (line (Omake.error-line e))
+         (char-beg (Omake.error-char-beg e))
+         (char-end (Omake.error-char-end e)))
+    ;; This part is finicky.  I'm not sure at the moment why
+    ;; the `switch-to-buffer' call is needed, but it is.
+    (set-window-buffer ew error-buffer)
+    (set-window-buffer cw code-buffer)
+    (select-window cw)
+    (select-frame-set-input-focus (window-frame cw))
+    ;;(raise-frame (window-frame cw))
+    ;;(select-frame (window-frame cw))
+    ;;(redirect-frame-focus (window-frame cw))
+    (switch-to-buffer code-buffer)
+    (remove-overlays (point-min) (point-max))
+    (Buffer.goto-line line)
+    (forward-char char-beg)
+    (let* ((left (point))
+           (right (+ left (- char-end char-beg)))
+           (overlay (make-overlay left right (current-buffer))))
+      (overlay-put overlay 'face 'Omake.Face.ocaml-error)
+      (Overlay.delete-soon overlay))))
+
+(defun Omake.Error.eval (e)
+  (let* ((id (Omake.error-id e))
+         (model (Omake.Model.get id))
+         (error-buffer (Omake.model-error-buffer model)))
+    (Omake.Model.make-current model e)
+    (with-current-buffer error-buffer (goto-char (point-min)))
+    (Omake.Error.show e)))
+
+(defun* Omake.Error.to-string (e &key is-current)
+  (assert (Omake.error-p e))
+  ;; Use lexical-let so we can put the error in a closure
+  (lexical-let*
+      (;; error jumping
+       (e e) ;; !!! close on e !!!
+       (error-face (if is-current 'Omake.Face.error-current 'Omake.Face.error-pending))
+       (goto-error (lambda ()
+                     (interactive)
+                     (Omake.Error.eval e)))
+       (goto-keymap   (make-sparse-keymap))
+       (_ (define-key goto-keymap   [mouse-1] goto-error))
+       (_ (define-key goto-keymap   "\C-m"     goto-error))
+       (error-str (Omake.Error.to-error-buffer-string e)))
+    (propertize error-str
+                'face            error-face
+                'mouse-face      'Omake.Face.error-mouse
+                'keymap          goto-keymap)))
+;; (Omake.Error.to-string e 0)
+
+(defun Omake.Error.mem (e es)
+  (List.exists (lambda (e1) (Omake.Error.same-error e e1)) es))
+
+(defun Omake.Error.toggle-full-text (e)
+  (let ((b (Omake.error-full-text-visible-p e)))
+    (setf (Omake.error-full-text-visible-p e) (not b))))
+
+(defun Omake.Error.contract (e)
+  (setf (Omake.error-full-text-visible-p e) nil))
+
+(defun Omake.Error.expand (e)
+  (setf (Omake.error-full-text-visible-p e) t))
+
+;;============================================================================;;
+;; Sets of errors                                                             ;;
+;;============================================================================;;
+
+(define-hash-table-test
+  'Omake.Error.hash-test 'Omake.Error.same-error 'Omake.Error.hash)
+
+(defun Omake.Error.make-hash-set (l)
+  (let ((table (make-hash-table :test 'Omake.Error.hash-test)))
+    (List.iter (lambda (e) (puthash e t table)) l)
+    table))
+;; (setq s (Omake.Error.make-hash-set (list current)))
+;; (gethash current s)
+
+(defstruct
+  (Omake.Error.set
+   (:constructor nil)
+   (:constructor
+    Omake.Error.Set.of-list
+    (l &aux (set (Omake.Error.make-hash-set l)))))
+  (set nil :read-only t))
+
+(defun Omake.Error.Set.mem (e s)
+  (assert (Omake.Error.set-p s))
+  (assert (Omake.error-p e))
+  (gethash e (Omake.Error.set-set s)))
+
+;;============================================================================;;
+;; Error ring                                                                 ;;
+;;============================================================================;;
+
+(defstruct
+  (Omake.ring
+   (:constructor nil)
+   (:constructor
+    Omake.Ring.create
+    (&key
+     current
+     pending
+     visited
+     &aux
+     (errs (append pending visited))
+     (errs (if current (cons current errs) errs))
+     (set (Omake.Error.Set.of-list errs)))))
+  (current nil :read-only t)
+  (pending nil :read-only t)
+  (visited nil :read-only t)
+  (set     nil :read-only t))
+
+(defun Omake.Ring.empty () (Omake.Ring.create :current nil :pending nil :visited nil))
+;; (Omake.Ring.empty)
+
+(defun Omake.Ring.of-list (l)
+  (if (null l) (Omake.Ring.empty)
+    (Omake.Ring.create :current nil :pending l :visited nil)))
+
+(defun Omake.Ring.mem (e ring)
+  (assert (Omake.error-p e))
+  (assert (Omake.ring-p ring))
+  (Omake.Error.Set.mem e (Omake.ring-set ring)))
+
+(defun Omake.Ring.current-file-errors-to-front (ring file)
+  (let* ((current (Omake.ring-current ring))
+         (visited (Omake.ring-visited ring))
+         (pending (Omake.ring-pending ring))
+         (errs (List.partition
+                (lambda (e) (equal
+                             (expand-file-name (Omake.Error.file-path e))
+                             (expand-file-name file)))
+                pending))
+         (file-errs (car errs))
+         (other-errs (cdr errs))
+         (pending (append file-errs other-errs)))
+    (if file-errs
+        (Omake.Ring.create :current current :pending pending :visited visited)
+      ring)))
+
+(defun Omake.Ring.to-list (ring)
+  (let* ((current (Omake.ring-current ring))
+         (pending (Omake.ring-pending ring))
+         (visited (Omake.ring-visited ring))
+         (current (if current (list current) nil)))
+    (append current pending visited)))
+
+(defun Omake.Ring.nth (ring k)
+  (let* ((l (Omake.Ring.to-list ring))
+         (n (length l)))
+    (assert (< k n))
+    (elt l k)))
+
+(defun Omake.Ring.num-errors (ring)
+  (length (Omake.Ring.to-list ring)))
+
+(defun Omake.Ring.is-empty (ring)
+  (null (Omake.Ring.to-list ring)))
+
+(defun Omake.Ring.to-string (ring)
+  (if (Omake.Ring.is-empty ring) ""
+    (let* ((current (Omake.ring-current ring))
+           (pending (Omake.ring-pending ring))
+           (visited (Omake.ring-visited ring))
+           (current-bar "======= current =======")
+           (current (when current (Omake.Error.to-string current :is-current t)))
+           (current (when current (list current-bar current)))
+           (num-pending (length pending))
+           (pending-bar (format "====== unvisited (%d) ======" num-pending))
+           (pending (List.map (lambda (e) (Omake.Error.to-string e :is-current nil)) pending))
+           (pending (when pending (cons pending-bar pending)))
+           (num-visited (length visited))
+           (visited-bar (format "======= visited (%d) =======" num-visited))
+           (visited (List.map (lambda (e) (Omake.Error.to-string e :is-current nil)) visited))
+           (visited (when visited (cons visited-bar visited)))
+           (all (append current pending visited)))
+      (concat "\n" (apply 'concat (List.intersperse "\n\n" all))))))
+
+(defun Omake.Ring.make-current (ring e)
+  "Replace the current error with `e'"
+  (let* ((current (Omake.ring-current ring))
+         (pending (Omake.ring-pending ring))
+         (visited (Omake.ring-visited ring))
+         (pending (List.filter (lambda (e1) (not (Omake.Error.same-error e e1))) pending))
+         (visited (List.filter (lambda (e1) (not (Omake.Error.same-error e e1))) visited)))
+    (Omake.Error.expand e)
+    (if (not current)
+        (Omake.Ring.create :current e :pending pending :visited visited)
+      (if (Omake.Error.same-error current e) ring
+        ;; Make sure to contract the error if it's expanded
+        (Omake.Error.contract current)
+        (Omake.Ring.create :current e :pending pending
+                           :visited (append visited (list current)))))))
+
+(defun Omake.Ring.combine (ring new-errors)
+  (assert (Omake.ring-p ring))
+  (assert (listp new-errors))
+  (let* ((fresh-errors
+          (List.filter
+           (lambda (e) (not (Omake.Ring.mem e ring))) new-errors))
+         (new-error-set (Omake.Error.Set.of-list new-errors))
+         (pending (Omake.ring-pending ring))
+         (pending
+          (List.filter
+           (lambda (e) (Omake.Error.Set.mem e new-error-set)) pending))
+         (pending (append pending fresh-errors))
+         (visited (Omake.ring-visited ring))
+         (visited
+          (List.filter (lambda (e) (Omake.Error.Set.mem e new-error-set))
+                       visited))
+         (current (Omake.ring-current ring))
+         (current
+          (when (and current (Omake.Error.Set.mem current new-error-set))
+            current)))
+    (Omake.Ring.create :current current :pending pending :visited visited)))
+
+(defun Omake.Ring.reset-visited (ring)
+  (assert (Omake.ring-p ring))
+  (let* ((current (Omake.ring-current ring))
+         (pending (Omake.ring-pending ring))
+         (pending (if current (cons current pending) pending))
+         (visited (Omake.ring-visited ring))
+         (pending (append pending visited)))
+    (Omake.Ring.create :current nil :pending pending :visited nil)))
+
+;;============================================================================;;
+;; Failure                                                                    ;;
+;;============================================================================;;
+
+;; Omake failures
+(defstruct
+  (Omake.failure
+   (:constructor nil)
+   (:constructor Omake.Failure (&key msg window)))
+   (msg     nil :read-only t)
+   (window  nil :read-only t))
+
+(defconst Omake.Failure.help-msg
+  (Emacs.color
+   (format "%s\n\n%s\n\n%s"
+           "To see the raw omake output, do \n\n  M-x Omake.show-raw-buffer (C-c C-r)"
+           "To restart do \n\n  M-x Omake.compile (C-c C-c)"
+           "Possibly relevant lines at the end of the raw omake output: ")
+   'Omake.Face.help))
+
+(defun Omake.Failure.to-string (failure)
+  (assert (Omake.failure-p failure))
+  (let* ((msg (Omake.failure-msg failure))
+         (msg (Emacs.color (format "OMAKE FAILURE\n\n%s" msg) 'Omake.Face.error))
+         (window (Omake.failure-window failure)))
+    (format "%s\n\n%s\n\n%s" msg Omake.Failure.help-msg window)))
+
+;;============================================================================;;
+;; Result                                                                     ;;
+;;============================================================================;;
+
+(defconst Omake.Result.failure-tag 1)
+(defconst Omake.Result.ring-tag 2)
+
+(defstruct
+  (Omake.result
+   (:constructor nil)
+   (:constructor Omake.Result.Failure (failure
+                                       &aux
+                                       (tag Omake.Result.failure-tag)
+                                       (_ (assert (Omake.failure-p failure)))
+                                       (ring nil)))
+   (:constructor Omake.Result.Ring (ring
+                                    &aux
+                                    (tag Omake.Result.ring-tag)
+                                    (_ (assert (Omake.ring-p ring)))
+                                    (failure nil))))
+   (tag     nil :read-only t)
+   (failure nil :read-only t)
+   (ring    nil :read-only t))
+
+(defun Omake.Result.failure-p (r)
+  (assert (Omake.result-p r))
+  (equal (Omake.result-tag r) Omake.Result.failure-tag))
+
+(defun Omake.Result.ring-p (r)
+  (assert (Omake.result-p r))
+  (equal (Omake.result-tag r) Omake.Result.ring-tag))
+
+(defun Omake.Result.is-empty (r)
+  (assert (Omake.result-p r))
+  (and (Omake.Result.ring-p r)
+       (Omake.Ring.is-empty (Omake.result-ring r))))
+
+(defun Omake.Result.to-string (result)
+  (assert (Omake.result-p result))
+  (if (Omake.Result.failure-p result)
+      (Omake.Failure.to-string (Omake.result-failure result))
+    (assert (Omake.Result.ring-p result))
+    (Omake.Ring.to-string (Omake.result-ring result))))
+;; (Omake.Model.show model)
+
+(defun Omake.Result.ok (result errors)
+  (if (Omake.Result.failure-p result)
+      (Omake.Result.Ring (Omake.Ring.of-list errors))
+    (let ((ring (Omake.result-ring result)))
+      (Omake.Result.Ring (Omake.Ring.combine ring errors)))))
+
+;;============================================================================;;
+;; Status                                                                     ;;
+;;============================================================================;;
+
+(defstruct
+  (Omake.status
+   (:constructor nil)
+   (:constructor Omake.Status.Starting                  (&aux (data 1)))
+   (:constructor Omake.Status.Polling                   (&aux (data 2)))
+   (:constructor Omake.Status.Reading_omakefiles        (&aux (data 3)))
+   (:constructor Omake.Status.Reading_omakefiles_failed (&aux (data 4)))
+   (:constructor Omake.Status.Finished_omakefiles       (&aux (data 5)))
+   (:constructor Omake.Status.Building                  (&aux (data 6)))
+   (:constructor Omake.Status.Dead                      (&aux (data 7))))
+  (data nil :read-only t))
+
+(defconst Omake.Status.Starting (Omake.Status.Starting))
+(defconst Omake.Status.Polling (Omake.Status.Polling))
+(defconst Omake.Status.Reading_omakefiles (Omake.Status.Reading_omakefiles))
+(defconst Omake.Status.Reading_omakefiles_failed (Omake.Status.Reading_omakefiles_failed))
+(defconst Omake.Status.Finished_omakefiles (Omake.Status.Finished_omakefiles))
+(defconst Omake.Status.Building (Omake.Status.Building))
+(defconst Omake.Status.Dead (Omake.Status.Dead))
+
+(defun Omake.Status.starting-p (s)
+  (assert (Omake.status-p s))
+  (equal s Omake.Status.Starting))
+
+(defun Omake.Status.polling-p (s)
+  (assert (Omake.status-p s))
+  (equal s Omake.Status.Polling))
+
+(defun Omake.Status.reading-omakefiles-p (s)
+  (assert (Omake.status-p s))
+  (equal s Omake.Status.Reading_omakefiles))
+
+(defun Omake.Status.reading-omakefiles-failed-p (s)
+  (assert (Omake.status-p s))
+  (equal s Omake.Status.Reading_omakefiles_failed))
+
+(defun Omake.Status.finished-omakefiles-p (s)
+  (assert (Omake.status-p s))
+  (equal s Omake.Status.Finished_omakefiles))
+
+(defun Omake.Status.building-p (s)
+  (assert (Omake.status-p s))
+  (equal s Omake.Status.Building))
+
+(defun Omake.Status.dead-p (s)
+  (assert (Omake.status-p s))
+  (equal s Omake.Status.Dead))
+
+(defun Omake.Status.show-spinner-p (s)
+  (assert (Omake.status-p s))
+  (not (Omake.Status.polling-p s)))
+
+(defun Omake.Status.stopped-p (s)
+  (assert (Omake.status-p s))
+  (or (Omake.Status.polling-p s)
+      (Omake.Status.reading-omakefiles-failed-p s)))
+
+;;============================================================================;;
+;; Buffers                                                                    ;;
+;;============================================================================;;
+
+;; There are four buffers:
+;;   - The raw buffer is the unmodified output of omake
+;;   - The error buffer is the primary user inteface, showing all errors
+;;     discovered by omake
+;;   - The elisp buffer is generated by the ocaml program, and is evaled.
+;;     You only ever need to look at it for debugging purposes
+;;   - The log buffer
+
+;; type buffer_kind = Raw | Error | Elisp | Log
+
+(defstruct
+  (Omake.buffer-kind
+   (:constructor nil)
+   (:constructor Omake.Buffer.Raw   (&aux (data 1)))
+   (:constructor Omake.Buffer.Error (&aux (data 2)))
+   (:constructor Omake.Buffer.Elisp (&aux (data 3)))
+   (:constructor Omake.Buffer.Log   (&aux (data 4))))
+  (data nil :read-only t))
+
+(defconst Omake.Buffer.Raw   (Omake.Buffer.Raw))
+(defconst Omake.Buffer.Error (Omake.Buffer.Error))
+(defconst Omake.Buffer.Elisp (Omake.Buffer.Elisp))
+(defconst Omake.Buffer.Log   (Omake.Buffer.Log))
+
+(defun Omake.Buffer.prefix (kind)
+  (assert (Omake.buffer-kind-p kind))
+  (cond
+   ((equal kind Omake.Buffer.Raw)   "[Raw]")
+   ((equal kind Omake.Buffer.Elisp) "[Elisp]")
+   ((equal kind Omake.Buffer.Error) "[Errors]")
+   ((equal kind Omake.Buffer.Log) "[Log]")))
+
+(defun Omake.Buffer.prefix-regexp (kind)
+  (assert (Omake.buffer-kind-p kind))
+  (cond
+   ((equal kind Omake.Buffer.Raw)   "\\[Raw\\]")
+   ((equal kind Omake.Buffer.Elisp) "\\[Elisp\\]")
+   ((equal kind Omake.Buffer.Log)   "\\[Log\\]")
+   ((equal kind Omake.Buffer.Error) "\\[Errors\\]")))
+
+(defun Omake.Buffer.error-p (buffer-or-name)
+  (let ((name (Buffer.name buffer-or-name)))
+    (if (string-match (Omake.Buffer.prefix-regexp Omake.Buffer.Error) name)
+        t
+      nil)))
+;; (Omake.Buffer.omake-buffer-p "[Errors]/mnt/local/sda1/smclaughlin/gord/test")
+;; (Omake.Buffer.omake-buffer-p "/mnt/local/sda1/smclaughlin/gord/test")
+
+(defun Omake.Buffer.name (kind dir)
+  (assert (Omake.buffer-kind-p kind))
+  (assert (stringp dir))
+  (assert (Omake.Path.ok dir))
+  (concat (Omake.Buffer.prefix kind) dir))
+
+(defun Omake.Buffer.create (kind dir)
+  (assert (Omake.buffer-kind-p kind))
+  (assert (stringp dir))
+  (let ((name (Omake.Buffer.name kind dir)))
+    (when (buffer-live-p (get-buffer name)) (error "Buffer %s already exists" name))
+    (get-buffer-create name)))
+
+;;============================================================================;;
+;; Files                                                                      ;;
+;;============================================================================;;
+
+;; The files used by omake-mode.
+;;   (PROJ is the path to the omakeroot with '/' replaced with '-')
+;;   (ROOT = /tmp/omake-server)
+;; - The omake server log (project independent)
+;;   ROOT/USER-server-log
+;; - A log for each project
+;;   ROOT/USER-PROJ-log
+;; - The raw omake output for each project
+;;   ROOT/USER-PROJ-omake
+;; - The generated elisp code for each project
+;;   ROOT/USER-PROJ-elisp
+;; - A unix socket used to communicate with the omake server
+;;   ROOT/USER-socket
+
+(defconst Omake.user (user-login-name))
+
+(defconst Omake.File.root "/tmp/omake-server")
+
+;; CR cfalls: Not all users want this.  Also, you should use custom-set-variables.
+;;    smclaughlin: If this is not set, people get a message every few seconds
+;;                 in the minibuffer.  Maybe I can find a way to set it on a
+;;                 buffer-by-buffer basis.
+(setq auto-revert-verbose nil)
+
+(defun Omake.File.auto-revert (file buffer-name)
+  "Open a file in auto-revert mode, and set the buffer name"
+  (when (not (get-buffer buffer-name))
+    (save-window-excursion
+      (if (file-exists-p file)
+          (progn
+            (find-file file)
+            (auto-revert-mode 1)
+            (rename-buffer buffer-name))
+        (message "ERROR: can't find file (%s)" file))))
+  (switch-to-buffer-other-window buffer-name))
+
+(defun Omake.File.project-file (name id)
+  (assert (Omake.id-p id))
+  (let ((id (Omake.id-to-string id)))
+    (format "%s/%s%s/%s" Omake.File.root Omake.user id name)))
+
+(defun Omake.File.omake (id) (Omake.File.project-file "omake" id))
+(defun Omake.File.elisp (id) (Omake.File.project-file "elisp" id))
+(defun Omake.File.log (id) (Omake.File.project-file "log" id))
+
+(defun Omake.File.of-kind (id kind)
+  (assert (Omake.buffer-kind-p kind))
+  (cond
+   ((equal kind Omake.Buffer.Raw) (Omake.File.omake id))
+   ((equal kind Omake.Buffer.Elisp) (Omake.File.elisp id))
+   ((equal kind Omake.Buffer.Log) (Omake.File.log id))
+   (t (error "Impossible"))))
+
+(defconst Omake.File.server-log
+  (format "%s/%s/server-log" Omake.File.root Omake.user))
+
+(defun Omake.File.socket ()
+  (format "%s/%s/socket" Omake.File.root Omake.user))
+
+;;============================================================================;;
+;; Spinner                                                                    ;;
+;;============================================================================;;
+
+;; - \ | / - \ | / - ...
+(defstruct
+  (Omake.spinner
+   (:constructor nil)
+   (:constructor Omake.Spinner.create (&aux (data 0))))
+  (data nil :read-only nil))
+
+(defun Omake.Spinner.spin (spinner)
+  (assert (Omake.spinner-p spinner))
+  (setf (Omake.spinner-data spinner)
+        (mod (+ (Omake.spinner-data spinner) 1) 4)))
+
+(defun Omake.Spinner.to-string (spinner)
+  (let* ((bar (case (Omake.spinner-data spinner)
+                (0 "-")
+                (1 "\\")
+                (2 "|")
+                (3 "/")
+                (t (error "bad spinner" ))))
+         (spin (format "[ %s ]" bar)))
+    (Emacs.color spin 'Omake.Face.spinner)))
+
+;; (setq spinner (Omake.Spinner.create))
+;; (Omake.Spinner.to-string spinner)
+;; (Omake.Spinner.spin spinner)
+;; (Omake.Spinner.to-string spinner)
+
+;; Quickly update the spinner in the error buffer
+(defun Omake.Spinner.update (model)
+  (assert (Omake.model-p model))
+  (let* ((spinner (Omake.model-spinner model))
+         (spinner (Omake.Spinner.to-string spinner))
+         (error-buf (Omake.model-error-buffer model)))
+    ;;(message "spinner-update: %s" spinner)
+    (with-current-buffer error-buf
+      (toggle-read-only -1)
+      (save-excursion
+        (goto-char (point-min))
+        (when (search-forward-regexp "\\[ [-\\|/] \\]" nil t)
+          (let ((beg (match-beginning 0))
+                (end (match-end 0)))
+            (delete-region beg end)
+            (goto-char beg)
+            (insert spinner))))
+      (toggle-read-only 1))))
+;; (Omake.Spinner.spin (Omake.model-spinner model))
+;; (Omake.Spinner.update model)
+;; (Omake.Model.spin-all)
+
+;;============================================================================;;
+;; Env                                                                         ;;
+;;============================================================================;;
+
+;; The server begins with the following variables set to one of {true,false}.
+;;
+;;   VERSION_UTIL_SUPPORT
+;;   LINK_EXECUTABLES
+;;   X_LIBRARY_INLINING
+;;   LIMIT_SUBDIRS_FOR_SPEED
+;;
+;; We want to show the values of these variables for each project.  We
+;; thus define temp variables that the o-server will set before
+;; starting a new project.  We put the values of those variables in
+;; the model.
+
+(defvar Omake.Env.x-library-inlining      nil)
+(defvar Omake.Env.link-executables        nil)
+(defvar Omake.Env.version-util-support    nil)
+(defvar Omake.Env.limit-subdirs-for-speed nil)
+
+(defun Omake.Env.reset ()
+  (setq Omake.Env.x-library-inlining nil
+        Omake.Env.link-executables nil
+        Omake.Env.version-util-support nil
+        Omake.Env.limit-subdirs-for-speed nil))
+
+(defstruct
+  (Omake.env
+   (:constructor nil)
+   (:constructor
+    Omake.Env.create
+    (id &aux
+     ;; Have the o-server set the current variables
+     (_ (assert (Omake.Server.running-p)))
+     (_ (Omake.Env.reset))
+     (_ (Omake.Server.set-emacs-env id))
+     (version-util-support Omake.Env.version-util-support)
+     (link-executables Omake.Env.link-executables)
+     (x-library-inlining Omake.Env.x-library-inlining)
+     (limit-subdirs-for-speed Omake.Env.limit-subdirs-for-speed)
+     (_ (assert (symbolp version-util-support) t))
+     (_ (assert (symbolp link-executables)))
+     (_ (assert (symbolp x-library-inlining)))
+     (_ (assert (symbolp limit-subdirs-for-speed)))
+     (_ (Omake.Env.reset)))))
+  (version-util-support    nil :read-only t)
+  (link-executables        nil :read-only t)
+  (x-library-inlining      nil :read-only t)
+  (limit-subdirs-for-speed nil :read-only t))
+;; (Omake.Env.create id)
+;; (Omake.Server.set-emacs-env)
+;; (setq my-env (Omake.Env.create id))
+;; (setq Omake.Env.version-util-support "true")
+;; (setq Omake.Env.x-library-inlining "true")
+;; (setq Omake.Env.link-executables "true")
+
+(defconst Omake.Env.vars
+  '("VERSION_UTIL_SUPPORT" "LINK_EXECUTABLES"
+    "X_LIBRARY_INLINING" "LIMIT_SUBDIRS_FOR_SPEED"))
+
+(defconst Omake.Env.values
+  '("true" "false"))
+
+(defun Omake.Env.completing-read ()
+  (intern (completing-read "Var: " Omake.Env.vars nil t)))
+
+(defun Omake.Env.completing-read-value ()
+  (intern (completing-read "Value: " Omake.Env.values nil t)))
+
+(defun Omake.Env.value (env var)
+  (assert (symbolp var))
+  (case var
+   (VERSION_UTIL_SUPPORT (Omake.env-version-util-support env))
+   (X_LIBRARY_INLINING (Omake.env-x-library-inlining env))
+   (LINK_EXECUTABLES (Omake.env-link-executables env))
+   (LIMIT_SUBDIRS_FOR_SPEED (Omake.env-limit-subdirs-for-speed env))
+   (t (error "Omake.Env.value: Impossible: %s" var))))
+
+(defun Omake.Env.set (id)
+  (when (or (not (Omake.Model.has id))
+            (Omake.Model.dead-p (Omake.Model.get id))
+            (Emacs.y-or-n-p
+             (format
+              "\
+Currently building:
+  %s
+Changes to variables will only take effect after restarting OMake.  Proceed? "
+              (Omake.model-compilation-dir (Omake.Model.get id)))))
+    ;; Set the variable on the server
+    (let ((var (Omake.Env.completing-read))
+          (value (Omake.Env.completing-read-value)))
+      (Omake.Server.setenv id var value)
+      ;; Set the variable in the model
+      (when (Omake.Model.has id)
+        (setf (Omake.model-env (Omake.Model.get id)) (Omake.Env.create id))))))
+
+(defun Omake.Env.get (id)
+  (let ((var (Omake.Env.completing-read)))
+    (Omake.Server.getenv id var)))
+
+(defun Omake.Env.not (env)
+  (case env
+   ('true 'false)
+   ('false 'true)
+   (t (error "Omake.Env.not error"))))
+;; (Omake.Env.not "true")
+
+(defun Omake.Env.toggle (id)
+  (let* ((var (Omake.Env.completing-read))
+         (env (Omake.Env.create id))
+         (val (Omake.Env.value env var))
+         (val (Omake.Env.not val)))
+    (Omake.Server.setenv id var val)
+    (when (Omake.Model.has id)
+      (setf (Omake.model-env (Omake.Model.get id)) (Omake.Env.create id)))
+    (message "%s set to %s" var val)))
+
+(defun Omake.Env.to-string (env)
+  (assert (Omake.env-p env))
+  (concat
+   (Omake.Model.verbose-line
+    "VERSION_UTIL_SUPPORT"
+    (Omake.env-version-util-support env))
+   (Omake.Model.verbose-line
+    "LINK_EXECUTABLES"
+    (Omake.env-link-executables env))
+   (Omake.Model.verbose-line
+    "X_LIBRARY_INLINING"
+    (Omake.env-x-library-inlining env))
+   (Omake.Model.verbose-line
+    "LIMIT_SUBDIRS_FOR_SPEED"
+    (Omake.env-limit-subdirs-for-speed env))
+   ))
+;; (Omake.Env.to-string (Omake.Env.create id))
+
+;;============================================================================;;
+;; Model                                                                      ;;
+;;============================================================================;;
+
+(defvar Omake.Model.table (make-hash-table :test 'equal))
+;; (setq Omake.Model.table (make-hash-table :test 'equal))
+
+(defun Omake.Model.has (id)
+  (assert (Omake.id-p id) t "Model.has: Not an id: %s")
+  (if (gethash id Omake.Model.table) t nil))
+
+(defun Omake.Model.get (id)
+  (assert (Omake.id-p id) t "Model.get: Not an id: %s")
+  (if (not (Omake.Model.has id))
+      (error "No model for project: %s" (Omake.id-to-string id))
+    (gethash id Omake.Model.table)))
+
+(defun Omake.Model.ids () (Hashtbl.keys Omake.Model.table))
+
+(defun Omake.Model.models () (Hashtbl.data Omake.Model.table))
+
+(defstruct
+  (Omake.model
+   (:constructor nil)
+   (:constructor
+    Omake.Model
+    (compilation-dir
+     &aux
+     (_ (assert (Omake.Path.ok compilation-dir)))
+     (id (Omake.Id.of-path compilation-dir))
+     (_ (when (Omake.Model.has id) (error "A model for %s already exists" id)))
+     (env (Omake.Env.create id))
+     (verbose nil)
+     (command Omake.omake-command)
+     (omakeroot-dir (Omake.Path.omakeroot-dir compilation-dir))
+     (error-buffer (Omake.Buffer.create Omake.Buffer.Error compilation-dir) model)
+     ;; turn on omake-mode in the error buffer
+     (_ (with-current-buffer error-buffer (omake-mode 1) (toggle-read-only 1)))
+     (spinner (Omake.Spinner.create))
+     (progress-bar '(0 . 0))
+     (targets-up-to-date '(0 . 0))
+     (status (Omake.Status.Starting))
+     (done nil)
+     (last-line "")
+     (result (Omake.Result.Ring (Omake.Ring.empty)))
+     ;; A way to signal serious errors like server disconnects
+     (error-msg nil)
+     )))
+  (id nil :read-only t)
+  (compilation-dir nil :read-only t)
+  env
+  verbose
+  command
+  (omakeroot-dir nil :read-only t)
+  (error-buffer nil :read-only t)
+  spinner
+  progress-bar
+  targets-up-to-date
+  status
+  done
+  last-line
+  result
+  error-msg)
+
+(defun Omake.Model.create (compilation-dir user-command)
+  (assert (stringp compilation-dir))
+  (Emacs.protect-from-quit
+    (let* ((model (Omake.Model compilation-dir))
+           (id (Omake.model-id model))
+           (ocaml-compilation-dir (expand-file-name compilation-dir))
+           (omakeroot-dir (Omake.model-omakeroot-dir model))
+           (user-command (if user-command user-command (Omake.model-command model))))
+      (setf (Omake.model-command model) user-command)
+      ;; add the model to the table
+      (puthash id model Omake.Model.table)
+      (Omake.Server.create :id id
+                           :omakeroot-dir omakeroot-dir
+                           :compilation-dir ocaml-compilation-dir
+                           :user-command user-command)
+      model)))
+
+(defun Omake.Model.kill (id)
+  (assert (Omake.id-p id))
+  (Emacs.protect-from-quit
+    (let* ((model (Omake.Model.get id))
+           (dir (Omake.model-omakeroot-dir model)))
+      ;; Critical section
+      (Buffer.kill-no-query-no-hooks (Omake.model-error-buffer model))
+      (Buffer.kill (Omake.Buffer.name Omake.Buffer.Raw dir))
+      (Buffer.kill (Omake.Buffer.name Omake.Buffer.Elisp dir))
+      (Buffer.kill (Omake.Buffer.name Omake.Buffer.Log dir))
+      (Omake.Server.kill id)
+      (remhash id Omake.Model.table)
+      ;; End critical section
+      )))
+
+(defun Omake.Model.bad-state-p (model)
+  "The model can get in an inconsistent state when the omake output is
+parsed incorrectly.
+
+Inconsistent = stopped, not done, no errors, no error message."
+  (assert (Omake.model-p model))
+  (let ((status (Omake.model-status model))
+        (result (Omake.model-result model))
+        (done (Omake.model-done model)))
+    (and (not done)
+         (Omake.Status.stopped-p status)
+         (Omake.Result.is-empty result)
+         (not (Omake.model-error-msg model)))))
+
+(defun Omake.Model.dead-p (model)
+  (assert (Omake.model-p model))
+  (let ((status (Omake.model-status model)))
+    (Omake.Status.dead-p status)))
+
+(defun Omake.Model.verbose-line (key value)
+  (Emacs.color (format "%-24s= %s\n" key value) 'Omake.Face.verbose))
+
+(defun Omake.Model.progress (model &key full)
+  (let* ((status (Omake.model-status model))
+         (targets (Omake.model-targets-up-to-date model))
+         (progress (Omake.model-progress-bar model))
+         ;; Use the up-to-date targets if they are nonzero and
+         ;; progress is stopped.  Otherwise use the status bar
+         (nd (if (or (equal (car targets) 0)
+                     (equal (cdr targets) 0)
+                     (not (Omake.Status.stopped-p status)))
+                 progress targets))
+         (num (car nd))
+         (denom (cdr nd))
+         (bar (Omake.Progress.make-bar num denom :full full)))
+    (cond
+     ((Omake.Status.starting-p status) "Starting")
+     ((Omake.Status.reading-omakefiles-p status) "Reading OMakefiles")
+     ((Omake.Status.reading-omakefiles-failed-p status) "Reading OMakefiles failed")
+     ((Omake.Status.finished-omakefiles-p status) "Finished reading OMakefiles")
+     (t bar))))
+;; (Omake.model-targets-up-to-date model)
+;; (Omake.model-progress-bar model)
+
+(defun Omake.Util.truncate (s n)
+  "Truncate a string at a given length, adding ... if it is truncated"
+  (assert (> n 3))
+  (let ((k (length s)))
+    (if (< k n) s
+      (format "%s..." (substring s 0 (- n 3))))))
+;; (Omake.Util.truncate "seanmclaughlin" 8)
+;; (Omake.Util.truncate "sean" 300)
+
+(defun Omake.Model.bad-state-msg (msg)
+  (format "%s\n\n%s"
+          (Emacs.color
+           (format "%s\n%s"
+                   "Compilation was not successful, but it's stopped and there are no errors."
+                   "Something is wrong")
+           'Omake.Face.red)
+          Omake.Failure.help-msg))
+;; (Omake.Model.bad-state-msg "abc")
+
+(defun Omake.Model.dead-msg (msg)
+  (concat
+   (Emacs.color "FATAL ERROR: The omake process is dead.\n\n" 'Omake.Face.red)
+   msg))
+;; (insert (Omake.Model.dead-msg "sean"))
+
+
+(defun Omake.Model.to-string (model)
+  (assert (Omake.model-p model))
+  (let* (;; model state
+         (status (Omake.model-status model))
+         (successful (Omake.model-done model))
+         (finished (Omake.Status.polling-p status))
+         (verbose (Omake.model-verbose model))
+         (error-msg (Omake.model-error-msg model))
+         ;; project
+         (root (Omake.model-omakeroot-dir model))
+         (root (Omake.Model.verbose-line "Project dir" root))
+         ;; compilation
+         (comp (Omake.model-compilation-dir model))
+         (comp (Omake.Model.verbose-line "Compilation dir" comp))
+         ;; command
+         (command (Omake.model-command model))
+         (command (Omake.Model.verbose-line "Command" command))
+         ;; env
+         (env (Omake.Env.to-string (Omake.model-env model)))
+         ;; last line
+         (last-line (Omake.model-last-line model))
+         (last-line (Omake.Util.truncate last-line 63))
+         (last-line (Omake.Model.verbose-line "Last line" last-line))
+         ;; spinner
+         (spinner (Omake.model-spinner model))
+         (spinner (Omake.Spinner.to-string spinner))
+         (spinner (if (or finished successful) "" spinner))
+         ;; progress
+         (progress (Omake.Model.progress model :full successful))
+         (progress (format "Progress : %s" progress))
+         (progress-face
+          (cond
+           (successful 'Omake.Face.progress-successful)
+           (finished 'Omake.Face.progress-stopped)
+           (t 'Omake.Face.progress-working)))
+         (progress (Emacs.color progress progress-face))
+         (progress (format "%s %s\n" progress spinner))
+         ;; body
+         (result (Omake.model-result model))
+         (body
+          (cond
+           (error-msg
+            (let ((error-msg (concat "ERROR: " error-msg)))
+              (Emacs.color error-msg 'Omake.Face.progress-stopped)))
+           (successful
+            (Emacs.color "\nSuccess!" 'Omake.Face.progress-successful))
+           (t (Omake.Result.to-string result))))
+         (header (when verbose (concat root comp command env last-line)))
+         (header
+          (if (Omake.Result.failure-p result)
+              header (concat header progress))))
+    (cond
+     ((Omake.Model.dead-p model)
+      (concat header "\n" (Omake.Model.dead-msg body)))
+     ((Omake.Model.bad-state-p model)
+      (concat header "\n\n" (Omake.Model.bad-state-msg body)))
+     ((Omake.Result.failure-p result)
+      (concat header "\n" body))
+     (t (concat header body)))))
+;; (Omake.Model.to-string model)
+;; (Omake.Model.show model)
+
+(defun Omake.Model.show (model)
+  "Show a model in the error buffer"
+  (assert (Omake.model-p model))
+  (let* ((error-buf (Omake.model-error-buffer model))
+         (error-win (Omake.Window.get 'error)))
+    (with-current-buffer error-buf
+      (toggle-read-only -1)
+      (let ((pt (point))
+            (ws (window-start error-win))
+            (we (window-end error-win)))
+        (delete-region (point-min) (point-max))
+        (insert (Omake.Model.to-string model))
+        (goto-char pt)
+        ;(set-window-point error-win pt)
+        ;(set-window-start error-win ws t))
+        (toggle-read-only 1))
+      )))
+;; (Omake.Model.show model)
+
+(defun Omake.Model.show-all ()
+  (List.iter 'Omake.Model.show (Omake.Model.models)))
+;; (Omake.Model.show-all)
+
+(defun Omake.Model.spin (model)
+  (let ((status (Omake.model-status model)))
+    (when (Omake.Status.show-spinner-p status)
+      (Omake.Spinner.spin (Omake.model-spinner model))
+      (Omake.Spinner.update model))))
+
+(defun Omake.Model.spin-all () (List.iter 'Omake.Model.spin (Omake.Model.models)))
+;; (Omake.Model.spin-all)
+
+(defun Omake.Model.reset-visited (id)
+  "Reset the visited status of errors"
+  (assert (Omake.id-p id))
+  (let* ((model (Omake.Model.get id))
+         (result (Omake.model-result model)))
+    (when (Omake.Result.ring-p result)
+      (let ((ring (Omake.result-ring result)))
+        (setf (Omake.model-result model)
+              (Omake.Result.Ring (Omake.Ring.reset-visited ring)))
+        (Omake.Model.show model)))))
+;; (Omake.Model.reset-visited id)
+
+(defun Omake.Model.make-current (model e)
+  "Make an error the current error"
+  (assert (Omake.model-p model))
+  (assert (Omake.error-p e))
+  (let ((result (Omake.model-result model)))
+    (when (Omake.Result.ring-p result)
+      (let* ((ring (Omake.result-ring result))
+             (ring (Omake.Ring.make-current ring e))
+             (result (Omake.Result.Ring ring)))
+        (setf (Omake.model-result model) result)
+        (Omake.Model.show model)))))
+
+(defun Omake.Model.create-buffer-kind (model kind)
+  (let* ((id (Omake.model-id model))
+         (dir (Omake.model-omakeroot-dir model))
+         (file (Omake.File.of-kind id kind))
+         (name (Omake.Buffer.name kind dir)))
+    (Omake.File.auto-revert file name)
+    (with-current-buffer name
+      (cd (Omake.model-compilation-dir model))
+      (toggle-read-only 1)
+      (omake-mode 1))))
+
+(defun Omake.Model.create-raw-buffer (model)
+  (Omake.Model.create-buffer-kind model Omake.Buffer.Raw))
+
+(defun Omake.Model.create-log-buffer (model)
+  (Omake.Model.create-buffer-kind model Omake.Buffer.Log))
+
+(defun Omake.Model.create-elisp-buffer (model)
+  (Omake.Model.create-buffer-kind model Omake.Buffer.Elisp))
+
+(defun Omake.Model.current () (Omake.Model.get (Omake.Id.current)))
+
+;;============================================================================;;
+;; Ocaml                                                                      ;;
+;;============================================================================;;
+
+(defun* Omake.Ocaml.update-model-ok
+    (&key id status last-line targets-up-to-date progress-bar done errors)
+  "Called when ocaml things everything is OK."
+  (assert (stringp id))
+  (assert (Omake.status-p status))
+  (assert (stringp last-line))
+  (assert (listp errors))
+  (assert (consp targets-up-to-date))
+  (assert (numberp (car targets-up-to-date)))
+  (assert (numberp (cdr targets-up-to-date)))
+  (assert (consp progress-bar))
+  (assert (numberp (car progress-bar)))
+  (assert (numberp (cdr progress-bar)))
+  (Emacs.protect-from-quit
+    (let* ((id (Omake.Id.of-path id))
+           (model (Omake.Model.get id))
+           (result (Omake.model-result model))
+           (result (Omake.Result.ok result errors)))
+      ;; Don't update a dead model.  This could be due to reading the
+      ;; Omake output in some race condition.  If the model is dead
+      ;; it can't be updating.
+      (unless (Omake.Model.dead-p model)
+        (setf (Omake.model-targets-up-to-date model) targets-up-to-date)
+        (setf (Omake.model-progress-bar model) progress-bar)
+        (setf (Omake.model-done model) done)
+        (setf (Omake.model-last-line model) last-line)
+        (setf (Omake.model-status model) status)
+        (setf (Omake.model-result model) result)
+        (Omake.Model.show model)))))
+
+(defun* Omake.Ocaml.update-model-failure (&key id msg window)
+  "Called when there's something wrong in the ocaml world."
+  (assert (stringp id))
+  (assert (stringp msg))
+  (Emacs.protect-from-quit
+    (let* ((id (Omake.Id.of-path id))
+           (model (Omake.Model.get id))
+           (result (Omake.Result.Failure (Omake.Failure :msg msg :window window))))
+      (unless (Omake.Model.dead-p model)
+        (setf (Omake.model-progress-bar model) '(0 . 1))
+        (setf (Omake.model-targets-up-to-date model) '(0 . 1))
+        (setf (Omake.model-result model) result)
+        (setf (Omake.model-done model) nil)
+        (Omake.Model.show model)))))
+
+(defun* Omake.Ocaml.update-model-dead (&key id msg)
+  "Called when the omake process for the model is dead."
+  (assert (stringp id))
+  (assert (stringp msg))
+  (Emacs.protect-from-quit
+    (let* ((id (Omake.Id.of-path id))
+           (model (Omake.Model.get id))
+           (result (Omake.Result.Failure (Omake.Failure :msg msg :window ""))))
+      (setf (Omake.model-progress-bar model) '(0 . 1))
+      (setf (Omake.model-targets-up-to-date model) '(0 . 1))
+      (setf (Omake.model-status model) Omake.Status.Dead)
+      (setf (Omake.model-result model) result)
+      (Omake.Model.show model))))
+
+;;============================================================================;;
+;; Servers                                                                    ;;
+;;============================================================================;;
+
+;;----------------------------------------------------------------------------;;
+;; Filter function                                                            ;;
+;;----------------------------------------------------------------------------;;
+
+(defvar Omake.Filter.debug nil
+  "When t, show extra debugging info in the log")
+
+(defvar Omake.Filter.partial-line "")
+
+(defconst Omake.Filter.debug-prefix "[debug]"
+  "prefix attached by the server to indicate a debugging line")
+
+(defconst Omake.Filter.debug-prefix-len
+  (length Omake.Filter.debug-prefix))
+
+;;(defconst Omake.Filter.truncate-messages t)
+(defconst Omake.Filter.truncate-messages nil)
+(defconst Omake.Filter.max-line-length 80)
+(defconst Omake.Filter.message-body-max-length (- Omake.Filter.max-line-length 31))
+(defun Omake.Filter.truncate (s)
+  (if Omake.Filter.truncate-messages
+      (Omake.Util.truncate s Omake.Filter.message-body-max-length)
+    s))
+
+(defun Omake.Filter.debug-p (s)
+  (when (>= (length s) Omake.Filter.debug-prefix-len)
+    (equal Omake.Filter.debug-prefix
+           (substring s 0 Omake.Filter.debug-prefix-len))))
+;; (Omake.Filter.debug-p "[debug] abc")
+
+(defconst Omake.Filter.event-prefix "[event]"
+  "prefix attached by the server to indicate an event line")
+
+(defconst Omake.Filter.event-prefix-len
+  (length Omake.Filter.event-prefix))
+
+(defun Omake.Filter.event-p (s)
+  (when (>= (length s) Omake.Filter.event-prefix-len)
+    (equal Omake.Filter.event-prefix
+           (substring s 0 Omake.Filter.event-prefix-len))))
+;; (Omake.Filter.event-p "[event] abc")
+
+(defun Omake.Filter.handle-complete-line (l)
+  (cond
+   ((Omake.Filter.debug-p l)
+    (Omake.Server.logf
+     "%-7s : %s"
+     (Emacs.color "Debug" 'Omake.Face.debug)
+     (Omake.Filter.truncate (substring l Omake.Filter.debug-prefix-len))))
+   ((Omake.Filter.event-p l)
+    (Omake.Server.logf
+     "%-7s : %s"
+     (Emacs.color "Event" 'Omake.Face.event)
+     (Omake.Filter.truncate (substring l Omake.Filter.event-prefix-len))))
+   (t
+    (Omake.Server.logf
+     "%-7s : %s"
+     (Emacs.color "Eval" 'Omake.Face.eval)
+     (Omake.Filter.truncate l))
+    (condition-case v
+        (String.eval l)
+      (error
+       ;;(message "omake-mode error.  see [omake-mode-log]")
+       (Omake.Server.logf
+        "%-7s: eval failed (error = %s) (partial = \"%s\"): %s"
+        (Emacs.color "ERROR" 'Omake.Face.error)
+        v Omake.Filter.partial-line l))))))
+;; (Omake.Filter.handle-complete-line ")")
+;; (Omake.Filter.handle-complete-line "[debug] ")
+
+(defun Omake.Filter.fun (_proc s)
+  (when Omake.Filter.debug
+    (Omake.Server.logf "%-7s : %s" (Emacs.color "Read" 'Omake.Face.debug)
+                       (String.escaped s)))
+  (Emacs.protect-from-quit
+    ;; 0 lines
+    (when (and (not (null s)) (< 0 (length s)))
+      (let* ((lines (String.lines s))
+             (n (length lines)))
+        ;; 1 line
+        (if (equal n 1)
+            (setq Omake.Filter.partial-line
+                  (concat Omake.Filter.partial-line (car lines)))
+          ;; There are at least 2 lines
+          ;; all middle lines (maybe none) are complete
+          ;; The first line finishes the partial line and gives a complete line
+          ;; The last line becomes the partial line
+          (let* ((first (concat Omake.Filter.partial-line (car lines)))
+                 (middle (List.butlast (cdr lines)))
+                 (last (List.last lines))
+                 (complete (cons first middle)))
+            (setq Omake.Filter.partial-line last)
+            (List.iter 'Omake.Filter.handle-complete-line complete)))))))
+;; (setq Omake.Filter.partial-line nil)
+;; (Omake.Filter.fun nil "(message \"abc\") \n (let ((x 5))")
+;; (Omake.Filter.fun nil " (message (format \"x = %d\" x) ")
+;; (Omake.Filter.fun nil " )) \n (setq   x 6  )")
+;; (Omake.Filter.fun nil " \n")
+
+;;----------------------------------------------------------------------------;;
+;; Omake server                                                               ;;
+;;----------------------------------------------------------------------------;;
+
+(defvar Omake.Server.process nil
+  "A handle on the omake server registration process.  Note that this is
+not the omake server process, but the result of
+  omake_server.exe client register-emacs")
+
+(defun Omake.Server.running-p ()
+  (let ((p (call-process Omake.Server.program nil nil nil "running")))
+    (equal p 0)))
+;; (Omake.Server.running-p)
+
+(defun Omake.Server.register ()
+  (call-process Omake.Server.program nil nil nil "running"))
+
+(defun Omake.Server.log-buffer () (Log.create "[omake-mode-log]"))
+
+(defun Omake.Server.logf (fmt &rest rest)
+  (apply 'Log.printf (cons (Omake.Server.log-buffer) (cons fmt rest))))
+;; (Omake.Server.logf "sean: %s %d" "abc" 8)
+
+(defun Omake.Server.version ()
+  (with-temp-buffer
+    (call-process
+     Omake.Server.program
+     nil
+     (current-buffer)
+     nil
+     "server-version")
+    (let ((res (buffer-substring (point-min) (point-max))))
+      (string-to-number res))))
+;; (Omake.Server.version)
+
+(defun Omake.Server.detect-mismatch ()
+  (let ((vs (Omake.Server.version))
+        (vo Omake.version))
+    (unless (equal vs vo)
+      (let ((res (y-or-n-p (format "Server version (%d) differs from elisp version (%d).  Reload? " vs vo))))
+        (if (not res)
+            (error "Not starting server")
+          (load-library "omake")
+          (if (equal vs Omake.version)
+              (Omake.Server.start)
+            (message "The versions still differ.  Aborting.")))))))
+
+(defun Omake.Server.start ()
+  (Omake.Server.logf "Starting server")
+  (Omake.Server.detect-mismatch)
+  (unless (Omake.Server.running-p)
+    (Emacs.protect-from-quit
+      (let ((process
+             (start-process
+              "omake-server"
+              (Omake.Server.log-buffer)
+              Omake.Server.program
+              "server" "start")))
+        (set-process-filter process 'Omake.Filter.fun))
+      ;; Give the server a chance to start and receive requests
+      (sleep-for 2)
+      (Omake.Server.logf "Server started")
+      (Omake.Timer.start))))
+;; (Omake.Server.start)
+;; (Omake.Timer.stop)
+;; (process-command Omake.Server.process)
+;; (process-status Omake.Server.process)
+;; (process-exit-status Omake.Server.process)
+
+(defun Omake.Server.ensure-running ()
+  (unless (Omake.Server.running-p) (Omake.Server.start)))
+
+(defun Omake.Server.stop ()
+  (Emacs.protect-from-quit
+    (Omake.Server.logf "Stopping server")
+    (call-process Omake.Server.program nil nil nil "server" "stop")
+    (Omake.Timer.stop)
+    (Omake.Server.logf "Server stopped")))
+;; (Omake.Server.stop)
+
+(defun Omake.Server.send (msg)
+  (assert (stringp msg))
+  (if (not (Omake.Server.running-p))
+      (Omake.Server.logf "Error: Trying to send msg (%s) but the server is not running" msg)
+    (Omake.Server.logf "Sending : %s" msg)
+    (let ((buf (get-buffer-create "[omake-server-output]")))
+      (Buffer.clear buf)
+      (let ((exit (call-process Omake.Server.program nil buf nil "send" msg)))
+        (if (equal exit 0)
+            (eval-buffer buf)
+          (Omake.Server.logf "Nonzero exit status: %s" exit))))))
+  ;; (setq msg "List")
+
+(defun Omake.Server.send-async (msg)
+  (assert (stringp msg))
+  (if (not (Omake.Server.running-p))
+      (Omake.Server.logf "Error: Trying to send msg (%s) but the server is not running" msg)
+    (Omake.Server.logf "%-7s : %s" (Emacs.color "Async" 'Omake.Face.async) msg)
+    (start-process "send-async" nil Omake.Server.program "send" msg)
+    t))
+;; (Omake.Server.send-async "List")
+
+(defun* Omake.Server.create (&key id omakeroot-dir compilation-dir user-command)
+  (assert (Omake.id-p id))
+  (assert (stringp omakeroot-dir))
+  (assert (stringp compilation-dir))
+  (let* ((id (Omake.id-to-string id))
+         (msg (format "(create ((id \"%s\") (omakeroot_dir \"%s\") (compilation_dir \"%s\") (user_command \"%s\")))"
+                      id omakeroot-dir compilation-dir user-command)))
+    (Omake.Server.send msg)))
+
+(defun Omake.Server.fatal-error (msg)
+  (List.iter
+   (lambda (model) (setf (Omake.model-error-msg model) msg))
+   (Omake.Model.models))
+  (Omake.Model.show-all))
+;; (Omake.Server.fatal-error "ERROR")
+
+(defun Omake.Server.ok ()
+  (List.iter
+   (lambda (model)
+     (when (Omake.model-error-msg model)
+       (setf (Omake.model-error-msg model) nil)
+       (Omake.Model.show model)))
+   (Omake.Model.models)))
+;; (Omake.Server.ok)
+
+(defun Omake.Server.kill (id)
+  (assert (Omake.id-p id))
+  (when (Omake.Server.running-p)
+    (let* ((id (Omake.id-to-string id))
+           (msg (format "(kill %s)" id)))
+      (Omake.Server.send msg))))
+
+(defun Omake.Server.force-update (id)
+  (assert (Omake.id-p id))
+  (let* ((id (Omake.id-to-string id))
+         (msg (format "(Force_update %s)" id)))
+    (Omake.Server.send-async msg)))
+
+(defun Omake.Server.setenv (id key data)
+  (assert (Omake.id-p id))
+  (assert (symbolp key))
+  (assert (symbolp data))
+  (let ((ids (Omake.id-to-string id)))
+    (Omake.Server.send (format "(Set_project_env %s %s %s)" ids key data))))
+
+(defun Omake.Server.getenv (id key)
+  (assert (Omake.id-p id))
+  (assert (symbolp key))
+  (Omake.Server.send
+   (format "(Get_project_env %s %s)" (Omake.id-to-string id) key)))
+
+(defun Omake.Server.set-emacs-env (id)
+  (assert (Omake.id-p id))
+  (Omake.Server.send (format "(Set_emacs_env %s)" (Omake.id-to-string id))))
+;; (Omake.Server.set-emacs-env)
+
+(defun Omake.Server.stop-and-kill-projects ()
+  (List.iter (lambda (id) (Omake.Model.kill id)) (Omake.Model.ids))
+  (Omake.Server.stop))
+
+;;============================================================================;;
+;; Pings                                                                      ;;
+;;============================================================================;;
+
+(defvar Omake.Ping.last-received-time (current-time))
+
+(defconst Omake.Ping.max-time (seconds-to-time 5))
+
+(defvar Omake.Ping.count 0)
+
+(defun Omake.Ping.send ()
+  ;; Send a ping asyncronously
+  (Omake.Server.send-async (format "(Ping ((version %d) (uid %d)))"
+                                   Omake.version Omake.Ping.count))
+  (setq Omake.Ping.count (1+ Omake.Ping.count))
+  ;; Check to be sure the last ping was received
+  (let* ((now (current-time))
+         (diff (time-subtract now Omake.Ping.last-received-time)))
+    (unless (time-less-p diff Omake.Ping.max-time)
+      ;; The server is not responding
+      (Omake.Server.fatal-error "The server is not responding."))))
+;; (Omake.Ping.send)
+;; (format-time-string "%H:%M:%S" Omake.Ping.last-received-time)
+
+(defun Omake.Ping.ack (n)
+  "Called by the o-server to acknowledge a received ping.
+Argument N is unused, but is print in the log"
+  (setq Omake.Ping.last-received-time (current-time))
+  (Omake.Server.ok))
+;; (Omake.Ping.ack)
+
+(defun* Omake.Ping.version-mismatch (&key server-received server-version)
+  "Called by the server to alert of a version mismatch"
+  (assert (nequal server-received server-version))
+  (let ((msg
+         (if (< server-received server-version)
+             (format "Server version (%d) is newer than elisp version (%d).  Run M-x load-library omake" server-version server-received)
+           (format "Server version (%d) is older than elisp version (%d).  Run M-x Omake.shutdown then M-x Omake.compile" server-version server-received)
+           )))
+    (message msg)))
+;; (Omake.Ping.version-mismatch :server-received 3 :server-version 2)
+;; (Omake.Ping.version-mismatch :server-received 2 :server-version 3)
+
+;;============================================================================;;
+;; Timer                                                                      ;;
+;;============================================================================;;
+
+(defun Omake.Timer.stop ()
+  (cancel-function-timers 'Omake.Model.spin-all)
+  (cancel-function-timers 'Omake.Ping.send))
+;; (Omake.Timer.stop)
+
+(defun Omake.Timer.start ()
+  (Omake.Timer.stop)
+  (run-with-timer 1 0.7 'Omake.Model.spin-all)
+  ;; Ping timer must be << Omake.Ping.max-time
+  (run-with-timer 10 3 'Omake.Ping.send))
+;; (Omake.Timer.stop)
+;; (Omake.Timer.start)
+;; (Omake.Ping.send)
+
+;;============================================================================;;
+;; User interface                                                             ;;
+;;============================================================================;;
+
+(defun Omake.set-dedicated-error-window ()
+  (interactive)
+  (Omake.check-dedicated)
+  (let ((w (selected-window))
+        (f (selected-frame)))
+    (when (equal w (Omake.Window.get 'code))
+      (error "The current window is already dedicated to code.
+Do M-x Omake.clear-dedicated-windows to reset" ))
+    (setq Omake.Window.Error w)
+    (setq Omake.Frame.Error f)))
+
+(defun Omake.set-dedicated-code-window ()
+  (interactive)
+  (Omake.check-dedicated)
+  (let ((w (selected-window))
+        (f (selected-frame)))
+    (when (equal w (Omake.Window.get 'error))
+      (error "The current window is already dedicated to errors.
+Do M-x Omake.clear-dedicated-windows to reset"))
+    (setq Omake.Window.Code w)
+    (setq Omake.Frame.Code f)))
+
+(defun Omake.clear-dedicated-windows ()
+  (interactive)
+  (Omake.Window.clear-dedicated))
+
+(defun Omake.set-dedicated-error-frame ()
+  (interactive)
+  (setq Omake.Frame.Error (selected-frame)))
+
+(defun Omake.set-dedicated-code-frame ()
+  (interactive)
+  (setq Omake.Frame.Code (selected-frame)))
+
+(defun Omake.show-error-buffer ()
+  "Set the error buffer in the error window.  Return the code and error windows."
+  (interactive)
+  (let* ((id (Omake.Id.current))
+         (model (Omake.Model.get id))
+         (eb (Omake.model-error-buffer model))
+         (ws (Omake.choose-windows id nil))
+         (ew (cdr ws)))
+    (set-window-buffer ew eb)
+    ws))
+
+(defun Omake.compile (&optional read-command)
+  "Compile the current directory."
+  (interactive "P")
+  (Omake.install-hooks)
+  (let ((server-was-running (Omake.Server.running-p)))
+    (Omake.Server.ensure-running)
+    (let* ((path (Filename.default-directory))
+           (id (Omake.Id.of-path path)))
+      (catch 'exit
+        (when (Omake.Model.has id)
+          ;; If there's a problem just kill the project
+          (if (or (not server-was-running)
+                  (Omake.Model.dead-p (Omake.Model.get id)))
+              (Omake.Model.kill id)
+            ;; Otherwise ask the user if they really want to kill it
+            (let ((kill (Emacs.y-or-n-p
+                         (format "\
+OMake is already building:
+  %s
+Proceeding will kill OMake and restart it on:
+  %s
+Proceed? "
+                                 (Omake.model-compilation-dir (Omake.Model.get id))
+                                 path
+                                 ))))
+              (if kill (Omake.Model.kill id) (throw 'exit t)))))
+        ;; Create the model
+        (let ((user-command
+               (when read-command
+                 (read-from-minibuffer "Command: " Omake.omake-command))))
+          (Omake.Model.create path user-command))
+        ;; Show the error buffer
+        (Omake.show-error-buffer)))))
+
+(defun Omake.reset-visited ()
+  "Reset error visited status"
+  (interactive)
+  (Omake.Model.reset-visited (Omake.Id.current)))
+
+(defun Omake.next-error (&optional user-num)
+  (interactive "P")
+  ;; We'll definitely show the error window, so uniconify the error frame if it exists
+  (Omake.Frame.uniconify 'error)
+  (let* ((model (Omake.Model.current))
+         (result (Omake.model-result model))
+         (status (Omake.model-status model))
+         (comp-dir (Omake.model-compilation-dir model))
+         (current-file (buffer-file-name)))
+    (if (Omake.Result.failure-p result)
+        (message "There is problem with omake.")
+      ;; Otherwise find the error.  The tricky thing is figuring out
+      ;; which error is next.  Recall C-u resets the error index to 0.
+      ;; Cases:
+      ;;   C-u ---> 0
+      ;;   current error (visited)
+      ;;     C-0 ---> 0 (a special case of the next rule)
+      ;;     C-N ---> N mod num-errors
+      ;;   current error (pending)
+      ;;     C-N ---> N mod num-errors       (N <= 0)
+      ;;     C-N ---> (M+N-1) mod num-errors (N >  0)
+      (let* ((ring (Omake.result-ring result))
+             (ring (if current-file
+                       (Omake.Ring.current-file-errors-to-front ring current-file)
+                     ring)))
+        (setf (Omake.model-result model) (Omake.Result.Ring ring))
+        (if (Omake.Ring.is-empty ring)
+            (let* ((ws (Omake.show-error-buffer))
+                   (cw (car ws))
+                   (ew (cdr ws)))
+              (when (equal (window-buffer cw) (window-buffer ew))
+                (let ((db (dired-noselect comp-dir)))
+                  (set-window-buffer cw db)))
+              (if (Omake.Status.polling-p status)
+                  (message "There are no errors.")
+                (message "There are no errors, but omake is still running")))
+          (let* ((current (Omake.ring-current ring))
+                 (user-num
+                  (cond
+                   ;; \C-u sends (list N) where N>0.
+                   ((consp user-num) 0)
+                   ;; No arg is equivalent to \C-1
+                   ((null user-num) 1)
+                   ;; I'm not sure what type '-' has interactively
+                   ;; \C-- is equivalent to \C--\C-1
+                   ((equal (prin1-to-string user-num) "-") -1)
+                   ((integerp user-num) user-num)
+                   (t (error "I can't parse user-num: %s" (prin1-to-string user-num)))))
+                 (user-num
+                  (if (<= user-num 0) user-num
+                    (if current user-num (- user-num 1))))
+                 (num-errors (Omake.Ring.num-errors ring))
+                 (n (mod user-num num-errors))
+                 (_ (assert (< n num-errors)))
+                 (e (Omake.Ring.nth ring n)))
+            ;; Make sure the code frame is visible
+            (Omake.Frame.uniconify 'code)
+            (message "There are errors")
+            (Omake.Error.eval e)))))))
+
+(defun Omake.toggle-expanded-error ()
+  (interactive)
+  (let* ((model (Omake.Model.current))
+         (result (Omake.model-result model)))
+    (when (Omake.Result.ring-p result)
+      (let* ((ring (Omake.result-ring result))
+             (current (Omake.ring-current ring)))
+        (when current
+          (Omake.Error.toggle-full-text current)
+          (Omake.Model.show model))))))
+
+(defun Omake.toggle-verbose ()
+  (interactive)
+  (let* ((model (Omake.Model.current))
+         (verbose (Omake.model-verbose model)))
+    (setf (Omake.model-verbose model) (not verbose))
+    (Omake.Model.show model)))
+
+(defun Omake.setenv ()
+  (interactive)
+  (let ((id (Omake.Id.current)))
+    (Omake.Env.set id)))
+
+(defun Omake.getenv ()
+  (interactive)
+  (let ((id (Omake.Id.current)))
+    (Omake.Env.get id)))
+
+(defun Omake.toggle-env ()
+  (interactive)
+  (let ((id (Omake.Id.current)))
+    (Omake.Env.toggle id)))
+
+(defun Omake.show-raw-buffer ()
+  (interactive)
+  (Omake.Model.create-raw-buffer (Omake.Model.current)))
+
+(defun Omake.show-project-log-buffer ()
+  (interactive)
+  (Omake.Model.create-log-buffer (Omake.Model.current)))
+
+(defun Omake.show-elisp-buffer ()
+  (interactive)
+  (Omake.Model.create-elisp-buffer (Omake.Model.current)))
+
+(defun Omake.show-server-log ()
+  (interactive)
+  (Omake.File.auto-revert Omake.File.server-log "[omake-server-log]"))
+
+(defun Omake.kill-project ()
+  "Kill a project"
+  (interactive)
+  (let ((id (Omake.Id.current)))
+    (unless (Omake.Model.has id) (error "There is no project for %s" (Omake.id-to-string id)))
+    (if (or (not Omake.prompt-before-killing-project)
+            (y-or-n-p (format "Really kill project %s " (Omake.id-to-string id))))
+        (progn
+          (Omake.Model.kill id)
+          (message "Killed")))))
+
+(defun Omake.shutdown ()
+  (interactive)
+  (Omake.Server.stop-and-kill-projects))
+
+(defun Omake.shutdown-and-remove-hooks ()
+  (interactive)
+  (Omake.shutdown)
+  (Omake.remove-hooks))
+
+(defun Omake.set-server-program (p)
+  (unless (equal Omake.Server.program p)
+    (if (Omake.Server.running-p)
+        (message "The server is running. Kill it with M-x Omake.shutdown before changing programs.")
+      (setq Omake.Server.program p))))
+
+(defun Omake.use-light-faces ()
+  (interactive)
+  (enable-theme 'omake-light))
+
+(defun Omake.use-dark-faces ()
+  (interactive)