My Emacs Configuration

lisp
alc-eshell.el

;;; alc-eshell.el --- TODO add description  -*- lexical-binding: t -*-

;;; Code:
(require 'alc-python)

(defun eshell/http-server (dirname &rest args)
  "Use `start-process' to invoke a background Python http.server in
the given DIRNAME"
  (let ((port (alc-eshell--get-port-number)))
    (start-process (format "http:%s" port)
                   (format "*http-server<%s>*" dirname)
                   (or python-shell-interpreter "python")
                   "-m" "http.server"
                   "-d" dirname
                   "-b" "0.0.0.0"
                   (format "%s" port))
     (browse-url (format "http://localhost:%s" port))))


(defun alc-eshell--get-port-number ()
  (alc-python-one-liner "
import socket

s = socket.socket()
s.bind(('', 0))
print(s.getsockname()[1], end = '')
s.close()"))

(provide 'alc-eshell)

;;; alc-eshell.el ends here
alc-git.el

;;; alc-git.el --- TODO add description  -*- lexical-binding: t -*-

;;; Code:
(defun alc-git-permalink-to-kill-ring ()
  "Construct a permalink URL for the current line or region and place it on
the kill-ring"
  (interactive)
  (if-let* ((repo (alc-git-get-remote-url))
            (commit (alc-git-get-commit))
            (filepath (alc-git-get-filepath))
            (pos (alc-git-get-position))
            (path-start (format "%s#L%s" filepath (nth 0 pos)))
            (url (s-join "/" `(,repo "blob" ,commit ,path-start))))
      (kill-new
       (if (nth 1 pos)
           (format "%s-L%s" url (nth 1 pos))
         url))
    (message "No permalink found")))

(defun alc-git-get-remote-url ()
  "Return the url of the remote associated with the current buffer.

Prioritizes 'upstream' but will fall back to 'origin' if it is unavailable."
  (condition-case err
      (vc-git-repository-url buffer-file-name "upstream")
    (error err
           (condition-case err
               (vc-git-repository-url buffer-file-name "origin")
             (error err nil)))))

(defun alc-git-get-commit ()
  "Return the commit hash for the current file"
  (vc-working-revision buffer-file-truename))

(defun alc-git-get-filepath ()
  "Return the filepath of the current buffer relative to the root of the
repository"
  (string-remove-prefix (vc-git-root buffer-file-truename)
                        buffer-file-truename))

(defun alc-git-get-position ()
  "Return the start and end line number"
  (if (region-active-p)
      `(,(line-number-at-pos (region-beginning))
        ,(line-number-at-pos (region-end)))
    `(,(line-number-at-pos) nil)))

(provide 'alc-git)

;;; alc-git.el ends here
alc-jj.el

;;; alc-jj.el --- TODO add description  -*- lexical-binding: t -*-

;;; Code:
;; Keymap must be defined before the major-mode in order to take effect.
(defvar alc-jj-log-view-mode-map
  (let ((map (make-sparse-keymap)))
    (keymap-set map "B" #'alc-jj-log-view-new-before)
    (keymap-set map "A" #'alc-jj-log-view-new-after)
    (keymap-set map "e" #'alc-jj-log-view-edit)
    (keymap-set map "d" #'alc-jj-log-view-describe)
    (keymap-set map "g" #'alc-jj-log-view-reload)
    map))

(define-derived-mode alc-jj-log-view-mode special-mode "jj-log"
  "Major mode for viewing and manipulating the jj log"
  (when (treesit-ready-p 'jjlog)
    (treesit-parser-create 'jjlog)
    (alc-jj-log-view-ts-setup)))

(defvar alc-jj-log-view-font-lock-rules
  '(:language jjlog
    :override t
    :feature elided
    ([((elided_revisions) @font-lock-comment-face)])
    :language jjlog
    :override t
    :feature working-copy
    ([((working_copy) @success)])
    :language jjlog
    :override t
    :feature refs
    ([((revision (ref) @font-lock-function-name-face))])
    :language jjlog
    :override t
    :feature conflicted
    ([(conflicted_change) "(conflict)"] @error)
    :language jjlog
    :override t
    :feature metadata
    ([(email) (datetime)] @shadow)
    :language jjlog
    :override t
    :feature change-ids
    ([((revision change_id: (ref) @font-lock-keyword-face))])))

(defun alc-jj-log-view-ts-setup ()
  "Setup treesit for alc-jj-log-view mode."
  (setq-local font-lock-defaults nil)
  (setq-local treesit-font-lock-feature-list
              '((change-ids
                 conflicted
                 elided
                 metadata
                 refs
                 working-copy)))

  (setq-local treesit-font-lock-settings
               (apply #'treesit-font-lock-rules alc-jj-log-view-font-lock-rules))
  (treesit-major-mode-setup))

(defvar alc-jj-log-view-combobulate-definitions
  '((procedures-sibling
     '((:activation-nodes
        ((:nodes ("revision")))
        :selector (:choose node :match-siblings (:match-rules ("revision"))))))))

(define-combobulate-language
 :name jjlog
 :language jjlog
 :major-modes (alc-jj-log-view-mode)
 :custom alc-jj-log-view-combobulate-definitions)

(defun alc-jj-log-view--change-at-point ()
  "Return the change id for the revision under point, if point is not on a revision
returns nil."
  (let ((node (treesit-node-at (point))))
    (while (not (or (string= (treesit-node-type node) "revision")
                    (null (treesit-node-parent node))))
      (setq node (treesit-node-parent node)))
    ;; Make sure we found a revision
    (if (string= (treesit-node-type node) "revision")
        (substring-no-properties
         (treesit-node-text
          (treesit-node-child-by-field-name node "change_id"))))))

(defun alc-jj-log-view--select-revision-with-change-id (change-id)
  "Given a CHANGE-ID move point to the corresponding revision."
  (if-let ((nodes (treesit-filter-child
                   (treesit-buffer-root-node)
                   (lambda (n) (string= change-id
                                        (treesit-node-text (treesit-node-child-by-field-name n "change_id")))))))
    (goto-char (treesit-node-start (car nodes)))))

(defun alc-jj-log-view-new-before ()
  "Insert a new revision before the revision under point"
  (interactive)
  (if-let ((change-id (alc-jj-log-view--change-at-point)))
    (progn
      (call-process "jj" nil nil nil "new" "--no-edit" "--insert-before" change-id)
      (alc-jj-log-view-reload))))

(defun alc-jj-log-view-new-after ()
  "Insert a new revision after the revision under point"
  (interactive)
  (if-let ((change-id (alc-jj-log-view--change-at-point)))
    (progn
      (call-process "jj" nil nil nil "new" "--no-edit" "--insert-after" change-id)
      (alc-jj-log-view-reload))))

(defun alc-jj-log-view-edit ()
  "Edit the revision under point."
  (interactive)
  (if-let ((change-id (alc-jj-log-view--change-at-point)))
    (progn
      (call-process "jj" nil nil nil "edit" change-id)
      (alc-jj-log-view-reload))))

(defun alc-jj-log-view-describe ()
  "Edit the commit message for the revision under point"
  (interactive)
  (if-let ((change-id (alc-jj-log-view--change-at-point)))
    (progn
      (call-process "jj" nil 0 nil "describe" change-id)
      (alc-jj-log-view-reload))))

(defun alc-jj-log-view-reload ()
  "Regenerate the log view buffer's content."
  (interactive)
  (let ((change-id (alc-jj-log-view--change-at-point)))
    (alc-jj-log)
    (if change-id
      (alc-jj-log-view--select-revision-with-change-id change-id))))

(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 ..."))))

(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)))))

(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)))))

(provide 'alc-jj)

;;; alc-jj.el ends here
alc-modeline.el

;;; alc-modeline.el --- TODO add description  -*- lexical-binding: t -*-

;;; Code:
(defgroup alc-modeline nil
  "My custom modeline"
  :group 'mode-line)

(defgroup alc-modeline-faces nil
  "Faces for my custom modeline"
  :group 'alc-modeline)

(defface alc-modeline-project-id-face
  '((default :inherit (bold)))
  "Face for styling the project indicator"
  :group 'alc-modeline-faces)

(defvar-local alc-modeline-project-identification
 '(:eval
   (if-let ((pr (project-current))
            (file (buffer-file-name)))
       (propertize (format "🖿 %s " (project-name pr))
                   'face 'alc-modeline-project-id-face))))
(put 'alc-modeline-project-identification 'risky-local-variable t)

(defvar-local alc-modeline-remote-indication
    '(:eval
       (when (file-remote-p default-directory)
         (propertize " ☁ "
                     'face '(bold)))))
(put 'alc-modeline-remote-indication 'risky-local-variable t)

(defun alc-modeline-buffer-identification-face ()
  "Return the face(s) to apply to the buffer name in the modeline."
  (cond ((and (buffer-file-name)
              (buffer-modified-p))
         'error)
        (buffer-read-only '(italic mode-line-buffer-id))
        (t 'mode-line-buffer-id)))

(defvar-local alc-modeline-buffer-identification
    '(:eval
       (propertize "%b"
                   'face (alc-modeline-buffer-identification-face))))
(put 'alc-modeline-buffer-identification 'risky-local-variable t)

(defun alc-modeline-buffer-position-face ()
  "Return the face(s) to apply to the buffer position in the modeline."
  (if (mode-line-window-selected-p)
      'mode-line
    'mode-line-inactive))

(defvar-local alc-modeline-buffer-position
    '(:eval
      (propertize "%l:%c"
                  'face (alc-modeline-buffer-position-face))))
(put 'alc-modeline-buffer-position 'risky-local-variable t)

(defface alc-modeline-window-dedicated-face
  '((default :inherit (bold)))
  "Face for styling the dedicated window indicator"
  :group 'alc-modeline-faces)

(defvar-local alc-modeline-window-dedicated
    '(:eval
      (when (window-dedicated-p)
        (propertize "🖈 "
                    'face 'alc-modeline-window-dedicated-face))))
(put 'alc-modeline-window-dedicated 'risky-local-variable t)

(with-eval-after-load 'modus-themes
  (defun alc-modeline-apply-modus-colors ()
    "Style the modeline using colors provided by the `modus-themes'"
    (if (modus-themes--current-theme) ; Only if a modus-theme is active.
        (modus-themes-with-colors
          (set-face-attribute 'mode-line nil
                              :background bg-active
                              :overline bg-active
                              :box `(:line-width 1 :color ,bg-active :style nil))
          (set-face-attribute 'mode-line-inactive nil
                              :background bg-inactive
                              :overline bg-inactive
                              :box `(:line-width 1 :color ,bg-inactive :style nil))
          (set-face-attribute 'alc-modeline-project-id-face nil
                              :background 'unspecified
                              :foreground blue))))

  (alc-modeline-apply-modus-colors)
  (add-hook 'modus-themes-after-load-theme-hook #'alc-modeline-apply-modus-colors))

(provide 'alc-modeline)

;;; alc-modeline.el ends here
alc-python.el

;;; alc-python.el --- TODO add description  -*- lexical-binding: t -*-

;;; Code:
(defun alc-python-env-select ()
  "Select from a list of available Python environments, return the path to
the Python executable"
  (if-let ((prj (project-current)))
      (cond ((alc-python-project-poetry-p prj)
             (alc-python-env-poetry-select prj))
            ((alc-python-project-hatch-p prj)
             (alc-python-env-hatch-select prj))
            ((alc-python-project-venv-p prj)
             (alc-python-env-venv-select prj))
            (t (message "No python environments detected.")))))

(defun alc-python-env-activate ()
  "Select a Python environment and activate it."
  (interactive)
  (if-let ((selected-env (alc-python-env-select))
           (default-directory (project-root (project-current))))
      (progn
        (setq-local python-shell-interpreter selected-env)
        (modify-dir-local-variable 'python-mode
                                   'python-shell-interpreter
                                   selected-env
                                   'add-or-replace)
        (modify-dir-local-variable 'python-mode
                                   'eglot-workspace-configuration
                                   `(:python
                                     (:pythonPath ,selected-env)
                                     :python.analysis
                                     (:logLevel "trace"))
                                   'add-or-replace)
        ;; Annoyingly, `modify-dir-local-variable' will leave the
        ;; .dir-locals.el file open in an unsaved buffer
        (save-buffer)
        (kill-buffer))))

(defun alc-python-project-hatch-p (prj)
  "Return t if the given Python PRJ is managed by hatch."
  (let ((root (project-root prj)))
    (or (file-exists-p (concat root "hatch.toml"))
        (not (null (gethash "hatch"
                            (gethash "tool"
                                     (alc-python-load-toml (concat root "pyproject.toml"))
                                     (make-hash-table))))))))

(defun alc-python-env-hatch-discover (prj)
  "Return a list of hatch managed environments available in PRJ"
  (if-let* ((default-directory (project-root prj))
            (hatch-exe (executable-find "hatch"))
            (hatch-cmd (format "%s env show --json" hatch-exe))
            (hatch-envs (json-parse-string
                         (shell-command-to-string hatch-cmd))))
      (let ((envs ()))
        (maphash (lambda (k v) (push k envs))
                 hatch-envs)
        envs)))

(defun alc-python-env-hatch-select (prj)
  "Select a Hatch environment defined by the given PRJ"
  (if-let* ((default-directory (project-root prj))
            (hatch-envs (alc-python-env-hatch-discover prj))
            (selected-env (completing-read "Select env: " hatch-envs))
            (hatch-exe (executable-find "hatch"))
            (hatch-cmd (format "%s -e %s env run python -- -c 'import sys;print(sys.executable)'"
                               hatch-exe selected-env)))
      (car (reverse (split-string
                     (string-trim (shell-command-to-string hatch-cmd))
                     "\n")))))

(defun alc-python-project-poetry-p (prj)
  "Return t if the given Python PRJ is managed by poetry."
  (file-exists-p (concat (project-root prj) "poetry.lock")))

(defun alc-python-env-poetry-discover (prj)
  "Return a list of poetry managed environments available in PRJ"
  (if-let* ((default-directory (project-root prj))
            (poetry-exe (executable-find "poetry"))
            (poetry-cmd (format "%s env list" poetry-exe))
            (poetry-envs (shell-command-to-string poetry-cmd)))
      (seq-filter
       (lambda (s) (not (string= s "(Activated)")))
       (split-string poetry-envs))))

(defun alc-python-env-poetry-select (prj)
  "Select a Poetry environment defined by the given PRJ"
  (if-let* ((default-directory (project-root prj))
            (poetry-envs (alc-python-env-poetry-discover prj))
            (selected-env (completing-read "Select env: " poetry-envs))
            (poetry-exe (executable-find "poetry")))
    (string-trim (shell-command-to-string (format "%s env info --executable" poetry-exe)))))

(defun alc-python-project-venv-p (prj)
  "Return t if the given Python PRJ has a virtual envrionment in the project folder."
  (when (directory-files-recursively (project-root prj) "pyvenv.cfg") t))

(defun alc-python-env-venv-select (prj)
  "Select a venv for the given PRJ"
  (if-let* ((venv-envs (mapcar #'file-name-directory
                               (directory-files-recursively (project-root prj) "pyvenv.cfg")))
            (selected-env (completing-read "Select env: " venv-envs)))
    (concat selected-env "bin/python")))

(defun alc-python-library-file-p (file-name)
  "Determine if the given FILE-NAME is a library file"
  (or  (string-match-p "site-packages/" file-name)
       (string-match-p "typeshed-fallback/" file-name)
       (string-match-p "/usr/lib\\(64\\)?/" file-name)))

(defun alc-python-load-toml (filename)
  "Load the toml in FILENAME as json, utilising the TOML parser in the
  Python standard library"
  (let ((cmd (string-join `("python" "-c"
                            "'import json,pathlib,sys,tomllib;print(json.dumps(tomllib.loads(pathlib.Path(sys.argv[1]).read_text())))'"
                            ,filename)
                          " ")))
    (json-parse-string (shell-command-to-string cmd))))

(defun alc-python-one-liner (code)
  "Take a string of python CODE, flatten it into one line and pass it to python -c.

Respects `python-shell-interpreter'."
  (let* ((lines (s-split "\n" code t))
         (one-liner (s-join ";" lines)))
    (shell-command-to-string (concat (or python-shell-interpreter "python")
                                     " -c \""
                                     one-liner
                                     "\""))))

(defun alc-python--pid-to-candidate (pid)
  "Convert the given PID into a completion candidate for `completing-read'"
  (let* ((process (process-attributes pid))
         (cmd (alist-get 'args process)))
    (if (string= (user-login-name) (alist-get 'user process))
      (cons (format "[%s] %s" pid cmd) pid))))

(defun alc-python--select-process-pid ()
   "Select a process id via `completing-read'"
   (let* ((processes (mapcar 'alc-python--pid-to-candidate (list-system-processes)))
          (selection (completing-read "Select process: " processes nil t)))
     (cdr (assoc-string selection processes))))

(defun alc-python-pdb-attach-to-process ()
   "Attach a pdb session to a running Python process."
   (interactive)
   (when-let* ((pid (alc-python--select-process-pid))
               (prj (project-current nil))
               (default-directory (project-root prj)))
     (pdb (format "uv run --no-project --python 3.14 python -m pdb -p %s" pid))))

(provide 'alc-python)

;;; alc-python.el ends here
alc-tab-bar.el

;;; alc-tab-bar.el --- TODO add description  -*- lexical-binding: t -*-

;;; Code:
(defun alc-tab-bar-tab-name-format-hints (name _tab i)
  (if tab-bar-tab-hints (concat (format "-%d-" i) "") name))

(defun alc-tab-bar-tab-group-format-function (tab i &optional current-p)
  (propertize
   (concat (funcall tab-bar-tab-group-function tab))
   'face (if current-p 'tab-bar-tab-group-current 'tab-bar-tab-group-inactive)))

(defun alc-tab-bar-project-other-tab-command ()
  "Run a project command, displaying resultant buffer in a new tab.

This implemented by hacking together parts of
`project--other-place-prefix' and `other-tab-prefix'.

The only difference between this version and `project-other-tab-command'
is that new tabs are automatically placed in a group named according to
the corresponding project.
"
  (interactive)
  (prefix-command-preserve-state)
  (display-buffer-override-next-command
   (lambda (buffer alist)
     (cons (progn
             (display-buffer-in-tab
              buffer (append alist '((tab-group . alc-tab-bar-buffer-to-project-name)
                                     (inhibit-same-window . nil))))
             (selected-window))
           'tab))
   nil "[other-tab]")
  (message "Display next project command buffer in a new tab...")
  (set-transient-map project-prefix-map))

(defun alc-tab-bar-buffer-to-project-name (buffer alist)
  "Given BUFFER, return the corresponding project name, if any"
  (with-current-buffer buffer
    (if-let ((pr (project-current nil)))
        (format "[%s]" (project-name pr)))))

(display-battery-mode)

(setq display-time-format "%H:%M %d/%m/%y"
      display-time-default-load-average nil)
(display-time-mode)

(provide 'alc-tab-bar)

;;; alc-tab-bar.el ends here
alc-theme.el

;;; alc-theme.el --- TODO add description  -*- lexical-binding: t -*-

;;; Code:
(defvar alc-theme-load-light-theme-function nil
  "The function to call in order to load the configured light theme")

(defvar alc-theme-load-dark-theme-function nil
  "The function to call in order to load the configured dark theme")

(defun alc-theme-load-theme (variant)
  "Load the light or dark theme according to VARIANT."
  (pcase variant
    ('light (funcall alc-theme-load-light-theme-function))
    ('dark (funcall alc-theme-load-dark-theme-function))
    (_ (error "Unknown theme variant: %s" variant))))

(defun alc-theme--system-preference-to-variant (pref)
  "Simple helper function for converting the system PREF into the
corresponding variant."
  (if (eq pref 1) 'dark 'light))

(require 'dbus)
(defun alc-theme-sync-to-system-theme (_frame)
  "Choose a light/dark theme based on the current system preference."
  (let ((pref (caar (dbus-call-method
                     :session
                     "org.freedesktop.portal.Desktop"
                     "/org/freedesktop/portal/desktop"
                     "org.freedesktop.portal.Settings" "Read"
                     "org.freedesktop.appearance" "color-scheme"))))
    (alc-theme-load-theme (alc-theme--system-preference-to-variant pref))
    (alc-theme--register-dbus-listener)))

(defvar alc-theme--dbus-listener nil
  "Variable in which to store the dbus listener.")

(defun alc-theme--register-dbus-listener ()
  "Register (if necessary) a DBus listener to react to changes in the
system's light/dark preference."
  (unless alc-theme--dbus-listener
    (setq alc-theme--dbus-listener
          (dbus-register-signal
           :session
           "org.freedesktop.portal.Desktop"
           "/org/freedesktop/portal/desktop"
           "org.freedesktop.portal.Settings"
           "SettingChanged"
           (lambda (path var val)
             (when (and (string= path "org.freedesktop.appearance")
                        (string= var "color-scheme"))
               (alc-theme-load-theme
                (alc-theme--system-preference-to-variant (car val)))))))))

(provide 'alc-theme)

;;; alc-theme.el ends here
alc-treesitter.el

;;; alc-treesitter.el --- TODO add description  -*- lexical-binding: t -*-

;;; Code:
(setq treesit-language-source-alist
      '((javascript "https://github.com/tree-sitter/tree-sitter-javascript" "v0.20.1" "src")
        (python "https://github.com/tree-sitter/tree-sitter-python" "v0.20.4")
        (tsx "https://github.com/tree-sitter/tree-sitter-typescript" "v0.20.3" "tsx/src")
        (typescript "https://github.com/tree-sitter/tree-sitter-typescript" "v0.20.3" "typescript/src")))

(if (treesit-language-available-p 'python)
  (add-to-list 'major-mode-remap-alist '(python-mode . python-ts-mode)))

(if (treesit-language-available-p 'javascript)
  (progn
    (add-to-list 'major-mode-remap-alist '(js-mode . js-ts-mode))
    (add-to-list 'major-mode-remap-alist '(js2-mode . js-ts-mode))))

(if (treesit-language-available-p 'typescript)
  (add-to-list 'major-mode-remap-alist '(typescript-mode . typescript-ts-mode)))

 (use-package combobulate
  :vc (:url "https://github.com/mickeynp/combobulate" :rev "master")
  :hook ((js-ts-mode . combobulate-mode)
         (python-ts-mode . combobulate-mode)
         (typescript-ts-mode . combobulate-mode)))

(provide 'alc-treesitter)

;;; alc-treesitter.el ends here
tree-sitter-grammars
jjlog
grammar.js

/// <reference types="tree-sitter-cli/dsl" />
// @ts-check

export default grammar({
name: "jjlog",

extras: $ => [
  /\s/, '|', '├', '─', '╯',
],

rules: {
  source_file: $ => repeat($._revision),

  _revision: $ => choice(
    $.revision,
    $.elided_revisions,
  ),

revision: $ => seq(
  $._node_type,
  $._change_metadata,
  optional('(conflict)'),
  $._description,
),

_node_type: $ => choice($.working_copy, $.immutable_change, $.conflicted_change, $.normal_change),

working_copy:      $ => '@',
immutable_change:  $ => '◆',
conflicted_change: $ => '×',
normal_change:     $ => '○',
elided_revisions:  $ => seq('~', optional('(elided revisions)')),

  _change_metadata: $ => seq(
    field("change_id", $.ref),
    field("author", $.email),
    field("timestamp", $.datetime),
    repeat($.ref),
  ),

  ref:         $ => /[-a-z0-9@*A-z]+/,
  email:       $ => /[^\s]+@[^\s]+/,
  datetime:    $ => /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/,

  _description: $ => seq(/[\n\r]/, $.description, /[\n\r]/),
  description: $ => /[^\n\r]+/,
}
});
early-init.el

(blink-cursor-mode -1)
(tool-bar-mode -1)

(setq inhibit-x-resources t
      inhibit-startup-message t)

(context-menu-mode t)
init.el

;;; init.el --- TODO add description  -*- lexical-binding: t -*-

;;; Code:
(setq custom-file (locate-user-emacs-file "custom.el"))
(when (file-exists-p custom-file)
 (load custom-file))

(package-initialize)
(add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/"))
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))

(dolist (pkg '(s))
  (unless (package-installed-p pkg)
    (unless package-archive-contents
      (package-refresh-contents))
    (package-install pkg)))

(if (file-exists-p "/home/linuxbrew/.linuxbrew/bin")
    (add-to-list 'exec-path "/home/linuxbrew/.linuxbrew/bin"))

(if (file-exists-p "/home/alex/.local/bin")
    (add-to-list 'exec-path "/home/alex/.local/bin"))

(setenv "PATH" (string-join exec-path ":"))

(recentf-mode t)
(savehist-mode t)
(save-place-mode t)

(setq backup-directory-alist `(("." . ,(expand-file-name "backups" user-emacs-directory))))

(repeat-mode 1)

(setq whitespace-style '(face empty trailing lines-char tab-mark))
(add-hook 'prog-mode-hook 'whitespace-mode)

(add-hook 'before-save-hook #'whitespace-cleanup)

(setq tab-always-indent t)
(setq-default indent-tabs-mode nil)

(setq compilation-always-kill t
      compilation-scroll-output t)

(add-hook 'compilation-filter-hook 'ansi-color-compilation-filter)

(add-to-list 'display-buffer-alist
             '("\\*compilation\\*"
               (display-buffer-reuse-window
                display-buffer-in-previous-window
                display-buffer-reuse-mode-window
                display-buffer-at-bottom)
               (window-height . 0.25)))

(keymap-global-set "<f5>" 'recompile)

(define-minor-mode recompile-on-save-mode
  "When enabled, run `recompile' after the current buffer is saved"
  :lighter " ⭮"
  (if recompile-on-save-mode
      (add-hook 'after-save-hook 'recompile nil t)
    (remove-hook 'after-save-hook 'recompile t)))

(use-package dired
 :hook ((dired-mode . dired-hide-details-mode)
        (dired-mode . hl-line-mode))
 :config
 (setq dired-dwim-target t
       dired-maybe-use-globstar t
       dired-isearch-filenames 'dwim
       ;; -A, all files - except '.' & '..'
       ;; -G, omit owning group
       ;; -h, human readable file sizes
       ;; -l, long listing, required for dired.
       dired-listing-switches "-AGhl --group-directories-first --time-style=long-iso"))

(setq project-vc-extra-root-markers '("Cargo.toml"
                                      "package.json"
                                      "pyproject.toml"))

(defun alc-project-try-vc-subproject (orig-fun &rest args)
  "Advice for `project-try-vc'.

When using `project-vc-extra-root-markers' to teach project.el
about subprojects within a monorepo, `project-try-vc'
successfully finds the subject's root but fails to detect the
backend. But by calling `vc-responsible-backend' on the found
root, we can fill in the blanks.

As a result, commands like `project-find-file' now respect the
parent repo's .gitignore file even when being run from within a
subproject."
  (let* ((res (apply orig-fun args))
         (dir (nth 2 res))
         (backend (or (nth 1 res)
                      (ignore-errors (vc-responsible-backend dir)))))
    (if dir
        `(vc ,backend ,dir))))

(advice-add 'project-try-vc :around #'alc-project-try-vc-subproject)

(use-package apheleia
  :ensure t
  :config
  (apheleia-global-mode))

(use-package js
  :mode ("\\.js\\'" . js-mode)
  :custom
  (js-indent-level 2)
  :hook ((js-ts-mode . eglot-ensure)))

(use-package typescript-ts-mode
  :mode "\\.ts\\'"
  :hook ((typescript-ts-mode . eglot-ensure)))

(use-package yaml-mode
  :ensure t)

(use-package gptel
  :ensure t
  :bind (("C-c RET" . gptel-send))
  :config
  (setq gptel-model 'llama3.2:latest)
  (setq gptel-backend (gptel-make-ollama "Ollama"
                       :host "localhost:11434"
                       :stream t
                       :models '(llama3.2:latest))))

(use-package magit
  :ensure t
  :defer
  :custom
  (magit-format-file-function #'magit-format-file-nerd-icons))

(use-package diff-hl
  :ensure t
  :hook ((dired-mode . diff-hl-dired-mode))
  :config
  (global-diff-hl-mode)
  (diff-hl-margin-mode))

(use-package forge
  :ensure t
  :after magit)

(cl-defun auth-source-ghcli-search (&rest spec
                                          &key backend require
                                          type max host user port
                                          &allow-other-keys)
  "Given a property list SPEC, return search matches from the `:backend'.
See `auth-source-search' for details on SPEC."
  ;; just in case, check that the type is correct (null or same as the backend)
  (cl-assert (or (null type) (eq type (oref backend type)))
             t "Invalid GH CLI search: %s %s")

  (when-let* ((hostname (string-remove-prefix "api." host))
              ;; split ghub--ident again
              (ghub_ident (split-string (or user "") "\\^"))
              (username (car ghub_ident))
              (package (cadr ghub_ident))
              (cmd (format "gh auth token --hostname '%s'" hostname))
              (token (when (string= package "forge") (string-trim-right (shell-command-to-string cmd))))
              (retval (list
                       :host hostname
                       :user username
                       :secret token)))
    (auth-source-do-debug  "auth-source-ghcli: return %s as final result (plus hidden password)"
                           (seq-subseq retval 0 -2)) ;; remove password
    (list retval)))

(defvar auth-source-ghcli-backend
  (auth-source-backend
   :source "." ;; not used
   :type 'gh-cli
   :search-function #'auth-source-ghcli-search)
  "Auth-source backend for GH CLI.")

(defun auth-source-ghcli-backend-parse (entry)
  "Create a GH CLI auth-source backend from ENTRY."
  (when (eq entry 'gh-cli)
    (auth-source-backend-parse-parameters entry auth-source-ghcli-backend)))

(if (boundp 'auth-source-backend-parser-functions)
    (add-hook 'auth-source-backend-parser-functions #'auth-source-ghcli-backend-parse)
  (advice-add 'auth-source-backend-parse :before-until #'auth-source-ghcli-backend-parse))

(setq auth-sources '(gh-cli))

(use-package alc-git
  :ensure nil
  :load-path "lisp/"
  :bind ("C-x v w" . alc-git-permalink-to-kill-ring))

(use-package python
  :hook ((python-mode . alc-python-mode-hook)
         (python-ts-mode . alc-python-mode-hook))
  :custom
  (python-shell-dedicated 'project))

(with-eval-after-load 'apheleia
  (setf (alist-get 'python-ts-mode apheleia-mode-alist)
       '(ruff-isort ruff)))

(defun alc-python-mode-hook ()
  "Tweaks and config to run when starting `python-mode'"
  (require 'alc-python)
  (setq-local fill-column 88)

  ;; Files in site-packages/ etc. should be read only by default.
  ;; Also do not start eglot in these locations to cut down on the
  ;; number of server instances.
  ;;
  ;; However, make sure this only runs for buffer's associated with a
  ;; file. Previous versions of this code would set `(with-temp-buffer ...)`
  ;; buffers read-only which breaks functions like `python-shell-send-region'!
  (if-let ((file-name (buffer-file-name)))
    (if (alc-python-library-file-p file-name)
        (read-only-mode)
      (eglot-ensure))))

(use-package eglot
  :bind (("<f2>" . eglot-rename))
  :custom
  (eglot-autoshutdown t)
  (eglot-extend-to-xref t)
  :config
  (advice-add 'eglot--workspace-configuration-plist :around #'alc-eglot-fix-workspace-configuration-scope)

  (add-to-list 'display-buffer-alist
               '("\\*EGLOT workspace configuration\\*"
                 (display-buffer-below-selected)
                 (dedicated . t))))

(require 's)

(defun alc-eglot-fix-workspace-configuration-scope (orig-fun server &optional path)
  "Fix the scopeUri of a workspace/configuration request.

When eglot handles a workspace/configuration request with an
associated scopeUri it uses the `file-name-directory' function to
determine the directory from which to resolve the configuration
values.

This causes an issue for servers like esbonio and pyright which
set the scopeUri to a directory. For `file-name-directory' to
treat the path as a directory it must include a trailing slash, a
convention which these servers do not follow.

Therefore `file-name-directory' treats the path as a file and
removes the final component, causing eglot to resolve the
configuration relative to the wrong directory.

This function fixes the issue by advising the
`eglot--workspace-configuration-plist' function, ensuring that
paths referencing directories include the trailing slash."
  (if (and path
           (file-directory-p path)
           (not (s-ends-with? "/" path)))
      (funcall orig-fun server (concat path "/"))
    (funcall orig-fun server path)))

(use-package denote-sequence
  :ensure t
  :after denote
  :custom
  (denote-sequence-scheme 'numeric))

(use-package denote
  :ensure t
  :hook ((dired-mode . denote-dired-mode))
  :config

  ;; Add reStructuredText support to denote
  (add-to-list 'denote-file-types `(rst
                                    :extension ".rst"
                                    :date-key-regexp "^:date:"
                                    :date-value-function denote-date-iso-8601
                                    :date-value-reverse-function denote-extract-date-from-front-matter
                                    :front-matter ":title: %s\n:date: %s\n:tags: %s\n:identifier: %s\n:signature: %s\n\n"
                                    :title-key-regexp "^:title:"
                                    :title-value-function identity
                                    :title-value-reverse-function denote-trim-whitespace
                                    :signature-key-regexp ":signature:"
                                    :signature-value-function identity
                                    :signature-value-reverse-function denote-trim-whitespace
                                    :keywords-key-regexp "^:tags:"
                                    :keywords-value-function ,(lambda (ks) (string-join ks ", "))
                                    :keywords-value-reverse-function denote-extract-keywords-from-front-matter
                                    :identifier-key-regexp "^:identifier:"
                                    :identifier-value-function identity
                                    :identifier-value-reverse-function denote-trim-whitespace
                                    :link ":denote:link:`%2$s <%1$s>`"
                                    :link-in-context-regexp ,(concat ":denote:link:`.*?<\\(?1:" denote-id-regexp "\\)>`"))))

(use-package eat
  :ensure t
  :hook (eshell-load . eat-eshell-mode))

(set-face-attribute 'default nil :family "UbuntuMonoNerdFont" :height 120)
(set-face-attribute 'fixed-pitch nil :family "UbuntuMonoNerdFont" :height 120)
(set-face-attribute 'variable-pitch nil :family "UbuntuSansNerdFont" :weight 'light :height 120)

(use-package nerd-icons
  :ensure t)

(use-package doric-themes :ensure t)

(use-package alc-theme
  :load-path "lisp"
  :config
  (require-theme 'doric-themes t)

  (setq alc-theme-load-light-theme-function
        (lambda () (doric-themes-select 'doric-coral))
        alc-theme-load-dark-theme-function
        (lambda () (doric-themes-select 'doric-fire)))

  (add-to-list 'after-make-frame-functions 'alc-theme-sync-to-system-theme))

(add-hook 'prog-mode-hook (lambda () (display-line-numbers-mode t)))

(setq-default display-line-numbers-widen t
              display-line-numbers-width 4)

(setq pixel-scroll-precision-use-momentum nil
      pixel-scroll-precision-interpolate-page t
      pixel-scroll-precision-momentum-seconds 0.5)
(pixel-scroll-precision-mode t)

(use-package alc-tab-bar
  :demand t
  :hook (after-init . tab-bar-mode)
  :bind (:map tab-prefix-map
         ("p" . alc-tab-bar-project-other-tab-command))
  :config
  (setq tab-bar-close-button-show nil)
  (setq tab-bar-tab-hints t)
  (setq tab-bar-auto-width nil)
  (setq tab-bar-format '(tab-bar-format-tabs-groups
                         tab-bar-separator
                         tab-bar-format-align-right
                         tab-bar-format-global
                         tab-bar-format-menu-bar))

  (setq tab-bar-tab-group-format-function 'alc-tab-bar-tab-group-format-function)
  (setq tab-bar-tab-name-format-functions '(alc-tab-bar-tab-name-format-hints
                                            tab-bar-tab-name-format-close-button
                                            tab-bar-tab-name-format-face))

  ;; Disable the menu-bar, since it's accessible via the tab bar.
  (menu-bar-mode -1))

(use-package alc-modeline
  :after alc-theme
  :load-path "lisp"
  :config
  (setq-default mode-line-format
             '("%e"
               mode-line-front-space
               alc-modeline-window-dedicated
               alc-modeline-project-identification
               "  "
               alc-modeline-remote-indication
               alc-modeline-buffer-identification
               " "
               alc-modeline-buffer-position
               "      "
               mode-line-modes
               )))

(keymap-global-set "C-x C-b" 'ibuffer)

(use-package alc-jj
  :demand t
  :hook ((alc-jj-log-view-mode . combobulate-mode))
  :bind (:map alc-jj-log-view-mode-map
         ("n" . combobulate-navigate-next)
         ("p" . combobulate-navigate-previous)))

(use-package alc-treesitter
  :load-path "lisp"
  :if (treesit-available-p))

(add-to-list 'Info-directory-list
             (concat (getenv "HOME") "/.local/share/info/"))

(use-package rst
  :hook ((rst-mode . visual-wrap-prefix-mode))
  :bind (:map rst-mode-map
          ("C-c m" . alc-rst-mode-tmenu)))

(use-package esbonio
  :vc (:url "https://github.com/swyddfa/esbonio.el" :rev "main")
  :hook ((rst-mode . esbonio-eglot-ensure)))

(transient-define-prefix alc-rst-mode-tmenu ()
  "Major mode transient menu for `rst-mode'"
  ["Esbonio"
   ["Previews"
    ("p" "Preview File" esbonio-preview-file)
    ("s" "Toggle Sync Scroll" esbonio-sync-scroll-mode)]])

(setq completions-detailed t
      completions-format 'one-column
      completions-group t
      completions-header-format (propertize "%s candidates\n" 'face 'shadow)
      completions-max-height 15
      completion-show-help nil)

(setq completion-auto-help 'visible
      completion-auto-select 'second-tab
      completion-cycle-threshold 3)

(use-package consult
  :ensure t
  :custom
  (consult-preview-key "M-.")
  :bind (("C-x b" . consult-buffer)))

(use-package embark
  :ensure t
  :bind (("C-." . embark-act)
         ("M-." . embark-dwim)))

(use-package embark-consult
  :after (embark consult)
  :ensure t)

(use-package orderless
  :ensure t
  :custom
  (completion-styles '(orderless basic))
  (completion-category-defaults nil)
  (completion-category-overrides '((file (styles basic partial-completion)))))

(use-package vertico
  :ensure t
  :init
  (vertico-mode)
  :config

  (setq vertico-multiform-commands
        '((consult-line buffer)
          (consult-imenu buffer)))

  (setq vertico-multiform-categories
        '((t unobtrusive)))

  (vertico-multiform-mode t))

(setq completion-in-region-function #'consult-completion-in-region)

(use-package alc-eshell)

(use-package sly
  :ensure t
  :config
  (setq sly-lisp-implementations '((sbcl ("sbcl")))))

(provide 'init)

;;; init.el ends here

Put all the noise generated by the custom system into a separate file.

init.el
(setq custom-file (locate-user-emacs-file "custom.el"))
(when (file-exists-p custom-file)
 (load custom-file))

Setup package archives

init.el
(package-initialize)
(add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/"))
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))

And install some utility libraries

init.el
(dolist (pkg '(s))
  (unless (package-installed-p pkg)
    (unless package-archive-contents
      (package-refresh-contents))
    (package-install pkg)))

Work Around

I am currently using Aurora which encourages the use of homebrew to install cli programs. This means executables are normally placed in /home/linuxbrew/.linuxbrew/bin/ which is usually added to PATH for my user account. However, this does not happen when launching Emacs via krunner and so it fails to find any tools installed there.

To work around this, let’s manually add it to exec-path

init.el
(if (file-exists-p "/home/linuxbrew/.linuxbrew/bin")
    (add-to-list 'exec-path "/home/linuxbrew/.linuxbrew/bin"))

Now that I’ve started running the Emacs server via systemctl --user, $HOME/.local/bin does not appear to be on the PATH either. (No idea why, so let’s just add it here and move on.)

init.el
(if (file-exists-p "/home/alex/.local/bin")
    (add-to-list 'exec-path "/home/alex/.local/bin"))

However, this isn’t enough to let functions like shell-command find the command, we also need to update Emacs’ version of PATH

init.el
(setenv "PATH" (string-join exec-path ":"))

Basic Editing

init.el
(recentf-mode t)
(savehist-mode t)
(save-place-mode t)

Backup Files

By default Emacs will litter folders with backup files (filename.txt~), rather than disable them entirely just put them out of sight.

init.el
(setq backup-directory-alist `(("." . ,(expand-file-name "backups" user-emacs-directory))))

Repeat Mode

repeat-mode is awesome and while I should really look at trying some of the things shown in Karthink’s guide, simply turning the mode on gives you a lot.

init.el
(repeat-mode 1)

Whitespace

Visualise certain whitespace characters

face

Enable visualisations that use faces

empty (requires face)

Highlight empty lines at the start/end of the buffer.

trailing (requires face)

Highlight trailing whitespace

lines-char (requires face)

If a line is too long (according to whitespace-line-column), highlight the first character that crosses the threshold

tab-mark

Render tab characters in the buffer (like set list in vim). Uses whitespace-display-mappings to determine the character(s) to use.

init.el
(setq whitespace-style '(face empty trailing lines-char tab-mark))
(add-hook 'prog-mode-hook 'whitespace-mode)

Enable automatic whitespace cleanup on each save

init.el
(add-hook 'before-save-hook #'whitespace-cleanup)

Indent using spaces by default, and ensure that the TAB key is only used for indentation.

init.el
(setq tab-always-indent t)
(setq-default indent-tabs-mode nil)

Compilation

Basic settings for Emacs’ compilation framework

init.el
(setq compilation-always-kill t
      compilation-scroll-output t)

Enable coloured output in compilation buffers

init.el
(add-hook 'compilation-filter-hook 'ansi-color-compilation-filter)

Attempt at a sane display-buffer-alist rule for compilation windows

init.el
(add-to-list 'display-buffer-alist
             '("\\*compilation\\*"
               (display-buffer-reuse-window
                display-buffer-in-previous-window
                display-buffer-reuse-mode-window
                display-buffer-at-bottom)
               (window-height . 0.25)))

Make F5 call recompile

init.el
(keymap-global-set "<f5>" 'recompile)

The following adds a simple minor mode which, when enabled, will call recompile each time the buffer is saved

init.el
(define-minor-mode recompile-on-save-mode
  "When enabled, run `recompile' after the current buffer is saved"
  :lighter " ⭮"
  (if recompile-on-save-mode
      (add-hook 'after-save-hook 'recompile nil t)
    (remove-hook 'after-save-hook 'recompile t)))

Dired

Settings for dired

init.el
(use-package dired
 :hook ((dired-mode . dired-hide-details-mode)
        (dired-mode . hl-line-mode))
 :config
 (setq dired-dwim-target t
       dired-maybe-use-globstar t
       dired-isearch-filenames 'dwim
       ;; -A, all files - except '.' & '..'
       ;; -G, omit owning group
       ;; -h, human readable file sizes
       ;; -l, long listing, required for dired.
       dired-listing-switches "-AGhl --group-directories-first --time-style=long-iso"))

project.el

The built-in project management, project.el is good enough for my needs. However, so that Emacs works nicely with monorepo style repositories defining some additional root marker files will get project.el to consider a sub-directory of a repo a valid project.

init.el
(setq project-vc-extra-root-markers '("Cargo.toml"
                                      "package.json"
                                      "pyproject.toml"))

This however, introduces a few problems the most obvious of which is that project-find-file doesn’t respect .gitignore when run from within a subproject. The following advice ensures that the right vc backend is selected for subprojects

init.el
(defun alc-project-try-vc-subproject (orig-fun &rest args)
  "Advice for `project-try-vc'.

When using `project-vc-extra-root-markers' to teach project.el
about subprojects within a monorepo, `project-try-vc'
successfully finds the subject's root but fails to detect the
backend. But by calling `vc-responsible-backend' on the found
root, we can fill in the blanks.

As a result, commands like `project-find-file' now respect the
parent repo's .gitignore file even when being run from within a
subproject."
  (let* ((res (apply orig-fun args))
         (dir (nth 2 res))
         (backend (or (nth 1 res)
                      (ignore-errors (vc-responsible-backend dir)))))
    (if dir
        `(vc ,backend ,dir))))

(advice-add 'project-try-vc :around #'alc-project-try-vc-subproject)

Languages

And radian-software/apheleia for automatic formatting of buffers

init.el
(use-package apheleia
  :ensure t
  :config
  (apheleia-global-mode))

JavaScript

init.el
(use-package js
  :mode ("\\.js\\'" . js-mode)
  :custom
  (js-indent-level 2)
  :hook ((js-ts-mode . eglot-ensure)))

TypeScript

init.el
(use-package typescript-ts-mode
  :mode "\\.ts\\'"
  :hook ((typescript-ts-mode . eglot-ensure)))

YAML

init.el
(use-package yaml-mode
  :ensure t)

LLMs

init.el
(use-package gptel
  :ensure t
  :bind (("C-c RET" . gptel-send))
  :config
  (setq gptel-model 'llama3.2:latest)
  (setq gptel-backend (gptel-make-ollama "Ollama"
                       :host "localhost:11434"
                       :stream t
                       :models '(llama3.2:latest))))

Awdur Templates

Making use of awdur’s templates removes the need to ensure code like (provide '<feature>) finds its way into an explicit code block.

elisp-module
{%- extends "default" %}
{% block header %};;; {{ path.name }} --- TODO add description  -*- lexical-binding: t -*-

;;; Code:
{% endblock %}

{% block footer %}

(provide '{{ path.stem }})

;;; {{ path.name }} ends here
{% endblock %}
treesit-grammar
{%- extends "default" -%}
{% block header %}
/// <reference types="tree-sitter-cli/dsl" />
// @ts-check

export default grammar({
{% endblock %}

{% block footer %}
});
{% endblock %}