chore: added emacs
3639f153
17 file(s) · +1680 −2
| 1 | + | # Licensed fonts: kept locally, not committed |
|
| 2 | + | fonts/ |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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 |
| 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 |
| 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 | + | } |
| 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 |
| 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))) |
|