siam-farmer/watch.el

86 lines
2.9 KiB
EmacsLisp

;;; clj-watch.el --- A pinned, self-overwriting watch buffer -*- lexical-binding: t -*-
;;; Commentary:
;; Polls `watch/render' on a timer and replaces the buffer contents in
;; place. Unlike `cider-tap', nothing is appended -- the buffer always shows
;; the current frame's snapshot and nothing else.
;;
;; Usage: M-x clj-watch / M-x clj-watch-stop
;;
;; Load with: (load "/path/to/project/watch.el")
;;; Code:
(require 'cider-client)
(defvar clj-watch-buffer "*clj-watch*")
(defvar clj-watch-interval 0.2
"Seconds between polls. This is the watch rate, not the frame rate.")
(defvar clj-watch--timer nil)
(defvar clj-watch--connection nil
"The CIDER connection buffer to poll, captured when watching starts.")
(defun clj-watch--paint (text)
"Replace the watch buffer's contents with TEXT."
(when-let* ((buf (get-buffer clj-watch-buffer)))
(let ((tmp (get-buffer-create " *clj-watch-src*")))
(with-current-buffer tmp
(erase-buffer)
(insert text))
(with-current-buffer buf
(let ((inhibit-read-only t))
;; replace-buffer-contents diffs rather than erasing, so point and
;; scroll position survive every tick. erase-buffer + insert would
;; yank the cursor back to the top five times a second.
(replace-buffer-contents tmp))))))
(defun clj-watch--tick ()
"Poll the snapshot once, asynchronously."
(cond
((not (get-buffer clj-watch-buffer)) (clj-watch-stop))
((not (buffer-live-p clj-watch--connection))
(clj-watch--paint "error:\nCIDER connection lost")
(clj-watch-stop))
(t
;; Async, not `cider-nrepl-sync-request': a sync request on a timer blocks
;; Emacs's UI thread every tick. Pass the connection explicitly -- the
;; timer fires with no reliable "current buffer" to resolve one from.
(cider-nrepl-request:eval
"(watch/render)"
(lambda (response)
(nrepl-dbind-response response (value err)
(cond
(err (clj-watch--paint (format "error:\n%s" err)))
(value (clj-watch--paint (car (read-from-string value)))))))
nil nil nil nil
clj-watch--connection))))
(define-derived-mode clj-watch-mode special-mode "clj-watch"
"Major mode for the pinned watch buffer."
(setq-local truncate-lines t))
;;;###autoload
(defun clj-watch ()
"Open the pinned watch buffer and start polling."
(interactive)
(setq clj-watch--connection (cider-current-repl nil 'ensure))
(with-current-buffer (get-buffer-create clj-watch-buffer)
(unless (eq major-mode 'clj-watch-mode)
(clj-watch-mode)))
(when clj-watch--timer (cancel-timer clj-watch--timer))
(setq clj-watch--timer
(run-with-timer 0 clj-watch-interval #'clj-watch--tick))
(display-buffer clj-watch-buffer))
(defun clj-watch-stop ()
"Stop polling."
(interactive)
(when clj-watch--timer
(cancel-timer clj-watch--timer)
(setq clj-watch--timer nil)))
(provide 'clj-watch)
;;; clj-watch.el ends here