;;; 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