summaryrefslogtreecommitdiff
path: root/emacs
diff options
context:
space:
mode:
authortslil <tslil@posteo.de>2026-09-02 18:59:20 +0100
committertslil <tslil@posteo.de>2026-09-02 19:03:29 +0100
commit601df114c430bbaf7d49b201295b29d6fed2b9b8 (patch)
tree4b728e62efec48c33a8047704775be78a0137d84 /emacs
parentf8b6b5ec5157b42da21e02a03874f8879c9c7305 (diff)
[emacs] monospace some fonts, jj-review.el
Diffstat (limited to 'emacs')
-rw-r--r--emacs/inits/01-basic.el7
-rw-r--r--emacs/inits/15-project-mgmt.el8
-rw-r--r--emacs/inits/20-programming.el8
-rw-r--r--emacs/scripts/jj-review.el245
4 files changed, 263 insertions, 5 deletions
diff --git a/emacs/inits/01-basic.el b/emacs/inits/01-basic.el
index d90138c..f5a745d 100644
--- a/emacs/inits/01-basic.el
+++ b/emacs/inits/01-basic.el
@@ -1,6 +1,6 @@
;;; -*- lexical-binding: t -*-
-;; Time-stamp: <2026-08-04 09h48 BST (a6b530d2)>
+;; Time-stamp: <2026-09-02 16h38 BST (anker)>
;; -----------------------------------------------------------------------------
;; Interface
@@ -110,6 +110,11 @@
'prepend)
(set-face-attribute 'font-lock-comment-face nil :slant 'italic))
+
+ (defun my-mono-font ()
+ (interactive)
+ (buffer-face-set '(:family "Iosevka Term Slab" :height 160))
+ (setq line-spacing nil))
)
(setq-default line-spacing nil)
diff --git a/emacs/inits/15-project-mgmt.el b/emacs/inits/15-project-mgmt.el
index 37da9e4..f21950c 100644
--- a/emacs/inits/15-project-mgmt.el
+++ b/emacs/inits/15-project-mgmt.el
@@ -1,6 +1,6 @@
;;; -*- lexical-binding: t -*-
-;; Time-stamp: <2026-08-26 20h27 BST (anker)>
+;; Time-stamp: <2026-09-02 19h02 BST (anker)>
(defun revert-all-file-buffers ()
"Refresh all open file buffers without confirmation"
@@ -25,3 +25,9 @@
(visual-line-mode 1)))
(add-hook 'log-edit-mode-hook 'my-log-edit-mode)
+
+(use-package jj-review
+ :load-path "~/.config/emacs/scripts/"
+ :hook (jj-review-mode . (lambda ()
+ (my-mono-font)
+ (auto-revert-mode 1))))
diff --git a/emacs/inits/20-programming.el b/emacs/inits/20-programming.el
index cd2dc14..757560e 100644
--- a/emacs/inits/20-programming.el
+++ b/emacs/inits/20-programming.el
@@ -1,6 +1,6 @@
;;; -*- lexical-binding: t -*-
-;; Time-stamp: <2026-08-27 14h00 BST (82909977)>
+;; Time-stamp: <2026-09-02 16h38 BST (anker)>
;; -----------------------------------------------------------------------------
;; General setup
@@ -60,7 +60,8 @@
(use-package eat
:ensure
- :defer)
+ :defer
+ :hook (eat-mode . (lambda () (my-mono-font))))
(defun programming-setup ()
(interactive)
@@ -239,7 +240,8 @@
(ruff-format-on-save-mode)
(ruff-fix-on-save-mode)
(eglot-ensure)
- (visual-line-mode)))
+ (visual-line-mode)
+ (my-mono-font)))
:bind (:map python-ts-mode-map
("C-c C-f" . nil)
diff --git a/emacs/scripts/jj-review.el b/emacs/scripts/jj-review.el
new file mode 100644
index 0000000..fc9567e
--- /dev/null
+++ b/emacs/scripts/jj-review.el
@@ -0,0 +1,245 @@
+;; -*- lexical-binding: t; -*-
+
+;;; jj-review.el --- PR-style review of Jujutsu changes -*- lexical-binding: t; -*-
+
+;; Author: tslil clingman
+;; Package-Requires: ((emacs "29.1"))
+
+(require 'diff-mode)
+(require 'outline)
+(require 'ansi-color)
+
+(defgroup jj-review nil
+ "PR-style review of jj changes."
+ :group 'vc)
+
+(defcustom jj-review-executable "jj"
+ "Name or path of the jj executable."
+ :type 'string)
+
+(defcustom jj-review-open-collapsed t
+ "If non-nil, open review buffers with all file sections folded."
+ :type 'boolean)
+
+(defvar-local jj-review--root nil
+ "Repository root of this review buffer.")
+
+(defvar-local jj-review--revset nil
+ "Revset this buffer is reviewing.")
+
+(defvar jj-review--revset-history nil)
+
+;;;; jj plumbing
+
+(defun jj-review--find-root ()
+ "Return the jj repository root containing `default-directory'."
+ (or (locate-dominating-file default-directory ".jj")
+ (user-error "Not inside a jj repository")))
+
+(defun jj-review--run (root &rest args)
+ "Run jj with ARGS in ROOT and return stdout as a string.
+stderr is kept separate (jj prints hints and warnings there) and
+included in the `user-error' signalled on nonzero exit."
+ (let ((err-file (make-temp-file "jj-review-stderr")))
+ (unwind-protect
+ (with-temp-buffer
+ (let* ((default-directory root)
+ (exit (apply #'call-process jj-review-executable nil
+ (list t err-file) nil
+ "--color=never" args)))
+ (unless (eql exit 0)
+ (user-error "jj %s: %s"
+ (string-join args " ")
+ (string-trim
+ (with-temp-buffer
+ (insert-file-contents err-file)
+ (buffer-string)))))
+ (buffer-string)))
+ (delete-file err-file))))
+
+;;;; Locating the file at point
+
+(defun jj-review--paths ()
+ "Return (BEFORE-PATH . AFTER-PATH) for the file section at point.
+Paths are repository-relative, taken from the nearest preceding
+\"diff --git a/... b/...\" header."
+ (save-excursion
+ (end-of-line)
+ (if (re-search-backward "^diff --git a/\\(.*\\) b/\\(.*\\)$" nil t)
+ (cons (match-string-no-properties 1)
+ (match-string-no-properties 2))
+ (user-error "No file at or before point"))))
+
+;;;; jj's own diff formatter (difftastic etc.), ANSI-rendered
+
+(defun jj-review--formatted-diff-buffer (root revset &optional path)
+ "Return a buffer with `jj diff -r REVSET' for ROOT, colors rendered.
+Restrict to PATH if non-nil. Uses whatever diff formatter jj is
+configured with; DFT_COLOR/DFT_WIDTH are set so difftastic emits
+color at the current window's width despite writing to a pipe."
+ (let ((buf (get-buffer-create
+ (format "*jj diff: %s*" (or path revset))))
+ (process-environment
+ (append (list "DFT_COLOR=always"
+ (format "DFT_WIDTH=%d" (window-width)))
+ process-environment)))
+ (with-current-buffer buf
+ (let ((inhibit-read-only t)
+ (default-directory root))
+ (erase-buffer)
+ (let ((exit (apply #'call-process jj-review-executable nil t nil
+ "--color=always" "diff" "-r" revset
+ (and path (list "--" path)))))
+ (unless (eql exit 0)
+ (user-error "jj diff: %s" (string-trim (buffer-string)))))
+ (ansi-color-apply-on-region (point-min) (point-max)))
+ (setq default-directory root)
+ (goto-char (point-min))
+ (special-mode))
+ buf))
+
+;;;; Commands
+
+(defun jj-review-diff-file ()
+ "Show `jj diff' for the file at point in jj's configured formatter."
+ (interactive)
+ (pop-to-buffer
+ (jj-review--formatted-diff-buffer jj-review--root jj-review--revset
+ (cdr (jj-review--paths)))))
+
+(defun jj-review-diff-revision ()
+ "Show `jj diff' for the whole revision in jj's configured formatter."
+ (interactive)
+ (pop-to-buffer
+ (jj-review--formatted-diff-buffer jj-review--root jj-review--revset)))
+
+(defun jj-review-collapse-all ()
+ "Fold every file section, leaving the file list."
+ (interactive)
+ (outline-hide-sublevels 1))
+
+(defun jj-review-expand-all ()
+ "Unfold everything."
+ (interactive)
+ (outline-show-all))
+
+;;;; Staying up to date (auto-revert wiring)
+
+(defvar-local jj-review--commit-ids nil
+ "Commit ids the revset denoted when this buffer was last filled.")
+
+(defun jj-review--query-commit-ids (root revset)
+ "Return the commit ids REVSET currently denotes in ROOT, as one string.
+Running jj snapshots the working copy, so on-disk edits are
+reflected in the result."
+ (string-trim
+ (jj-review--run root "log" "--no-graph" "-r" revset "-T" "commit_id")))
+
+(defun jj-review--stale-p (&optional _noconfirm)
+ "Non-nil if the revset's commit ids changed since the last fill.
+Errors (e.g. the revset no longer resolving) count as not stale,
+so a broken state doesn't spam; refresh manually with \\[revert-buffer]."
+ (and jj-review--root jj-review--revset
+ (condition-case nil
+ (not (equal (jj-review--query-commit-ids jj-review--root
+ jj-review--revset)
+ jj-review--commit-ids))
+ (error nil))))
+
+(defun jj-review--expanded-files ()
+ "Return after-paths of file sections whose bodies are visible."
+ (let (res)
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward "^diff --git a/.* b/\\(.*\\)$" nil t)
+ (unless (invisible-p (min (line-end-position) (point-max)))
+ (push (match-string-no-properties 1) res))))
+ (nreverse res)))
+
+(defun jj-review--expand-files (paths)
+ "Reveal the file sections whose after-path is in PATHS."
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward "^diff --git a/.* b/\\(.*\\)$" nil t)
+ (when (member (match-string-no-properties 1) paths)
+ (beginning-of-line)
+ (outline-show-subtree)
+ (end-of-line)))))
+
+(defun jj-review--revert (&optional _ignore-auto _noconfirm)
+ "Refill the review buffer, preserving point and per-file fold state.
+Installed as `revert-buffer-function', so both \\[revert-buffer]
+and `auto-revert-mode' use it."
+ (let ((expanded (jj-review--expanded-files))
+ (pos (point)))
+ (jj-review--fill (current-buffer) jj-review--root jj-review--revset)
+ (jj-review--expand-files expanded)
+ (goto-char (min pos (point-max)))))
+
+;;;; Mode and buffer construction
+
+(defvar-keymap jj-review-mode-map
+ :doc "Keymap for `jj-review-mode'."
+ "n" #'diff-hunk-next
+ "p" #'diff-hunk-prev
+ "N" #'diff-file-next
+ "P" #'diff-file-prev
+ "RET" #'diff-goto-source
+ "TAB" #'outline-toggle-children
+ "<backtab>" #'outline-cycle-buffer
+ "d" #'jj-review-diff-file
+ "D" #'jj-review-diff-revision
+ "c" #'jj-review-collapse-all
+ "x" #'jj-review-expand-all
+ "g" #'revert-buffer
+ "q" #'quit-window)
+
+(define-derived-mode jj-review-mode diff-mode "jj-review"
+ "PR-style review of a jj revision.
+An ordinary read-only `diff-mode' buffer with per-file folding
+and access to the before/after version of each file.
+
+\\{jj-review-mode-map}"
+ (setq buffer-read-only t)
+ (setq-local revert-buffer-function #'jj-review--revert
+ buffer-stale-function #'jj-review--stale-p)
+ (outline-minor-mode 1))
+
+(defun jj-review--fill (buf root revset)
+ "Fill BUF with the review of REVSET in ROOT."
+ (let ((ids (jj-review--query-commit-ids root revset))
+ (stat (jj-review--run root "diff" "--stat" "-r" revset))
+ (diff (jj-review--run root "diff" "--git" "-r" revset)))
+ (when (string-empty-p (string-trim diff))
+ (user-error "Revset %s introduces no changes" revset))
+ (with-current-buffer buf
+ (let ((inhibit-read-only t))
+ (erase-buffer)
+ (insert "Reviewing: " revset "\n\n" stat "\n" diff))
+ (setq default-directory root)
+ (unless (derived-mode-p 'jj-review-mode)
+ (jj-review-mode))
+ (setq jj-review--root root
+ jj-review--revset revset
+ jj-review--commit-ids ids)
+ (when jj-review-open-collapsed
+ (jj-review-collapse-all))
+ (goto-char (point-min)))
+ buf))
+
+(defun jj-review-buffer (revset &optional dir)
+ "Return a review buffer for REVSET in the repo containing DIR."
+ (let* ((default-directory (or dir default-directory))
+ (root (jj-review--find-root))
+ (buf (get-buffer-create (format "*jj-review: %s*" revset))))
+ (jj-review--fill buf root revset)))
+
+;;;###autoload
+(defun jj-review (revset)
+ "Review the changes introduced by REVSET (default @), PR-style."
+ (interactive
+ (list (read-string "Revset (default @): " nil 'jj-review--revset-history "@")))
+ (pop-to-buffer (jj-review-buffer revset)))
+
+(provide 'jj-review)
+;;; jj-review.el ends here