A Simple jj Integration for eshell

As the jj cli is composed of many sub commands it’s simple enough to write a dispatcher function based on the first argument

lisp/alc-jj.el
(defun eshell/jj (&rest items)
  "Eshell wrapper around jj."
  (let ((cmd  (car items))
        (args (cdr items)))
    (cond ((string= cmd "log")  (alc-jj-log args))
          ((string= cmd "diff") (alc-jj-diff args))
          (t "Command not supported, run *jj ..."))))

I would have loved for this to transparently fall back to running jj directly, but I couldn’t figure out the incantation to make this work well. I got close with:

(t (throw 'eshell-external
          (eshell-external-command "jj" items)))

which appears to be a common pattern in the eshell codebase. However, eshell wasn’t able to detect that the jj process had exited, forcing me to send a C-c C-c to get the prompt back.

So for now I’ve admitted defeat and just return a message reminding myself to use *jj ... instead which bypasses my wrapper and runs it normally.

jj diff

Rather than dumping the output of jj diff into the eshell window, let’s redirect it to a dedicated buffer. At some point I’d like to build a jj-enhanced diff-mode with convenience commands for sending changes to different revisions.

lisp/alc-jj.el
(defun alc-jj-diff (&rest args)
  "Wrapper around 'jj diff'"
  (let* ((current-dir default-directory)
         (pr (project-current nil))
         (buffer-name (if pr (format "*jj-diff[%s]*" (project-name pr)) "*jj-diff*")))
    (with-current-buffer (get-buffer-create buffer-name)
      ;; Ensure the command uses the current directory from where the command was invoked.
      (setq default-directory current-dir)

      ;; Clear any previous content
      (read-only-mode -1)
      (erase-buffer)

      ;; Call jj, insert output into current buffer
      (apply 'call-process "jj" nil t nil "diff" "--no-pager" "--color" "never" (delq nil args))
      (diff-mode)

      ;; Re-enable read-only mode and show the buffer
      (read-only-mode)
      (pop-to-buffer (current-buffer)))))

jj log

Rather than dumping the output of jj diff into the eshell window, let’s redirect it to a dedicated buffer. Then we can invoke alc-jj-log-view-mode and get all the goodies defined there.

lisp/alc-jj.el
(defun alc-jj-log (&rest args)
  "Wrapper around 'jj log'"
  (let* ((current-dir default-directory)
         (pr (project-current nil))
         (buffer-name (if pr (format "*jj-log[%s]*" (project-name pr)) "*jj-log*")))
    (with-current-buffer (get-buffer-create buffer-name)
      ;; Ensure the command uses the current directory from where the command was invoked.
      (setq default-directory current-dir)

      ;; Clear any previous content
      (read-only-mode -1)
      (erase-buffer)

      ;; Call jj, insert output into current buffer
      (apply 'call-process "jj" nil t nil "log" "--no-pager" "--color" "never" (delq nil args))
      (alc-jj-log-view-mode)

      ;; Re-enable read-only mode and show the buffer
      (read-only-mode)
      (pop-to-buffer (current-buffer)))))