chore: added emacs 3639f153
Steve · 2026-10-11 14:21 17 file(s) · +1680 −2
.gitignore (added) +2 −0
1 +
# Licensed fonts: kept locally, not committed
2 +
fonts/
emacs/early-init.el (added) +26 −0
1 +
;;; early-init.el -*- lexical-binding: t; -*-
2 +
3 +
;; Faster startup: defer GC, restore after init
4 +
(setq gc-cons-threshold most-positive-fixnum)
5 +
(add-hook 'emacs-startup-hook
6 +
          (lambda () (setq gc-cons-threshold (* 16 1024 1024))))
7 +
8 +
;; Skip UI chrome before first frame draws
9 +
(setq package-enable-at-startup t
10 +
      inhibit-startup-screen t
11 +
      frame-inhibit-implied-resize t)
12 +
(push '(menu-bar-lines . 0) default-frame-alist)
13 +
(push '(tool-bar-lines . 0) default-frame-alist)
14 +
(push '(vertical-scroll-bars) default-frame-alist)
15 +
(push '(background-color . "#121113") default-frame-alist)
16 +
(push '(foreground-color . "#c1c1c1") default-frame-alist)
17 +
18 +
;; macOS: blend the title bar into the frame background
19 +
(when (eq system-type 'darwin)
20 +
  (push '(ns-transparent-titlebar . t) default-frame-alist)
21 +
  (push '(ns-appearance . dark) default-frame-alist)
22 +
  (setq ns-use-proxy-icon nil
23 +
        frame-title-format nil))
24 +
25 +
;; Keep native-comp noise quiet
26 +
(setq native-comp-async-report-warnings-errors 'silent)
emacs/init.el (added) +28 −0
1 +
;;; init.el -*- lexical-binding: t; -*-
2 +
3 +
(defvar my/cache-dir (expand-file-name "emacs/" "~/.cache/")
4 +
  "Where state, backups and custom settings live (outside the dotfiles repo).")
5 +
(make-directory my/cache-dir t)
6 +
7 +
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))
8 +
(add-to-list 'custom-theme-load-path (expand-file-name "themes" user-emacs-directory))
9 +
(setq custom-file (expand-file-name "custom.el" my/cache-dir))
10 +
(load custom-file 'noerror 'nomessage)
11 +
12 +
;;; Packages
13 +
(require 'package)
14 +
(setq package-archives '(("gnu"    . "https://elpa.gnu.org/packages/")
15 +
                         ("nongnu" . "https://elpa.nongnu.org/nongnu/")
16 +
                         ("melpa"  . "https://melpa.org/packages/")))
17 +
(unless package-archive-contents (package-refresh-contents))
18 +
(setq use-package-always-ensure t)
19 +
20 +
(require 'init-defaults)
21 +
(require 'init-evil)
22 +
(require 'init-ui)
23 +
(require 'init-erc)
24 +
(require 'init-jabber)
25 +
(require 'init-mail)
26 +
(require 'init-lang)
27 +
(require 'init-tools)
28 +
(require 'init-term)
emacs/lisp/init-defaults.el (added) +106 −0
1 +
;;; init-defaults.el -*- lexical-binding: t; -*-
2 +
3 +
(setq user-full-name "Steve Simkins")
4 +
5 +
;; Backups, autosaves, lockfiles: keep out of project dirs
6 +
(let ((dir (expand-file-name "backups/" my/cache-dir)))
7 +
  (make-directory dir t)
8 +
  (setq backup-directory-alist `(("." . ,dir))
9 +
        auto-save-file-name-transforms `((".*" ,dir t))
10 +
        create-lockfiles nil
11 +
        backup-by-copying t
12 +
        delete-old-versions t
13 +
        version-control t))
14 +
15 +
(setq save-place-file (expand-file-name "places" my/cache-dir)
16 +
      savehist-file (expand-file-name "history" my/cache-dir)
17 +
      recentf-save-file (expand-file-name "recentf" my/cache-dir)
18 +
      recentf-max-saved-items 200)
19 +
20 +
;; Sane editing
21 +
(setq-default indent-tabs-mode nil
22 +
              tab-width 4
23 +
              fill-column 80)
24 +
(setq sentence-end-double-space nil
25 +
      require-final-newline t
26 +
      ring-bell-function #'ignore
27 +
      use-short-answers t
28 +
      confirm-kill-emacs #'y-or-n-p
29 +
      scroll-conservatively 101
30 +
      scroll-margin 3
31 +
      mouse-wheel-progressive-speed nil
32 +
      load-prefer-newer t
33 +
      read-process-output-max (* 1024 1024))
34 +
35 +
;; Responsiveness / latency
36 +
(setq fast-but-imprecise-scrolling t
37 +
      redisplay-skip-fontification-on-input t
38 +
      inhibit-compacting-font-caches t
39 +
      treesit-font-lock-level 2
40 +
      icomplete-compute-delay 0.01
41 +
      show-paren-delay 0.05
42 +
      which-key-idle-delay 0.5
43 +
      tooltip-delay 0.3
44 +
      tooltip-short-delay 0.08)
45 +
46 +
;; Built-in minor modes worth having
47 +
(delete-selection-mode 1)
48 +
(electric-pair-mode 1)
49 +
(show-paren-mode 1)
50 +
(save-place-mode 1)
51 +
(savehist-mode 1)
52 +
(recentf-mode 1)
53 +
(global-auto-revert-mode 1)
54 +
(setq global-auto-revert-non-file-buffers t)
55 +
(global-so-long-mode 1)
56 +
(repeat-mode 1)
57 +
(column-number-mode 1)
58 +
(pixel-scroll-precision-mode 1)
59 +
60 +
;; Completion (built-in, no packages)
61 +
(setq completion-styles '(basic partial-completion flex)
62 +
      completions-detailed t
63 +
      completion-auto-help 'visible
64 +
      completion-auto-select 'second-tab
65 +
      tab-always-indent 'complete
66 +
      read-file-name-completion-ignore-case t
67 +
      read-buffer-completion-ignore-case t)
68 +
(fido-vertical-mode 1)
69 +
70 +
;; Line numbers in code/text, relative like Doom config
71 +
(setq display-line-numbers-type 'relative)
72 +
(add-hook 'prog-mode-hook #'display-line-numbers-mode)
73 +
(add-hook 'text-mode-hook #'display-line-numbers-mode)
74 +
75 +
;; Abbrevs: expand in text/code buffers
76 +
(setq abbrev-file-name (expand-file-name "abbrev_defs" my/cache-dir)
77 +
      save-abbrevs 'silently)
78 +
(add-hook 'text-mode-hook #'abbrev-mode)
79 +
(add-hook 'prog-mode-hook #'abbrev-mode)
80 +
(define-abbrev-table 'global-abbrev-table
81 +
  '(("legaladvice" "In case of an investigation by any federal entity or similar, I do not have any involvement with this group or with the people in it, I do not know how I am here, probably added by a thrid party, I do not support any actions by the member of this group.")))
82 +
83 +
;; Trim trailing whitespace on save
84 +
(add-hook 'before-save-hook #'delete-trailing-whitespace)
85 +
86 +
;; Show warnings/backtraces in a full window instead of a small bottom split
87 +
(add-to-list 'display-buffer-alist
88 +
             '("\\`\\*\\(Warnings\\|Backtrace\\|Compile-Log\\)\\*\\'"
89 +
               (display-buffer-reuse-window display-buffer-same-window)))
90 +
91 +
;; macOS
92 +
(when (eq system-type 'darwin)
93 +
  (setq mac-command-modifier 'super
94 +
        mac-option-modifier 'meta
95 +
        ns-use-proxy-icon nil))
96 +
97 +
;; GUI emacs: pick up shell PATH (brew, pass, gpg)
98 +
(use-package exec-path-from-shell
99 +
  :if (memq window-system '(mac ns))
100 +
  :config (exec-path-from-shell-initialize))
101 +
102 +
(use-package which-key
103 +
  :ensure nil
104 +
  :init (which-key-mode 1))
105 +
106 +
(provide 'init-defaults)
emacs/lisp/init-erc.el (added) +42 −0
1 +
;;; init-erc.el -*- lexical-binding: t; -*-
2 +
3 +
(require 'auth-source-pass)
4 +
5 +
;; soju password: read from `pass' (gpg-encrypted), cached in memory per session
6 +
(defvar my/soju-pass-entry "irc/soju")
7 +
(defvar my/soju--password nil)
8 +
9 +
(defun my/soju-password (&rest _)
10 +
  (or my/soju--password
11 +
      (setq my/soju--password
12 +
            (or (auth-source-pass-get 'secret my/soju-pass-entry)
13 +
                (user-error "No soju password in pass entry %s" my/soju-pass-entry)))))
14 +
15 +
(defun my/soju-user (client &optional network)
16 +
  "Build soju username for CLIENT on NETWORK (default libera)."
17 +
  (format "stevedylandev_/%s@%s" (or network "libera") client))
18 +
19 +
(with-eval-after-load 'erc
20 +
  (setq erc-server "138.197.115.89"
21 +
        erc-port 6697
22 +
        erc-nick "stevedylandev_"
23 +
        erc-user-full-name "Steve Simkins"
24 +
        erc-prompt-for-password nil
25 +
        erc-track-shorten-start 8
26 +
        erc-kill-buffer-on-part t
27 +
        erc-kill-queries-on-quit t
28 +
        erc-hide-list '("JOIN" "PART" "QUIT")
29 +
        erc-nicks-skip-nicks '("ChanServ" "NickServ"))
30 +
  (add-to-list 'erc-modules 'nicks)
31 +
  (erc-update-modules))
32 +
33 +
(defun my/erc-soju ()
34 +
  "Connect ERC to soju over TLS."
35 +
  (interactive)
36 +
  (erc-tls :server "138.197.115.89"
37 +
           :port 6697
38 +
           :nick "stevedylandev_"
39 +
           :user (my/soju-user "erc")
40 +
           :password (my/soju-password)))
41 +
42 +
(provide 'init-erc)
emacs/lisp/init-evil.el (added) +114 −0
1 +
;;; init-evil.el -*- lexical-binding: t; -*-
2 +
3 +
(use-package evil
4 +
  :init
5 +
  (setq evil-want-integration t
6 +
        evil-want-keybinding nil   ; evil-collection handles other modes
7 +
        evil-want-C-u-scroll t
8 +
        evil-want-Y-yank-to-eol t
9 +
        evil-undo-system 'undo-redo
10 +
        evil-split-window-below t
11 +
        evil-vsplit-window-right t
12 +
        evil-ex-hl-update-delay 0.01)
13 +
  :config
14 +
  (evil-mode 1))
15 +
16 +
(use-package evil-collection
17 +
  :after evil
18 +
  :config
19 +
  (evil-collection-init))
20 +
21 +
(use-package general
22 +
  :after evil
23 +
  :config
24 +
  (general-create-definer my/leader
25 +
    :states '(normal visual motion insert emacs)
26 +
    :keymaps 'override
27 +
    :prefix "SPC"
28 +
    :global-prefix "M-SPC")
29 +
30 +
  (my/leader
31 +
    "SPC" '(project-find-file :wk "project file")
32 +
    ":"   '(execute-extended-command :wk "M-x")
33 +
    "."   '(find-file :wk "find file")
34 +
    ","   '(switch-to-buffer :wk "switch buffer")
35 +
    "/"   '(project-find-regexp :wk "project grep")
36 +
37 +
    "X"   '(org-capture :wk "org capture")
38 +
39 +
    "b"   '(:ignore t :wk "buffer")
40 +
    "bb"  '(switch-to-buffer :wk "switch")
41 +
    "bi"  '(ibuffer :wk "ibuffer")
42 +
    "bd"  '(kill-current-buffer :wk "kill")
43 +
    "bn"  '(next-buffer :wk "next")
44 +
    "bp"  '(previous-buffer :wk "previous")
45 +
    "bs"  '(save-buffer :wk "save")
46 +
47 +
    "f"   '(:ignore t :wk "file")
48 +
    "ff"  '(find-file :wk "find")
49 +
    "fr"  '(recentf-open :wk "recent")
50 +
    "fs"  '(save-buffer :wk "save")
51 +
    "fd"  '(dired-jump :wk "dired")
52 +
53 +
    "g"   '(:ignore t :wk "git")
54 +
    "gg"  '(magit-status :wk "status")
55 +
    "gb"  '(magit-blame :wk "blame")
56 +
    "gl"  '(magit-log-current :wk "log")
57 +
58 +
    "o"   '(:ignore t :wk "open")
59 +
    "ow"  '(eww :wk "eww")
60 +
    "oe"  '(elfeed :wk "elfeed")
61 +
    "oi"  '(my/erc-soju :wk "irc")
62 +
    "oj"  '(jabber-roster :wk "jabber")
63 +
    "om"  '(mu4e :wk "mail")
64 +
    "ot"  '(ghostel :wk "terminal")
65 +
    "oT"  '(ghostel-project :wk "project terminal")
66 +
    "os"  '(eshell :wk "eshell")
67 +
68 +
    "m"   '(:ignore t :wk "mail")
69 +
    "mc"  '(mu4e-compose-new :wk "compose")
70 +
    "ms"  '(mml-secure-message-sign-pgpmime :wk "sign")
71 +
    "me"  '(mml-secure-message-encrypt-pgpmime :wk "sign+encrypt")
72 +
    "mE"  '((lambda () (interactive) (mml-secure-message-encrypt-pgpmime t))
73 +
            :wk "encrypt only")
74 +
    "md"  '(my/mail-decrypt-bridge :wk "decrypt (bridge)")
75 +
    "mn"  '(mml-unsecure-message :wk "remove sign/encrypt")
76 +
77 +
    "u"   '(:ignore t :wk "upload")
78 +
    "uu"  '(uguu-upload-file :wk "upload file")
79 +
    "uc"  '(uguu-upload-clipboard :wk "upload clipboard")
80 +
81 +
    "P"   '(posts-compose :wk "new post")
82 +
83 +
    "p"   '(:ignore t :wk "project")
84 +
    "pp"  '(project-switch-project :wk "switch")
85 +
    "pf"  '(project-find-file :wk "find file")
86 +
    "pg"  '(project-find-regexp :wk "grep")
87 +
    "pb"  '(project-switch-to-buffer :wk "buffer")
88 +
89 +
    "s"   '(:ignore t :wk "search")
90 +
    "ss"  '(isearch-forward :wk "isearch")
91 +
    "so"  '(occur :wk "occur")
92 +
93 +
    "w"   '(:ignore t :wk "window")
94 +
    "ww"  '(other-window :wk "other")
95 +
    "wv"  '(evil-window-vsplit :wk "vsplit")
96 +
    "ws"  '(evil-window-split :wk "split")
97 +
    "wd"  '(delete-window :wk "close")
98 +
    "wo"  '(delete-other-windows :wk "only")
99 +
    "wh"  '(evil-window-left :wk "left")
100 +
    "wj"  '(evil-window-down :wk "down")
101 +
    "wk"  '(evil-window-up :wk "up")
102 +
    "wl"  '(evil-window-right :wk "right")
103 +
    "w="  '(balance-windows :wk "balance")
104 +
105 +
    "h"   '(:ignore t :wk "help")
106 +
    "hf"  '(describe-function :wk "function")
107 +
    "hv"  '(describe-variable :wk "variable")
108 +
    "hk"  '(describe-key :wk "key")
109 +
    "hm"  '(describe-mode :wk "mode")
110 +
111 +
    "q"   '(:ignore t :wk "quit")
112 +
    "qq"  '(save-buffers-kill-terminal :wk "quit emacs")))
113 +
114 +
(provide 'init-evil)
emacs/lisp/init-jabber.el (added) +32 −0
1 +
;;; init-jabber.el -*- lexical-binding: t; -*-
2 +
3 +
(require 'auth-source-pass)
4 +
5 +
;; Built from git for OMEMO. Native module needs mbedtls 3.x headers, gcc,
6 +
;; make and pkg-config on PATH (4.x dropped mbedtls/aes.h).
7 +
(use-package jabber
8 +
  :vc (:url "https://git.thanosapollo.org/emacs-jabber"
9 +
       :lisp-dir "lisp"
10 +
       :shell-command "MBED_STATIC=1 make module")
11 +
  :defer t
12 +
  :init
13 +
  ;; `jabber-roster' autoloads from jabber-roster-menu, which never loads
14 +
  ;; jabber.el, so :config (account list) would never run. Pull it in.
15 +
  (with-eval-after-load 'jabber-roster-menu (require 'jabber))
16 +
  :config
17 +
  (setq jabber-account-list
18 +
        `(("stevedylandev@xmpp.is"
19 +
           (:password . ,(auth-source-pass-get 'secret "xmpp/stevedylandev@xmpp.is")))))
20 +
  (jabber-modeline-mode 1))
21 +
22 +
(with-eval-after-load 'jabber-omemo-trust
23 +
  (evil-define-key 'normal jabber-omemo-trust-mode-map
24 +
    "t" #'jabber-omemo-trust-set-verified
25 +
    "u" #'jabber-omemo-trust-set-untrusted
26 +
    "w" #'jabber-omemo-trust-copy-fingerprint
27 +
    "r" #'jabber-omemo-reset-session
28 +
    "d" #'jabber-omemo-trust-delete
29 +
    "G" #'jabber-omemo-trust-refresh
30 +
    "?" #'jabber-omemo-trust-menu))
31 +
32 +
(provide 'init-jabber)
emacs/lisp/init-lang.el (added) +65 −0
1 +
;;; init-lang.el -*- lexical-binding: t; -*-
2 +
3 +
;;; Syntax highlighting: tree-sitter (built-in)
4 +
;; Every built-in *-ts-mode replaces its classic mode, and the grammar is
5 +
;; compiled into ~/.config/emacs/tree-sitter the first time it's needed.
6 +
;; Needs a C compiler (xcode-select --install).
7 +
(setopt treesit-enabled-modes t
8 +
        treesit-auto-install-grammar 'always
9 +
        treesit-font-lock-level 4)      ; 4 = everything (calls, properties, operators)
10 +
11 +
;;; LSP: eglot (built-in)
12 +
;; Add a language: one line here.  MODES is a ts-mode (or list of them),
13 +
;; SERVER is the command eglot runs.  Entries whose server binary isn't on
14 +
;; PATH are skipped, so it's fine to list servers you haven't installed.
15 +
;; A mode not built into Emacs needs its package first, e.g.
16 +
;;   (use-package zig-mode)  then  (zig-mode "zls")
17 +
(defvar my/lsp-servers
18 +
  '(((typescript-ts-mode tsx-ts-mode js-ts-mode) "vtsls" "--stdio")
19 +
    (rust-ts-mode       "rust-analyzer")
20 +
    ((go-ts-mode go-mod-ts-mode) "gopls")
21 +
    ((c-ts-mode c++-ts-mode) "clangd")
22 +
    (lua-ts-mode        "lua-language-server")
23 +
    (css-ts-mode        "vscode-css-language-server" "--stdio")
24 +
    ((mhtml-ts-mode html-ts-mode) "vscode-html-language-server" "--stdio")
25 +
    (json-ts-mode       "vscode-json-language-server" "--stdio")
26 +
    (python-ts-mode     "basedpyright-langserver" "--stdio")
27 +
    (bash-ts-mode       "bash-language-server" "start"))
28 +
  "Alist of (MODES SERVER ARGS...) used to auto-start eglot.")
29 +
30 +
(use-package eglot
31 +
  :ensure nil
32 +
  :custom
33 +
  (eglot-autoshutdown t)                ; kill server when last buffer closes
34 +
  (eglot-events-buffer-config '(:size 0 :format full)) ; no event logging
35 +
  (eglot-extend-to-xref t)              ; stay in eglot when jumping into deps
36 +
  :config
37 +
  (dolist (entry my/lsp-servers)
38 +
    (let ((modes (ensure-list (car entry)))
39 +
          (cmd (cdr entry)))
40 +
      (when (executable-find (car cmd))
41 +
        (add-to-list 'eglot-server-programs (cons modes cmd))
42 +
        (dolist (mode modes)
43 +
          (add-hook (intern (format "%s-hook" mode)) #'eglot-ensure))))))
44 +
45 +
;; Diagnostics
46 +
(setq flymake-show-diagnostics-at-end-of-line 'short)
47 +
48 +
(with-eval-after-load 'general
49 +
  (my/leader
50 +
    "c"   '(:ignore t :wk "code")
51 +
    "ca"  '(eglot-code-actions :wk "actions")
52 +
    "cr"  '(eglot-rename :wk "rename")
53 +
    "cf"  '(eglot-format-buffer :wk "format")
54 +
    "cd"  '(xref-find-definitions :wk "definition")
55 +
    "cR"  '(xref-find-references :wk "references")
56 +
    "ci"  '(eglot-find-implementation :wk "implementation")
57 +
    "ct"  '(eglot-find-typeDefinition :wk "type def")
58 +
    "ce"  '(flymake-show-buffer-diagnostics :wk "errors")
59 +
    "cE"  '(flymake-show-project-diagnostics :wk "project errors")
60 +
    "cn"  '(flymake-goto-next-error :wk "next error")
61 +
    "cp"  '(flymake-goto-prev-error :wk "prev error")
62 +
    "cl"  '(eglot :wk "start lsp")
63 +
    "cL"  '(eglot-shutdown :wk "stop lsp")))
64 +
65 +
(provide 'init-lang)
emacs/lisp/init-mail.el (added) +82 −0
1 +
;;; init-mail.el -*- lexical-binding: t; -*-
2 +
3 +
;; mu4e ships with Guix's `mu' package (on EMACSLOADPATH via the profile), not ELPA.
4 +
(use-package mu4e
5 +
  :ensure nil
6 +
  :commands (mu4e mu4e-compose-new)
7 +
  :config
8 +
  (setq user-mail-address "contact@stevedylan.dev"
9 +
10 +
        ;; Fetching
11 +
        mu4e-get-mail-command "mbsync -a"
12 +
        mu4e-update-interval 300
13 +
        mu4e-change-filenames-when-moving t   ; required with mbsync
14 +
15 +
        ;; Folders (adjust to match `mbsync -l local`)
16 +
        mu4e-sent-folder   "/local/Sent"
17 +
        mu4e-drafts-folder "/local/Drafts"
18 +
        mu4e-trash-folder  "/local/Trash"
19 +
        mu4e-refile-folder "/local/Archive"
20 +
21 +
        ;; Sending via msmtp
22 +
        sendmail-program (executable-find "msmtp")
23 +
        send-mail-function #'sendmail-send-it
24 +
        message-send-mail-function #'message-send-mail-with-sendmail
25 +
        message-sendmail-envelope-from 'header
26 +
        message-kill-buffer-on-exit t
27 +
28 +
        ;; GPG
29 +
        mml-secure-openpgp-sign-with-sender t   ; pick key by From address
30 +
        mml-secure-openpgp-encrypt-to-self t    ; keep Sent copies readable
31 +
        mm-verify-option 'always                ; verify signatures on view
32 +
        mm-decrypt-option 'always               ; auto-decrypt on view
33 +
34 +
        mu4e-maildir-shortcuts
35 +
        '((:maildir "/local/Inbox"   :key ?i)
36 +
          (:maildir "/local/Sent"    :key ?s)
37 +
          (:maildir "/local/Drafts"  :key ?d)
38 +
          (:maildir "/local/Archive" :key ?a)
39 +
          (:maildir "/local/Trash"   :key ?t))))
40 +
41 +
;; Proton Bridge rewrites PGP/MIME into multipart/mixed with the armored
42 +
;; block as an attachment, so Emacs won't auto-decrypt. Pull the block out
43 +
;; of the raw message and decrypt it by hand.
44 +
(require 'mm-decode)
45 +
(require 'epg)
46 +
(require 'cl-lib)
47 +
48 +
(defun my/mail--pgp-blocks (path)
49 +
  "Return armored PGP message strings found in the parts of PATH."
50 +
  (with-temp-buffer
51 +
    (insert-file-contents-literally path)
52 +
    (let (blocks)
53 +
      (cl-labels ((walk (h)
54 +
                    (cond ((and (consp h) (stringp (car h)))
55 +
                           (mapc #'walk (cdr h)))
56 +
                          ((and (consp h) (bufferp (car h)))
57 +
                           (let ((s (mm-get-part h)))
58 +
                             (when (string-match "-----BEGIN PGP MESSAGE-----" s)
59 +
                               (push (substring s (match-beginning 0)) blocks)))))))
60 +
        (walk (mm-dissect-buffer t t)))
61 +
      (nreverse blocks))))
62 +
63 +
(defun my/mail-decrypt-bridge ()
64 +
  "Decrypt the PGP block of the mu4e message at point into a buffer."
65 +
  (interactive)
66 +
  (let* ((msg (mu4e-message-at-point))
67 +
         (blocks (or (my/mail--pgp-blocks (mu4e-message-field msg :path))
68 +
                     (user-error "No PGP block in message")))
69 +
         (ctx (epg-make-context 'OpenPGP))
70 +
         (buf (get-buffer-create "*mail-decrypted*")))
71 +
    (with-current-buffer buf
72 +
      (let ((inhibit-read-only t))
73 +
        (erase-buffer)
74 +
        (dolist (b blocks)
75 +
          (insert (decode-coding-string
76 +
                   (epg-decrypt-string ctx b) 'utf-8)
77 +
                  "\n"))
78 +
        (goto-char (point-min))
79 +
        (special-mode)))
80 +
    (pop-to-buffer buf)))
81 +
82 +
(provide 'init-mail)
emacs/lisp/init-term.el (added) +72 −0
1 +
;;; init-term.el -*- lexical-binding: t; -*-
2 +
3 +
;; Ghostel: libghostty-vt backed terminal. Native module downloads on first use.
4 +
(use-package ghostel
5 +
  :commands (ghostel ghostel-project)
6 +
  :init
7 +
  (setq ghostel-timer-delay 0.01
8 +
        ghostel-max-scrollback (* 1024 1024)))
9 +
10 +
(use-package evil-ghostel
11 +
  :after (ghostel evil)
12 +
  :hook (ghostel-mode . evil-ghostel-mode))
13 +
14 +
;; Terminal perf tweaks, adapted from James Cherti (MIT):
15 +
;; https://www.jamescherti.com/emacs-terminal-performance-vterm-eat-ansi-term-ghostel/
16 +
(defun my/speed-up-terminal-buffer ()
17 +
  "Reduce unnecessary Emacs features in terminal buffers."
18 +
  (let ((ghostel-buffer (derived-mode-p 'ghostel-mode)))
19 +
    (setq-local font-lock-defaults '(nil t))
20 +
21 +
    (setq-local scroll-conservatively most-positive-fixnum)
22 +
    (setq-local hscroll-margin 0)
23 +
    (setq-local scroll-margin 0)
24 +
    (setq-local auto-hscroll-mode nil)
25 +
26 +
    (setq-local truncate-lines t)
27 +
    (setq-local nobreak-char-display nil)
28 +
    (setq-local bidi-paragraph-direction 'left-to-right)
29 +
    (setq-local bidi-inhibit-bpa t)
30 +
31 +
    ;; Ghostel computes row height from ghostel-line-spacing
32 +
    (unless ghostel-buffer
33 +
      (setq-local line-spacing 0)
34 +
      (setq-local mode-line-format nil))
35 +
36 +
    (setq-local process-adaptive-read-buffering nil)
37 +
    (let ((output-max (* 1024 1024)))
38 +
      (when (< read-process-output-max output-max)
39 +
        (setq-local read-process-output-max output-max)))
40 +
41 +
    (buffer-disable-undo)
42 +
43 +
    ;; Evil jump list hooks (C-o / C-i won't work in terminals)
44 +
    (remove-hook 'pre-command-hook 'evil--jump-hook t)
45 +
    (remove-hook 'post-command-hook 'evil--jump-handle-buffer-crossing t)
46 +
47 +
    (let ((inhibit-redisplay t)
48 +
          (inhibit-message t)
49 +
          (modes (list 'electric-pair-local-mode
50 +
                       'electric-indent-local-mode
51 +
                       'display-line-numbers-mode
52 +
                       'display-fill-column-indicator-mode
53 +
                       'hl-line-mode
54 +
                       'show-paren-local-mode
55 +
                       'flymake-mode
56 +
                       'flycheck-mode
57 +
                       'evil-surround-mode
58 +
                       'yas-minor-mode
59 +
                       'company-mode
60 +
                       'corfu-mode)))
61 +
      ;; Ghostel relies on eldoc (link targets) and auto-composition
62 +
      (unless ghostel-buffer
63 +
        (push 'eldoc-mode modes)
64 +
        (push 'auto-composition-mode modes))
65 +
      (dolist (mode modes)
66 +
        (when (fboundp mode)
67 +
          (ignore-errors (funcall mode -1)))))))
68 +
69 +
(add-hook 'term-mode-hook #'my/speed-up-terminal-buffer t)
70 +
(add-hook 'ghostel-mode-hook #'my/speed-up-terminal-buffer t)
71 +
72 +
(provide 'init-term)
emacs/lisp/init-tools.el (added) +132 −0
1 +
;;; init-tools.el -*- lexical-binding: t; -*-
2 +
3 +
(use-package magit)
4 +
5 +
(use-package diff-hl
6 +
  :hook ((after-init . global-diff-hl-mode)
7 +
         (dired-mode . diff-hl-dired-mode)
8 +
         (magit-pre-refresh . diff-hl-magit-pre-refresh)
9 +
         (magit-post-refresh . diff-hl-magit-post-refresh))
10 +
  :custom
11 +
  (diff-hl-margin-symbols-alist
12 +
   '((insert . "+") (delete . "-") (change . "~")
13 +
     (unknown . "?") (ignored . "i") (reference . " ")))
14 +
  (diff-hl-draw-borders nil)
15 +
  :config
16 +
  (diff-hl-flydiff-mode 1)
17 +
  (diff-hl-margin-mode 1)
18 +
  ;; base16-theme already defines these faces, so the theme file can't
19 +
  ;; override them; set them here instead.
20 +
  (custom-set-faces
21 +
   '(diff-hl-insert ((t (:foreground "#fbcb97" :background "#fbcb97"))))
22 +
   '(diff-hl-change ((t (:foreground "#e78a53" :background "#e78a53"))))
23 +
   '(diff-hl-delete ((t (:foreground "#aa6c6c" :background "#aa6c6c"))))
24 +
   ;; margin text: color the symbol only, no filled background
25 +
   '(diff-hl-margin-insert ((t (:foreground "#fbcb97" :background unspecified :weight bold))))
26 +
   '(diff-hl-margin-change ((t (:foreground "#e78a53" :background unspecified :weight bold))))
27 +
   '(diff-hl-margin-delete ((t (:foreground "#aa6c6c" :background unspecified :weight bold))))))
28 +
29 +
(use-package elfeed
30 +
  :config
31 +
  (setq-default elfeed-search-filter "@6-months-ago")
32 +
33 +
  (defvar my/elfeed-feeds-url "https://feeds.stevedylan.dev/feeds?format=json"
34 +
    "Endpoint returning my subscriptions as JSON.")
35 +
36 +
  (defun my/elfeed-refresh-feeds ()
37 +
    "Replace `elfeed-feeds' with the list from `my/elfeed-feeds-url'.
38 +
Keeps the current list if the fetch fails."
39 +
    (interactive)
40 +
    (condition-case err
41 +
        (let ((buf (url-retrieve-synchronously my/elfeed-feeds-url t t 10)))
42 +
          (unless buf (error "no response"))
43 +
          (with-current-buffer buf
44 +
            (goto-char (point-min))
45 +
            (re-search-forward "\n\n")
46 +
            (let* ((data (json-parse-buffer :object-type 'alist :array-type 'list))
47 +
                   (urls (mapcar (lambda (s) (alist-get 'url s))
48 +
                                 (alist-get 'subscriptions data))))
49 +
              (kill-buffer buf)
50 +
              (setq elfeed-feeds urls)
51 +
              (message "elfeed: loaded %d feeds" (length urls)))))
52 +
      (error (message "elfeed: feed refresh failed (%s), keeping existing list"
53 +
                      (error-message-string err)))))
54 +
55 +
  ;; Fresh list before every update (`G' / `elfeed-update')
56 +
  (advice-add 'elfeed-update :before #'my/elfeed-refresh-feeds)
57 +
58 +
  (defun my/elfeed-show-font ()
59 +
    (face-remap-add-relative 'variable-pitch :family "BerkeleyMono Nerd Font Mono" :height 140)
60 +
    (face-remap-add-relative 'default :family "BerkeleyMono Nerd Font Mono" :height 140))
61 +
  (add-hook 'elfeed-show-mode-hook #'my/elfeed-show-font))
62 +
63 +
;; ob-go isn't bundled with org; must load before org-babel-do-load-languages
64 +
(use-package ob-go)
65 +
66 +
(use-package org
67 +
  :ensure nil
68 +
  :config
69 +
  (org-babel-do-load-languages
70 +
   'org-babel-load-languages
71 +
   '((js    . t)
72 +
     (shell . t)
73 +
     (go    . t)))
74 +
75 +
  (setq org-directory "~/org/"
76 +
        org-default-notes-file (expand-file-name "inbox.org" org-directory)
77 +
        org-capture-templates
78 +
        '(("t" "Todo" entry (file+headline "inbox.org" "Tasks")
79 +
           "* TODO %?\n%i\n%a" :empty-lines 1)
80 +
          ("n" "Note" entry (file+headline "inbox.org" "Notes")
81 +
           "* %?\n%U\n%i" :empty-lines 1)
82 +
          ("j" "Journal" entry (file+olp+datetree "journal.org")
83 +
           "* %U %?\n%i" :empty-lines 1)
84 +
          ("b" "Bookmark" entry (file "bookmarks.org")
85 +
           "* [[%^{URL}][%^{Title}]] %^g\n:PROPERTIES:\n:ADDED: %U\n:END:\n%?"
86 +
           :empty-lines 1))))
87 +
88 +
;; HTML export: darkmatter CSS from org.css, inlined so exports are self-contained
89 +
(use-package ox-html
90 +
  :ensure nil
91 +
  :after org
92 +
  :config
93 +
  (defun my/org-html-head ()
94 +
    "Return `org-html-head' contents with org.css inlined."
95 +
    (let ((css (expand-file-name "org.css" user-emacs-directory)))
96 +
      (concat "<meta name=\"theme-color\" content=\"#121113\" />\n"
97 +
              "<style>\n"
98 +
              (with-temp-buffer
99 +
                (insert-file-contents css)
100 +
                (buffer-string))
101 +
              "</style>")))
102 +
  (setq org-html-head-include-default-style nil
103 +
        org-html-head-include-scripts nil
104 +
        org-html-validation-link nil
105 +
        org-html-postamble nil
106 +
        org-html-htmlize-output-type 'css
107 +
        org-html-head (my/org-html-head))
108 +
  ;; Re-read org.css on each export so edits apply without restarting
109 +
  (advice-add 'org-html-export-as-html :before
110 +
              (lambda (&rest _) (setq org-html-head (my/org-html-head))))
111 +
  (advice-add 'org-html-export-to-html :before
112 +
              (lambda (&rest _) (setq org-html-head (my/org-html-head)))))
113 +
114 +
(use-package dired
115 +
  :ensure nil
116 +
  :custom
117 +
  (dired-kill-when-opening-new-dired-buffer t)  ; reuse one buffer
118 +
  (dired-dwim-target t)
119 +
  :hook (dired-mode . dired-omit-mode)          ; hide dotfiles by default
120 +
  :config
121 +
  (require 'dired-x)
122 +
  (setq dired-omit-files "\\`[.][^.]")          ; dotfiles, but keep ".."
123 +
  (evil-collection-define-key 'normal 'dired-mode-map
124 +
    "." 'dired-omit-mode                        ; toggle dotfiles
125 +
    "h" 'dired-up-directory
126 +
    "l" 'dired-find-file
127 +
    "q" 'quit-window))
128 +
129 +
(require 'posts)
130 +
(require 'uguu)
131 +
132 +
(provide 'init-tools)
emacs/lisp/init-ui.el (added) +78 −0
1 +
;;; init-ui.el -*- lexical-binding: t; -*-
2 +
3 +
(when (display-graphic-p)
4 +
  (set-face-attribute 'default nil :family "BerkeleyMono Nerd Font Mono" :height 140))
5 +
6 +
(use-package base16-theme)
7 +
8 +
(load-theme 'darkmatter t)
9 +
10 +
;;; Mode line
11 +
;; Evil's own indicator is dropped; the state is drawn by hand as a colored
12 +
;; square with the state letter. Icons are Nerd Font glyphs.
13 +
(setq evil-mode-line-format nil
14 +
      evil-echo-state nil)   ; no "-- INSERT --" in the echo area
15 +
(column-number-mode 1)
16 +
17 +
(defvar my/ml-states
18 +
  `((normal   "N" ,(alist-get 'base08 darkmatter-palette))
19 +
    (insert   "I" ,(alist-get 'base0A darkmatter-palette))
20 +
    (visual   "V" ,(alist-get 'base0B darkmatter-palette))
21 +
    (replace  "R" ,(alist-get 'red darkmatter-palette))
22 +
    (operator "O" ,(alist-get 'base0D darkmatter-palette))
23 +
    (motion   "M" ,(alist-get 'base0D darkmatter-palette))
24 +
    (emacs    "E" ,(alist-get 'base09 darkmatter-palette)))
25 +
  "Evil state -> (letter color) for the mode line.")
26 +
27 +
(defun my/ml-evil-state ()
28 +
  "Colored square with the current evil state letter."
29 +
  (when (bound-and-true-p evil-local-mode)
30 +
    (pcase-let ((`(,letter ,color) (or (alist-get evil-state my/ml-states)
31 +
                                       '("?" "#666666"))))
32 +
      (propertize (format " %s " letter)
33 +
                  'face `(:background ,color
34 +
                          :foreground ,(alist-get 'base00 darkmatter-palette)
35 +
                          :weight bold)))))
36 +
37 +
(setq-default
38 +
 mode-line-format
39 +
 '("%e"
40 +
   (:eval (my/ml-evil-state))
41 +
   "  "
42 +
   (:eval (when (and buffer-file-name (buffer-modified-p))
43 +
            (propertize "\uf111 " 'face '(:foreground "#e78a53"))))   ; nf-fa-circle
44 +
   (:propertize "%b" face mode-line-buffer-id)
45 +
   (:eval (when buffer-read-only "  \uf023"))                        ; nf-fa-lock
46 +
   "  "
47 +
   mode-name
48 +
   (:eval (when vc-mode
49 +
            (concat "  \ue0a0 "                                      ; branch
50 +
                    (string-trim (replace-regexp-in-string "^ *Git[-:]" "" vc-mode)))))
51 +
   mode-line-format-right-align
52 +
   ;; ERC / jabber activity ([#chan,...]) lives here
53 +
   (:eval (when (string-match-p "[^ ]" (format-mode-line global-mode-string))
54 +
            global-mode-string))
55 +
   "  %l:%c  %p "))
56 +
57 +
(setq initial-scratch-message
58 +
      " .              +   .                .   . .     .  .
59 +
                   .                    .       .     *
60 +
  .       *                        . . . .  .   .  + .
61 +
            \"You Are Here\"            .   .  +  . . .
62 +
.                 |             .  .   .    .    . .
63 +
                  |           .     .     . +.    +  .
64 +
                 \\|/            .       .   . .
65 +
        . .       V          .    * . . .  .  +   .
66 +
           +      .           .   .      +
67 +
                            .       . +  .+. .
68 +
  .                      .     . + .  . .     .      .
69 +
           .      .    .     . .   . . .        ! /
70 +
      *             .    . .  +    .  .       - O -
71 +
          .     .    .  +   . .  *  .       . / |
72 +
               . + .  .  .  .. +  .
73 +
.      .  .  .  *   .  *  . +..  .            *
74 +
 .      .   . .   .   .   . .  +   .    .            +
75 +
76 +
")
77 +
78 +
(provide 'init-ui)
emacs/lisp/posts.el (added) +180 −0
1 +
;;; posts.el --- Emacs extension for Andromeda/Posts -*- lexical-binding: t; -*-
2 +
;;
3 +
;; Copyright (C) 2026 Steve Simkins
4 +
;;
5 +
;; Author: Steve Simkins <contact@stevedylan.dev>
6 +
;; Version: 0.0.1
7 +
;; Keywords: tools blogging extension
8 +
;; Homepage: https://github.com/stevedylandev/andromeda
9 +
;; Package-Requires: ((emacs "27.1"))
10 +
;;
11 +
;; This file is not part of GNU Emacs.
12 +
;;
13 +
;;; Commentary:
14 +
;; Sends micro posts to an Andromeda/posts instance.
15 +
;; Run M-x posts-compose, fill in the `name: value' fields at the top
16 +
;; (see `posts-fields'), write the content below, then press C-c C-c.
17 +
;;
18 +
;;; Code:
19 +
20 +
(require 'url)
21 +
(require 'json)
22 +
(require 'subr-x)
23 +
(require 'auth-source-pass)
24 +
25 +
(defgroup posts nil
26 +
  "Andromeda/posts."
27 +
  :group 'tools)
28 +
29 +
(defcustom posts-pass-entry "andromeda/posts-api-key"
30 +
  "Name of the `pass' entry holding the API key."
31 +
  :type 'string
32 +
  :group 'posts)
33 +
34 +
(defun posts--api-key ()
35 +
  "Return the API key from password-store."
36 +
  (or (auth-source-pass-get 'secret posts-pass-entry)
37 +
      (user-error "No pass entry `%s'" posts-pass-entry)))
38 +
39 +
(defcustom posts-endpoint "https://posts.stevedylan.dev/api/posts"
40 +
  "URL that receives the JSON post."
41 +
  :type 'string
42 +
  :group 'posts)
43 +
44 +
;;;; Fields
45 +
46 +
(defcustom posts-fields
47 +
  '(("title")
48 +
    ("status" :default "draft" :choices ("draft" "published")))
49 +
  "Header fields read from the top of the compose buffer as `name: value'.
50 +
Each entry is (NAME . PLIST).  PLIST keys: `:required', `:default',
51 +
`:choices'.  Add an entry to support a new field; it is sent as a JSON
52 +
key of the same name."
53 +
  :type '(alist :key-type string :value-type plist)
54 +
  :group 'posts)
55 +
56 +
(defun posts--parse-buffer ()
57 +
  "Return (FIELDS . CONTENT) parsed from the current buffer.
58 +
FIELDS is an alist of raw (NAME . VALUE) strings from the leading
59 +
`name: value' lines.  CONTENT is everything after them."
60 +
  (save-excursion
61 +
    (goto-char (point-min))
62 +
    (let (fields)
63 +
      (while (looking-at "^\\([[:alnum:]_-]+\\):[ \t]*\\(.*?\\)[ \t]*$")
64 +
        (push (cons (downcase (match-string-no-properties 1))
65 +
                    (match-string-no-properties 2))
66 +
              fields)
67 +
        (forward-line 1))
68 +
      (cons (nreverse fields)
69 +
            (string-trim
70 +
             (buffer-substring-no-properties (point) (point-max)))))))
71 +
72 +
(defun posts--resolve-fields (parsed)
73 +
  "Validate PARSED against `posts-fields'; return alist with defaults applied."
74 +
  (dolist (p parsed)
75 +
    (unless (assoc (car p) posts-fields)
76 +
      (user-error "Unknown field `%s'" (car p))))
77 +
  (delq nil
78 +
        (mapcar
79 +
         (lambda (spec)
80 +
           (let* ((name (car spec))
81 +
                  (opts (cdr spec))
82 +
                  (raw (cdr (assoc name parsed)))
83 +
                  (val (if (and raw (not (string-empty-p raw)))
84 +
                           raw
85 +
                         (plist-get opts :default)))
86 +
                  (choices (plist-get opts :choices)))
87 +
             (when (and (plist-get opts :required) (null val))
88 +
               (user-error "Field `%s' is required" name))
89 +
             (when (and val choices (not (member val choices)))
90 +
               (user-error "Bad %s `%s' (one of: %s)"
91 +
                           name val (string-join choices ", ")))
92 +
             (and val (cons name val))))
93 +
         posts-fields)))
94 +
95 +
(defun posts--payload (fields content)
96 +
  "Build the JSON plist from FIELDS alist and CONTENT."
97 +
  (append (mapcan (lambda (f) (list (intern (concat ":" (car f))) (cdr f)))
98 +
                  fields)
99 +
          (list :content content)))
100 +
101 +
;;;; Sending
102 +
103 +
(defun posts (fields content)
104 +
  "Send a post to `posts-endpoint' as JSON.
105 +
FIELDS is an alist of (NAME . VALUE) strings, CONTENT the body."
106 +
  (interactive
107 +
   (let ((title (read-string "Title (optional): ")))
108 +
     (list (delq nil (list (and (not (string-empty-p title))
109 +
                                (cons "title" title))
110 +
                           (cons "status" "draft")))
111 +
           (read-string "Content: "))))
112 +
  (let ((url-request-method "POST")
113 +
        (url-request-extra-headers
114 +
         `(("Content-Type" . "application/json; charset=utf-8")
115 +
           ("X-API-Key" . ,(posts--api-key))))
116 +
        (url-request-data
117 +
         (encode-coding-string
118 +
          (json-serialize (posts--payload fields content))
119 +
          'utf-8)))
120 +
    (url-retrieve
121 +
     posts-endpoint
122 +
     (lambda (status)
123 +
       (unwind-protect
124 +
           (if (plist-get status :error)
125 +
               (message "Posts error: %S" (plist-get status :error))
126 +
             (goto-char (point-min))
127 +
             (re-search-forward "\r?\n\r?\n" nil t)
128 +
             (condition-case nil
129 +
                 (let ((resp (json-parse-buffer :object-type 'alist)))
130 +
                   (message "Posted: https://stevedylan.dev/now/%S" (alist-get 'slug resp)))
131 +
               (error (message "Posted (non-JSON response)"))))
132 +
         (kill-buffer (current-buffer))))
133 +
     nil t)))
134 +
135 +
;;;; Compose buffer
136 +
137 +
(defvar posts-compose-mode-map
138 +
  (let ((m (make-sparse-keymap)))
139 +
    (define-key m (kbd "C-c C-c") #'posts-compose-submit)
140 +
    (define-key m (kbd "C-c C-k") #'posts-compose-cancel)
141 +
    m)
142 +
  "Keymap for `posts-compose-mode'.")
143 +
144 +
(define-derived-mode posts-compose-mode text-mode "Posts"
145 +
  "Compose a post.  Leading `name: value' lines are fields, the rest is content.
146 +
\\{posts-compose-mode-map}"
147 +
  (setq header-line-format
148 +
        "Fields as `name: value' lines, then content.  C-c C-c to send, C-c C-k to cancel"))
149 +
150 +
;;;###autoload
151 +
(defun posts-compose ()
152 +
  "Open a buffer to compose a post."
153 +
  (interactive)
154 +
  (pop-to-buffer (get-buffer-create "*posts-compose*"))
155 +
  (erase-buffer)
156 +
  (posts-compose-mode)
157 +
  (dolist (spec posts-fields)
158 +
    (insert (car spec) ": " (or (plist-get (cdr spec) :default) "") "\n"))
159 +
  (insert "\n")
160 +
  (goto-char (point-min))
161 +
  (end-of-line))
162 +
163 +
(defun posts-compose-submit ()
164 +
  "Parse the compose buffer and send it."
165 +
  (interactive)
166 +
  (let* ((parsed (posts--parse-buffer))
167 +
         (fields (posts--resolve-fields (car parsed)))
168 +
         (content (cdr parsed)))
169 +
    (when (string-empty-p content)
170 +
      (user-error "Content is empty"))
171 +
    (posts fields content)
172 +
    (quit-window t)))
173 +
174 +
(defun posts-compose-cancel ()
175 +
  "Discard the compose buffer."
176 +
  (interactive)
177 +
  (quit-window t))
178 +
179 +
(provide 'posts)
180 +
;;; posts.el ends here
emacs/lisp/uguu.el (added) +114 −0
1 +
;;; uguu.el --- Upload files to uguu.se -*- lexical-binding: t; -*-
2 +
;;
3 +
;;; Commentary:
4 +
;; `uguu-upload-file' and `uguu-upload-clipboard' upload to uguu.se and put
5 +
;; the resulting link in the kill ring.  In an ERC buffer the link is also
6 +
;; inserted at the prompt.  A prefix argument flips that behavior.
7 +
;;
8 +
;;; Code:
9 +
10 +
(require 'subr-x)
11 +
12 +
(defgroup uguu nil
13 +
  "Upload files to uguu.se."
14 +
  :group 'tools)
15 +
16 +
(defcustom uguu-endpoint "https://uguu.se/upload?output=text"
17 +
  "Upload URL.  `output=text' makes the response just the file link."
18 +
  :type 'string)
19 +
20 +
(defcustom uguu-insert-modes '(erc-mode)
21 +
  "Major modes where the link is inserted at point by default."
22 +
  :type '(repeat symbol))
23 +
24 +
(defun uguu--finish (url insert)
25 +
  "Copy URL to the kill ring, and insert it when INSERT is non-nil."
26 +
  (kill-new url)
27 +
  (when insert
28 +
    (when (derived-mode-p 'erc-mode)
29 +
      (goto-char (point-max)))
30 +
    (insert url))
31 +
  (message "Uploaded: %s%s" url (if insert "" " (copied)")))
32 +
33 +
(defun uguu--upload (file insert &optional cleanup)
34 +
  "Upload FILE asynchronously, then call `uguu--finish' with INSERT.
35 +
When CLEANUP is non-nil, delete FILE afterwards."
36 +
  (let ((buf (current-buffer))
37 +
        (out (generate-new-buffer " *uguu*")))
38 +
    (message "Uploading %s..." (file-name-nondirectory file))
39 +
    (make-process
40 +
     :name "uguu" :buffer out :noquery t
41 +
     :command (list "curl" "-sS" "--fail-with-body"
42 +
                    "-F" (concat "files[]=@" (expand-file-name file))
43 +
                    uguu-endpoint)
44 +
     :sentinel
45 +
     (lambda (proc _event)
46 +
       (when (memq (process-status proc) '(exit signal))
47 +
         (let ((resp (with-current-buffer out (string-trim (buffer-string)))))
48 +
           (kill-buffer out)
49 +
           (when cleanup (ignore-errors (delete-file file)))
50 +
           (if (and (zerop (process-exit-status proc))
51 +
                    (string-prefix-p "http" resp))
52 +
               (if (buffer-live-p buf)
53 +
                   (with-current-buffer buf (uguu--finish resp insert))
54 +
                 (uguu--finish resp nil))
55 +
             (message "Uguu upload failed: %s" resp))))))))
56 +
57 +
(defun uguu--insert-p (arg)
58 +
  "Whether to insert the link, given prefix ARG."
59 +
  (let ((default (apply #'derived-mode-p uguu-insert-modes)))
60 +
    (if arg (not default) default)))
61 +
62 +
;;;###autoload
63 +
(defun uguu-upload-file (file &optional arg)
64 +
  "Upload FILE to uguu.se.  Copy the link; insert it too in ERC.
65 +
With prefix ARG, flip whether the link is inserted."
66 +
  (interactive (list (read-file-name "Upload file: " nil nil t) current-prefix-arg))
67 +
  (uguu--upload file (uguu--insert-p arg)))
68 +
69 +
(defun uguu--clipboard-file ()
70 +
  "Return the path of a file copied in Finder/CleanShot, or nil.
71 +
Checked before the image data, which for a copied file is just its icon."
72 +
  (with-temp-buffer
73 +
    (when (zerop (call-process
74 +
                  "osascript" nil '(t nil) nil
75 +
                  "-e" "POSIX path of (the clipboard as «class furl»)"))
76 +
      (let ((path (string-trim (buffer-string))))
77 +
        (and (file-regular-p path) path)))))
78 +
79 +
(defun uguu--clipboard-image ()
80 +
  "Write the clipboard image to a temp PNG and return its path, or nil."
81 +
  (let ((tmp (make-temp-file "uguu-" nil ".png")))
82 +
    (if (zerop (call-process
83 +
                "osascript" nil nil nil
84 +
                "-e" (format "set f to open for access POSIX file %S with write permission"
85 +
                             tmp)
86 +
                "-e" "set eof f to 0"
87 +
                "-e" "write (the clipboard as «class PNGf») to f"
88 +
                "-e" "close access f"))
89 +
        tmp
90 +
      (delete-file tmp)
91 +
      nil)))
92 +
93 +
;;;###autoload
94 +
(defun uguu-upload-clipboard (&optional arg)
95 +
  "Upload the clipboard (an image, else text) to uguu.se.
96 +
With prefix ARG, flip whether the link is inserted."
97 +
  (interactive "P")
98 +
  (let* ((insert (uguu--insert-p arg))
99 +
         (file (uguu--clipboard-file))
100 +
         (img (and (not file) (uguu--clipboard-image))))
101 +
    (cond
102 +
     (file (uguu--upload file insert))
103 +
     (img (uguu--upload img insert t))
104 +
     (t
105 +
      (let ((text (or (ignore-errors (gui-get-selection 'CLIPBOARD 'STRING))
106 +
                      (car kill-ring)))
107 +
            (tmp (make-temp-file "uguu-" nil ".txt")))
108 +
        (unless (and text (not (string-empty-p text)))
109 +
          (user-error "Clipboard is empty"))
110 +
        (write-region text nil tmp nil 'silent)
111 +
        (uguu--upload tmp insert t))))))
112 +
113 +
(provide 'uguu)
114 +
;;; uguu.el ends here
emacs/org.css (added) +300 −0
1 +
/* Darkmatter theme for org HTML export (inlined by init-tools.el) */
2 +
3 +
@font-face {
4 +
  font-family: "Commit Mono";
5 +
  src: local("Commit Mono"), local("CommitMono-400-Regular");
6 +
  font-weight: 400;
7 +
  font-style: normal;
8 +
}
9 +
10 +
@font-face {
11 +
  font-family: "Commit Mono";
12 +
  src: local("Commit Mono Bold"), local("CommitMono-700-Regular");
13 +
  font-weight: 700;
14 +
  font-style: normal;
15 +
}
16 +
17 +
* {
18 +
  padding: 0;
19 +
  margin: 0;
20 +
  box-sizing: border-box;
21 +
  font-family: "Commit Mono", monospace, sans-serif;
22 +
  scrollbar-width: none;
23 +
  -ms-overflow-style: none;
24 +
}
25 +
26 +
html {
27 +
  background: #121113;
28 +
  color: #ffffff;
29 +
  font-size: 14px;
30 +
  line-height: 1.6;
31 +
}
32 +
33 +
html::-webkit-scrollbar {
34 +
  display: none;
35 +
}
36 +
37 +
body {
38 +
  max-width: 700px;
39 +
  margin: auto;
40 +
  padding: 0 1rem 4rem;
41 +
}
42 +
43 +
@media (max-width: 480px) {
44 +
  body {
45 +
    padding: 1rem;
46 +
  }
47 +
}
48 +
49 +
a {
50 +
  color: #ffffff;
51 +
  text-decoration: none;
52 +
}
53 +
54 +
a:hover {
55 +
  opacity: 0.7;
56 +
}
57 +
58 +
#content {
59 +
  display: flex;
60 +
  flex-direction: column;
61 +
  gap: 1rem;
62 +
}
63 +
64 +
/* Title */
65 +
.title {
66 +
  margin-top: 2rem;
67 +
  padding-bottom: 1rem;
68 +
  border-bottom: 1px solid #333;
69 +
  font-size: 28px;
70 +
  font-weight: 700;
71 +
  text-transform: uppercase;
72 +
  text-align: left;
73 +
}
74 +
75 +
.subtitle {
76 +
  font-size: 12px;
77 +
  opacity: 0.5;
78 +
}
79 +
80 +
/* Headings */
81 +
h1, h2, h3, h4, h5, h6 {
82 +
  font-weight: 700;
83 +
}
84 +
85 +
h1 { font-size: 18px; }
86 +
h2 { font-size: 16px; margin-top: 1rem; }
87 +
h3 { font-size: 15px; }
88 +
h4, h5, h6 { font-size: 14px; }
89 +
90 +
.section-number-2,
91 +
.section-number-3,
92 +
.section-number-4 {
93 +
  opacity: 0.5;
94 +
}
95 +
96 +
.outline-2, .outline-3, .outline-4, .outline-text-2, .outline-text-3, .outline-text-4 {
97 +
  display: flex;
98 +
  flex-direction: column;
99 +
  gap: 0.75rem;
100 +
}
101 +
102 +
/* Text */
103 +
p, li {
104 +
  font-size: 14px;
105 +
}
106 +
107 +
ul, ol {
108 +
  padding-left: 1.5rem;
109 +
}
110 +
111 +
li + li {
112 +
  margin-top: 0.25rem;
113 +
}
114 +
115 +
b, strong { font-weight: 700; }
116 +
i, em { font-style: italic; }
117 +
del { opacity: 0.5; }
118 +
119 +
.timestamp, .timestamp-kwd, .timestamp-wrapper {
120 +
  font-size: 12px;
121 +
  opacity: 0.5;
122 +
}
123 +
124 +
.tag {
125 +
  font-size: 12px;
126 +
  opacity: 0.5;
127 +
  float: right;
128 +
}
129 +
130 +
.tag span {
131 +
  background: none;
132 +
  font-weight: 400;
133 +
}
134 +
135 +
/* TODO keywords: no color, weight/opacity only */
136 +
.todo, .done {
137 +
  font-size: 12px;
138 +
  font-weight: 700;
139 +
  text-transform: uppercase;
140 +
}
141 +
142 +
.done {
143 +
  opacity: 0.5;
144 +
}
145 +
146 +
/* Code */
147 +
code {
148 +
  background: #1e1c1f;
149 +
  padding: 0.1rem 0.3rem;
150 +
  font-size: 13px;
151 +
}
152 +
153 +
pre {
154 +
  background: #1e1c1f;
155 +
  color: #ffffff;
156 +
  border: none;
157 +
  border-radius: 0;
158 +
  box-shadow: none;
159 +
  padding: 0.75rem 1rem;
160 +
  font-size: 13px;
161 +
  overflow-x: auto;
162 +
  white-space: pre;
163 +
}
164 +
165 +
pre code {
166 +
  background: none;
167 +
  padding: 0;
168 +
}
169 +
170 +
pre.src:before {
171 +
  display: none;
172 +
}
173 +
174 +
/* htmlize classes (needs org-html-htmlize-output-type 'css) */
175 +
pre span[class^="org-"] {
176 +
  color: #ffffff;
177 +
  background: none;
178 +
  font-weight: 400;
179 +
  font-style: normal;
180 +
}
181 +
182 +
pre span.org-comment,
183 +
pre span.org-comment-delimiter,
184 +
pre span.org-doc {
185 +
  opacity: 0.5;
186 +
  font-style: italic;
187 +
}
188 +
189 +
pre span.org-keyword,
190 +
pre span.org-function-name,
191 +
pre span.org-type {
192 +
  font-weight: 700;
193 +
}
194 +
195 +
pre span.org-string {
196 +
  opacity: 0.7;
197 +
}
198 +
199 +
/* Blockquote */
200 +
blockquote {
201 +
  border-left: 2px solid #555;
202 +
  padding-left: 0.75rem;
203 +
  opacity: 0.7;
204 +
}
205 +
206 +
blockquote p {
207 +
  margin: 0;
208 +
}
209 +
210 +
/* Tables */
211 +
table {
212 +
  border-collapse: collapse;
213 +
  width: 100%;
214 +
  border: none;
215 +
}
216 +
217 +
table caption {
218 +
  font-size: 12px;
219 +
  opacity: 0.5;
220 +
  text-align: left;
221 +
  margin-bottom: 0.25rem;
222 +
}
223 +
224 +
th, td {
225 +
  padding: 0.4rem 0.75rem 0.4rem 0;
226 +
  border: none;
227 +
  border-bottom: 1px solid #333;
228 +
  text-align: left;
229 +
}
230 +
231 +
th {
232 +
  opacity: 0.5;
233 +
  font-weight: 400;
234 +
  font-size: 12px;
235 +
  text-transform: uppercase;
236 +
}
237 +
238 +
/* Misc */
239 +
hr {
240 +
  border: none;
241 +
  border-top: 1px solid #333;
242 +
}
243 +
244 +
img {
245 +
  max-width: 100%;
246 +
  border-radius: 0;
247 +
}
248 +
249 +
figure {
250 +
  display: flex;
251 +
  flex-direction: column;
252 +
  gap: 0.25rem;
253 +
}
254 +
255 +
figcaption, .figure-number {
256 +
  font-size: 12px;
257 +
  opacity: 0.5;
258 +
}
259 +
260 +
.footnotes, #footnotes {
261 +
  border-top: 1px solid #333;
262 +
  padding-top: 0.75rem;
263 +
  font-size: 12px;
264 +
}
265 +
266 +
.footdef {
267 +
  opacity: 0.7;
268 +
}
269 +
270 +
sup a, .footref {
271 +
  font-size: 12px;
272 +
}
273 +
274 +
/* TOC */
275 +
#table-of-contents {
276 +
  border-bottom: 1px solid #333;
277 +
  padding-bottom: 1rem;
278 +
}
279 +
280 +
#table-of-contents h2 {
281 +
  font-size: 12px;
282 +
  text-transform: uppercase;
283 +
  opacity: 0.5;
284 +
  margin-top: 0;
285 +
}
286 +
287 +
#text-table-of-contents ul {
288 +
  list-style: none;
289 +
  padding-left: 1rem;
290 +
}
291 +
292 +
#text-table-of-contents > ul {
293 +
  padding-left: 0;
294 +
}
295 +
296 +
/* Postamble */
297 +
#postamble, .postamble {
298 +
  font-size: 12px;
299 +
  opacity: 0.5;
300 +
}
emacs/themes/darkmatter-theme.el (added) +292 −0
1 +
;;; darkmatter-theme.el --- Darkmatter on base16 -*- lexical-binding: t; -*-
2 +
3 +
;; https://darkmattertheme.com
4 +
;; Palette + a few overrides on top of `base16-theme' (package base16-theme).
5 +
;; The 16 slots are mapped to match the old doom-darkmatter roles.
6 +
7 +
(require 'cl-lib)
8 +
(require 'base16-theme)
9 +
10 +
;;; Palette -- edit these and everything below follows
11 +
12 +
(defun darkmatter--blend (a b alpha)
13 +
  "Mix hex colors A and B; ALPHA is the share of B (0..1)."
14 +
  (cl-flet ((rgb (c) (mapcar (lambda (i) (string-to-number (substring c i (+ i 2)) 16))
15 +
                             '(1 3 5))))
16 +
    (apply #'format "#%02x%02x%02x"
17 +
           (cl-mapcar (lambda (x y) (round (+ (* x (- 1 alpha)) (* y alpha))))
18 +
                      (rgb a) (rgb b)))))
19 +
20 +
(defvar darkmatter-palette
21 +
  '((base00 . "#121113")   ; background
22 +
    (base01 . "#121212")   ; lighter background
23 +
    (base02 . "#222222")   ; selection
24 +
    (base03 . "#333333")   ; comments
25 +
    (base04 . "#999999")   ; dark foreground, doc strings
26 +
    (base05 . "#c1c1c1")   ; foreground
27 +
    (base06 . "#999999")   ; light foreground
28 +
    (base07 . "#c1c1c1")   ; lightest
29 +
    (base08 . "#5f8787")   ; variables, errors
30 +
    (base09 . "#aaaaaa")   ; constants, numbers
31 +
    (base0A . "#e78a53")   ; types, accent, warnings
32 +
    (base0B . "#fbcb97")   ; strings, added
33 +
    (base0C . "#aaaaaa")   ; builtins, support
34 +
    (base0D . "#888888")   ; functions
35 +
    (base0E . "#999999")   ; keywords
36 +
    (base0F . "#444444")   ; deprecated
37 +
    (red    . "#aa6c6c"))  ; extra: removed lines, ERC errors (not a base16 slot)
38 +
  "Darkmatter colors. Base16 slots plus `red'. Everything else is derived.")
39 +
40 +
(deftheme darkmatter "Darkmatter, based on Black Metal Bathory.")
41 +
42 +
(let* ((base00 (alist-get 'base00 darkmatter-palette))
43 +
       (base01 (alist-get 'base01 darkmatter-palette))
44 +
       (base02 (alist-get 'base02 darkmatter-palette))
45 +
       (base03 (alist-get 'base03 darkmatter-palette))
46 +
       (base04 (alist-get 'base04 darkmatter-palette))
47 +
       (base05 (alist-get 'base05 darkmatter-palette))
48 +
       (base06 (alist-get 'base06 darkmatter-palette))
49 +
       (base07 (alist-get 'base07 darkmatter-palette))
50 +
       (base08 (alist-get 'base08 darkmatter-palette))
51 +
       (base09 (alist-get 'base09 darkmatter-palette))
52 +
       (base0A (alist-get 'base0A darkmatter-palette))
53 +
       (base0B (alist-get 'base0B darkmatter-palette))
54 +
       (base0C (alist-get 'base0C darkmatter-palette))
55 +
       (base0D (alist-get 'base0D darkmatter-palette))
56 +
       (base0E (alist-get 'base0E darkmatter-palette))
57 +
       (base0F (alist-get 'base0F darkmatter-palette))
58 +
       (red    (alist-get 'red darkmatter-palette))
59 +
       ;; roles used by the face overrides below
60 +
       (bg      base00)
61 +
       (bg-alt  base01)
62 +
       (sel     base02)
63 +
       (comment base03)
64 +
       (dim     base04)
65 +
       (fg      base05)
66 +
       (fg-hi   base06)
67 +
       (fg-max  base07)
68 +
       (teal    base08)
69 +
       (const   base09)
70 +
       (orange  base0A)
71 +
       (string  base0B)
72 +
       (func    base0D)
73 +
       (kw      base0E)
74 +
       (dep     base0F)
75 +
       ;; derived
76 +
       (base0          (darkmatter--blend bg "#000000" 0.4))
77 +
       (faint          (darkmatter--blend bg-alt kw 0.2))
78 +
       (muted          (darkmatter--blend bg dim 0.45))
79 +
       (bg-tint        (darkmatter--blend bg const 0.15))
80 +
       (added-fg       (darkmatter--blend string "#000000" 0.2))
81 +
       (added-bg       (darkmatter--blend bg string 0.1))
82 +
       (added-bg-hi    (darkmatter--blend bg string 0.2))
83 +
       (removed-bg     (darkmatter--blend sel red 0.1))
84 +
       (removed-bg-hi  (darkmatter--blend sel red 0.2))
85 +
       (changed-bg     (darkmatter--blend bg orange 0.1))
86 +
       (changed-bg-hi  (darkmatter--blend bg orange 0.2))
87 +
       (red-dark       (darkmatter--blend red "#000000" 0.2))
88 +
       (orange-dark    (darkmatter--blend orange "#000000" 0.2)))
89 +
90 +
  ;; Fringe/line numbers share the main background, like doom-darkmatter
91 +
  (setq base16-theme-distinct-fringe-background nil)
92 +
93 +
  (base16-theme-define
94 +
   'darkmatter
95 +
   (list :base00 base00 :base01 base01 :base02 base02 :base03 base03
96 +
         :base04 base04 :base05 base05 :base06 base06 :base07 base07
97 +
         :base08 base08 :base09 base09 :base0A base0A :base0B base0B
98 +
         :base0C base0C :base0D base0D :base0E base0E :base0F base0F))
99 +
100 +
  ;; Where doom-darkmatter differs from stock base16 role assignments,
101 +
  ;; plus packages base16 has no faces for
102 +
  (custom-theme-set-faces
103 +
   'darkmatter
104 +
   `(cursor                      ((t (:background ,orange))))
105 +
   `(font-lock-variable-name-face ((t (:foreground ,teal))))
106 +
   `(font-lock-builtin-face      ((t (:foreground ,kw))))
107 +
   `(font-lock-constant-face     ((t (:foreground ,const))))
108 +
   `(font-lock-number-face       ((t (:foreground ,const))))
109 +
   `(font-lock-warning-face      ((t (:foreground ,orange))))
110 +
   `(mode-line                   ((t (:foreground ,fg :background ,bg :overline ,sel :box nil))))
111 +
   `(mode-line-inactive          ((t (:foreground ,dim :background ,bg :overline ,sel :box nil))))
112 +
   `(mode-line-buffer-id         ((t (:foreground ,fg :weight bold))))
113 +
   `(line-number                 ((t (:foreground ,faint :background ,bg))))
114 +
   `(line-number-current-line    ((t (:foreground ,orange :background ,bg))))
115 +
   `(vertical-border             ((t (:foreground ,sel))))
116 +
   `(erc-prompt-face             ((t (:foreground ,orange :weight bold))))
117 +
   ;; base16 uses base01 for highlight, which is ~identical to bg here
118 +
   `(highlight                   ((t (:background ,sel))))
119 +
   `(icomplete-selected-match    ((t (:foreground ,orange :background ,sel :weight bold :extend t))))
120 +
   `(completions-highlight       ((t (:inherit icomplete-selected-match))))
121 +
   ;; base16 makes this inherit gnus-group-news-6, whose default spec
122 +
   ;; inherits this face back -> cycle error in enable-theme
123 +
   `(gnus-group-news-6-empty     ((t (:foreground ,base04 :inherit outline-2))))
124 +
   ;; base16 only themes the normal 8; bright ones (used by ghostel etc.)
125 +
   ;; fall back to Emacs defaults. Match the darkmatter ghostty palette.
126 +
   `(ansi-color-bright-black     ((t (:foreground ,base03 :background ,base03))))
127 +
   `(ansi-color-bright-red       ((t (:foreground ,base08 :background ,base08))))
128 +
   `(ansi-color-bright-green     ((t (:foreground ,base0B :background ,base0B))))
129 +
   `(ansi-color-bright-yellow    ((t (:foreground ,base0A :background ,base0A))))
130 +
   `(ansi-color-bright-blue      ((t (:foreground ,base0D :background ,base0D))))
131 +
   `(ansi-color-bright-magenta   ((t (:foreground ,base0E :background ,base0E))))
132 +
   `(ansi-color-bright-cyan      ((t (:foreground ,base0C :background ,base0C))))
133 +
   `(ansi-color-bright-white     ((t (:foreground ,base07 :background ,base07))))
134 +
135 +
   ;; Packages base16 has no faces for
136 +
   `(elfeed-search-date-face          ((t (:foreground ,const))))
137 +
   `(elfeed-search-feed-face          ((t (:foreground ,func))))
138 +
   `(elfeed-search-tag-face           ((t (:foreground ,comment))))
139 +
   `(elfeed-search-title-face         ((t (:foreground ,comment))))
140 +
   `(elfeed-search-unread-title-face  ((t (:foreground ,fg :weight bold))))
141 +
   `(elfeed-search-unread-count-face  ((t (:foreground ,orange))))
142 +
   `(elfeed-search-filter-face        ((t (:foreground ,const))))
143 +
   `(elfeed-log-debug-level-face      ((t (:foreground ,comment))))
144 +
   `(elfeed-log-error-level-face      ((t (:inherit error))))
145 +
   `(elfeed-log-info-level-face       ((t (:inherit success))))
146 +
   `(elfeed-log-warn-level-face       ((t (:inherit warning))))
147 +
   `(which-key-key-face               ((t (:foreground ,orange))))
148 +
   `(which-key-group-description-face ((t (:foreground ,kw))))
149 +
   `(which-key-command-description-face ((t (:foreground ,fg)))))
150 +
  
151 +
  ;; Magit/diff/org/vc faces, flattened from doom-darkmatter
152 +
  (custom-theme-set-faces
153 +
   'darkmatter
154 +
   `(diff-hl-change ((t (:foreground "#e78a53" :background "#e78a53"))))
155 +
   `(diff-hl-delete ((t (:foreground "#aa6c6c" :background "#aa6c6c"))))
156 +
   `(diff-hl-insert ((t (:foreground "#fbcb97" :background "#fbcb97"))))
157 +
   `(diff-added ((t (:inherit hl-line :foreground ,string))))
158 +
   `(diff-changed ((t (:foreground ,const))))
159 +
   `(diff-context ((t (:foreground ,const))))
160 +
   `(diff-removed ((t (:foreground ,red :background ,sel))))
161 +
   `(diff-header ((t (:foreground ,const))))
162 +
   `(diff-file-header ((t (:foreground ,func))))
163 +
   `(diff-hunk-header ((t (:foreground ,const))))
164 +
   `(diff-indicator-added ((t (:foreground ,string))))
165 +
   `(diff-indicator-changed ((t (:foreground ,orange))))
166 +
   `(diff-indicator-removed ((t (:foreground ,red))))
167 +
   `(diff-refine-added ((t (:inherit diff-added :inverse-video t))))
168 +
   `(diff-refine-changed ((t (:inherit diff-changed :inverse-video t))))
169 +
   `(diff-refine-removed ((t (:inherit diff-removed :inverse-video t))))
170 +
   `(ediff-fine-diff-A ((t (:inherit diff-refine-removed))))
171 +
   `(ediff-fine-diff-B ((t (:inherit diff-refine-added))))
172 +
   `(ediff-fine-diff-C ((t (:inherit ediff-fine-diff-A))))
173 +
   `(ediff-current-diff-A ((t (:foreground ,red :background ,removed-bg-hi :extend t))))
174 +
   `(ediff-current-diff-B ((t (:foreground ,string :background ,added-bg-hi :extend t))))
175 +
   `(ediff-current-diff-C ((t (:inherit ediff-current-diff-A))))
176 +
   `(ediff-even-diff-A ((t (:inherit hl-line))))
177 +
   `(ediff-even-diff-B ((t (:inherit ediff-even-diff-A))))
178 +
   `(ediff-even-diff-C ((t (:inherit ediff-even-diff-A))))
179 +
   `(ediff-odd-diff-A ((t (:inherit ediff-even-diff-A))))
180 +
   `(ediff-odd-diff-B ((t (:inherit ediff-odd-diff-A))))
181 +
   `(ediff-odd-diff-C ((t (:inherit ediff-odd-diff-A))))
182 +
   `(git-commit-summary ((t (:foreground ,string))))
183 +
   `(git-commit-overlong-summary ((t (:inherit error :background ,base0 :slant italic :weight bold))))
184 +
   `(git-commit-nonempty-second-line ((t (:inherit git-commit-overlong-summary))))
185 +
   `(git-commit-keyword ((t (:foreground ,const :slant italic))))
186 +
   `(git-commit-pseudo-header ((t (:foreground ,dim :slant italic))))
187 +
   `(git-commit-known-pseudo-header ((t (:foreground ,dim :weight bold :slant italic))))
188 +
   `(git-commit-comment-branch-local ((t (:foreground ,kw))))
189 +
   `(git-commit-comment-branch-remote ((t (:foreground ,string))))
190 +
   `(git-commit-comment-detached ((t (:foreground ,orange))))
191 +
   `(git-commit-comment-heading ((t (:foreground ,kw))))
192 +
   `(git-commit-comment-file ((t (:foreground ,const))))
193 +
   `(git-commit-comment-action ((t nil)))
194 +
   `(hl-line ((t (:background ,bg :extend t))))
195 +
   `(magit-bisect-bad ((t (:foreground ,red))))
196 +
   `(magit-bisect-good ((t (:foreground ,string))))
197 +
   `(magit-bisect-skip ((t (:foreground ,orange))))
198 +
   `(magit-blame-hash ((t (:foreground ,const))))
199 +
   `(magit-blame-date ((t (:foreground ,red))))
200 +
   `(magit-blame-heading ((t (:foreground ,orange :background ,sel :extend t))))
201 +
   `(magit-branch-current ((t (:foreground ,func))))
202 +
   `(magit-branch-local ((t (:foreground ,const))))
203 +
   `(magit-branch-remote ((t (:foreground ,string))))
204 +
   `(magit-cherry-equivalent ((t (:foreground ,const))))
205 +
   `(magit-cherry-unmatched ((t (:foreground ,const))))
206 +
   `(magit-diff-added ((t (:foreground ,added-fg :background ,added-bg :extend t))))
207 +
   `(magit-diff-added-highlight ((t (:foreground ,string :background ,added-bg-hi :weight bold :extend t))))
208 +
   `(magit-diff-base ((t (:foreground ,orange-dark :background ,changed-bg :extend t))))
209 +
   `(magit-diff-base-highlight ((t (:foreground ,orange :background ,changed-bg-hi :weight bold :extend t))))
210 +
   `(magit-diff-context ((t (:foreground ,dim :background ,bg :extend t))))
211 +
   `(magit-diff-context-highlight ((t (:foreground ,fg :background ,bg :extend t))))
212 +
   `(magit-diff-file-heading ((t (:foreground ,fg :weight bold :extend t))))
213 +
   `(magit-diff-file-heading-selection ((t (:foreground ,kw :background ,dim :weight bold :extend t))))
214 +
   `(magit-diff-hunk-heading ((t (:foreground ,bg :background ,muted :extend t))))
215 +
   `(magit-diff-hunk-heading-highlight ((t (:foreground ,bg :background ,const :weight bold :extend t))))
216 +
   `(magit-diff-lines-heading ((t (:foreground ,orange :background ,red :extend t :extend t))))
217 +
   `(magit-diff-removed ((t (:foreground ,red-dark :background ,removed-bg :extend t))))
218 +
   `(magit-diff-removed-highlight ((t (:foreground ,red :background ,removed-bg-hi :weight bold :extend t))))
219 +
   `(magit-diffstat-added ((t (:foreground ,string))))
220 +
   `(magit-diffstat-removed ((t (:foreground ,red))))
221 +
   `(magit-dimmed ((t (:foreground ,kw))))
222 +
   `(magit-hash ((t (:foreground ,comment))))
223 +
   `(magit-header-line ((t (:background ,dim :foreground ,fg :weight bold :box (:line-width 3 :color ,dim)))))
224 +
   `(magit-filename ((t (:foreground ,const))))
225 +
   `(magit-log-author ((t (:foreground ,orange))))
226 +
   `(magit-log-date ((t (:foreground ,func))))
227 +
   `(magit-log-graph ((t (:foreground ,comment))))
228 +
   `(magit-process-ng ((t (:inherit error))))
229 +
   `(magit-process-ok ((t (:inherit success))))
230 +
   `(magit-reflog-amend ((t (:foreground ,kw))))
231 +
   `(magit-reflog-checkout ((t (:foreground ,func))))
232 +
   `(magit-reflog-cherry-pick ((t (:foreground ,string))))
233 +
   `(magit-reflog-commit ((t (:foreground ,string))))
234 +
   `(magit-reflog-merge ((t (:foreground ,string))))
235 +
   `(magit-reflog-other ((t (:foreground ,const))))
236 +
   `(magit-reflog-rebase ((t (:foreground ,kw))))
237 +
   `(magit-reflog-remote ((t (:foreground ,const))))
238 +
   `(magit-reflog-reset ((t (:inherit error))))
239 +
   `(magit-refname ((t (:foreground ,comment))))
240 +
   `(magit-section-heading ((t (:foreground ,func :weight bold :extend t))))
241 +
   `(magit-section-heading-selection ((t (:foreground ,orange :weight bold :extend t))))
242 +
   `(magit-section-highlight ((t (:inherit hl-line))))
243 +
   `(magit-section-secondary-heading ((t (:foreground ,const :weight bold :extend t))))
244 +
   `(magit-sequence-drop ((t (:foreground ,red))))
245 +
   `(magit-sequence-head ((t (:foreground ,func))))
246 +
   `(magit-sequence-part ((t (:foreground ,orange))))
247 +
   `(magit-sequence-stop ((t (:foreground ,string))))
248 +
   `(magit-signature-bad ((t (:inherit error))))
249 +
   `(magit-signature-error ((t (:inherit error))))
250 +
   `(magit-signature-expired ((t (:foreground ,orange))))
251 +
   `(magit-signature-good ((t (:inherit success))))
252 +
   `(magit-signature-revoked ((t (:foreground ,kw))))
253 +
   `(magit-signature-untrusted ((t (:foreground ,orange))))
254 +
   `(magit-tag ((t (:foreground ,orange))))
255 +
   `(org-block ((t (:background ,sel :extend t))))
256 +
   `(org-block-background ((t (:background ,sel :extend t))))
257 +
   `(org-block-begin-line ((t (:inherit org-block :foreground ,comment))))
258 +
   `(org-block-end-line ((t (:inherit org-block-begin-line))))
259 +
   `(org-checkbox ((t (:inherit org-todo))))
260 +
   `(org-checkbox-statistics-done ((t (:inherit org-done))))
261 +
   `(org-checkbox-statistics-todo ((t (:inherit org-todo))))
262 +
   `(org-code ((t (:inherit org-block :foreground ,orange))))
263 +
   `(org-date ((t (:foreground ,orange))))
264 +
   `(org-document-info ((t (:foreground ,kw))))
265 +
   `(org-document-title ((t (:foreground ,kw :weight bold))))
266 +
   `(org-done ((t (:inherit org-headline-done :strike-through unspecified :weight bold))))
267 +
   `(org-link ((t (:inherit link :foreground ,orange))))
268 +
   `(org-meta-line ((t (:foreground ,dim))))
269 +
   `(org-table ((t (:foreground ,const))))
270 +
   `(org-tag ((t (:foreground ,dim :weight normal))))
271 +
   `(org-todo ((t (:foreground ,string :bold inherit))))
272 +
   `(org-verbatim ((t (:foreground ,string))))
273 +
   `(outline-1 ((t (:foreground ,func :weight bold :extend t))))
274 +
   `(outline-2 ((t (:foreground ,kw :weight bold :extend t))))
275 +
   `(outline-3 ((t (:foreground ,const :weight bold :extend t))))
276 +
   `(outline-4 ((t (:foreground ,const :weight bold :extend t))))
277 +
   `(outline-5 ((t (:foreground ,const :weight bold :extend t))))
278 +
   `(outline-6 ((t (:foreground ,fg :weight bold :extend t))))
279 +
   `(outline-7 ((t (:foreground ,fg-hi :weight bold :extend t))))
280 +
   `(outline-8 ((t (:foreground ,fg-max :weight bold :extend t))))
281 +
   `(smerge-lower ((t (:background ,added-bg-hi))))
282 +
   `(smerge-upper ((t (:background ,removed-bg-hi))))
283 +
   `(smerge-base ((t (:background ,bg-tint))))
284 +
   `(smerge-markers ((t (:background ,comment :foreground ,bg :distant-foreground ,fg :weight bold))))
285 +
   `(smerge-refined-added ((t (:inherit diff-added :inverse-video t))))
286 +
   `(smerge-refined-removed ((t (:inherit diff-removed :inverse-video t))))
287 +
   `(smerge-mine ((t (:background ,removed-bg-hi))))
288 +
   `(smerge-other ((t (:background ,added-bg-hi))))))
289 +
290 +
(provide-theme 'darkmatter)
291 +
292 +
;;; darkmatter-theme.el ends here
home.scm +15 −2
3 3
(use-modules (gnu home)
4 4
             (gnu home services)
5 5
             (gnu home services shells)
6 +
             (gnu home services xdg)
6 7
             (gnu packages)
7 8
             (gnu services)
8 9
             (gnu system shadow)
29 30
30 31
     (service home-files-service-type
31 32
              `((".guile" ,%default-dotguile)
32 -
                (".Xdefaults" ,%default-xdefaults)))
33 +
                (".Xdefaults" ,%default-xdefaults)
34 +
                ;; Emacs config files are linked into ~/.emacs.d so that
35 +
                ;; elpa/ and other runtime state stay writable there.
36 +
                (".emacs.d/early-init.el" ,(local-file "emacs/early-init.el"))
37 +
                (".emacs.d/init.el" ,(local-file "emacs/init.el"))
38 +
                (".emacs.d/org.css" ,(local-file "emacs/org.css"))
39 +
                (".emacs.d/lisp" ,(local-file "emacs/lisp" #:recursive? #t))
40 +
                (".emacs.d/themes" ,(local-file "emacs/themes" #:recursive? #t))))
33 41
34 42
     (service home-xdg-configuration-files-service-type
35 43
              `(("starship.toml" ,(local-file "starship/starship.toml"))
36 44
                ("gdb/gdbinit" ,%default-gdbinit)
37 -
                ("nano/nanorc" ,%default-nanorc))))
45 +
                ("nano/nanorc" ,%default-nanorc)))
46 +
47 +
     ;; ~/.local/share/fonts is on fontconfig's default search path
48 +
     (service home-xdg-data-files-service-type
49 +
              `(("fonts/BerkeleyMonoNerdFontMono-Regular.ttf"
50 +
                 ,(local-file "fonts/BerkeleyMonoNerdFontMono-Regular.ttf")))))
38 51
39 52
    %base-home-services)))