diff options
Diffstat (limited to 'emacs/init.el')
-rw-r--r-- | emacs/init.el | 580 |
1 files changed, 527 insertions, 53 deletions
diff --git a/emacs/init.el b/emacs/init.el index fa52557..43df25a 100644 --- a/emacs/init.el +++ b/emacs/init.el @@ -1,6 +1,15 @@ -(add-to-list 'load-path (expand-file-name (concat user-emacs-directory "site-lisp"))) +;;; init.el --- my emacs configuration -*- lexical-binding: t; -*- + +(add-to-list 'load-path (expand-file-name (concat user-emacs-directory "site-lisp"))) (setq garbage-collection-messages t) +(add-hook 'emacs-startup-hook + #'(lambda () + (message (format "Initialised in %s seconds with %s garbage collections." (emacs-init-time) gcs-done)) + ;;; reset garbage collector + (setq + gc-cons-threshold 800000 + gc-cons-percentage 0.2))) (setopt user-mail-address "noa@gaiwan.org") @@ -27,7 +36,6 @@ minibuffer-depth-indicate-mode t) (setopt - browse-url-browser-function 'eww-browse-url url-cookie-trusted-urls '() url-cookie-untrusted-urls '(".*")) @@ -43,6 +51,19 @@ (read-file-name-completion-ignore-case t) (completion-ignore-case t) ) +(use-package vertico-directory + :after vertico + :ensure nil + ;; More convenient directory navigation commands + :bind ( + :map vertico-map + ("RET" . vertico-directory-enter) + ("<backspace>" . vertico-directory-delete-char) + ("C-<backspace>" . vertico-directory-delete-word) + ) + ;; Tidy shadowed file names + :hook (rfn-eshadow-update-overlay . vertico-directory-tidy) +) (use-package orderless :ensure t @@ -53,7 +74,36 @@ (setopt confirm-nonexistent-file-or-buffer 'after-completion) +(defun noa/mode-line-modified () + (cond + (buffer-read-only "RO") + ((buffer-modified-p) "**") + (t "RW"))) + +(setq-default header-line-format '( + (:propertize + ( + "" + mode-line-modified + mode-line-remote + ) + face highlight + ) + " " + "%b" + ":%l,%c" + " " + ;; (vc-mode vc-mode) + ;; " " + ;; mode-line-modes + mode-line-misc-info + mode-line-end-spaces + ) +) +(setq-default mode-line-format nil) + (use-package nano-modeline + :disabled t :ensure t :hook ( (prog-mode . nano-modeline-prog-mode) @@ -77,6 +127,40 @@ :delight :custom (beacon-mode t)) +(use-package jabber + :ensure t + :init + (setq noa/jabber-activity-dont-show '( + "#tildetown%town@irc.hmm.st" + "#meta%tilde.chat@irc.hmm.st" + )) + (defun noa/jabber-activity-show-p (jid) + "Return non-nil if JID should be hidden. +A JID should be hidden when there is an invisible buffer for JID, +when JID is not in `noa/jabber-activity-dont-show', +and when JID is not in `jabber-activity-banned'." + (let ((buffer (jabber-activity-find-buffer-name jid))) + (and + (buffer-live-p buffer) + (not (get-buffer-window buffer 'visible)) + (not + (cl-dolist (entry jabber-activity-banned) + (when (string-match entry jid) + (cl-return t)))) + (not + (cl-dolist (entry noa/jabber-activity-dont-show) + (when (string-match entry jid) + (cl-return t)))) + ) + ) + ) + :custom + (jabber-history-enabled t) + (jabber-account-list '(("noa@hmm.st"))) + (jabber-keepalive-interval 60) + (noa/jabber-activity-show-p #'noa/jabber-activity-show-p) +) + (use-package nov :ensure t :mode ("\\.epub\\'" . nov-mode)) @@ -111,6 +195,7 @@ (use-package marginalia :ensure t + :after vertico :custom (marginalia-mode t) (marginalia-max-relative-age most-positive-fixnum) @@ -142,29 +227,112 @@ (use-package org-roam :ensure t + :after org + :after transient :custom (org-roam-directory (expand-file-name "~/data/notes/")) (org-roam-completion-everywhere t) - :bind ( - ("C-c n l" . org-roam-buffer-toggle) - ("C-c n f" . org-roam-node-find) - ("C-c n g" . org-roam-graph) - ("C-c n i" . org-roam-node-insert) - ("C-c n c" . org-roam-capture) - ;; Dailies - ("C-c n j" . org-roam-dailies-capture-today) - ) - :custom ;; If you're using a vertical completion framework, you might want a more informative completion interface (org-roam-node-display-template (concat "${title:*} " (propertize "${tags:10}" 'face 'org-tag))) - (org-roam-db-autosync-mode t) + :bind ( + ("C-c n" . noa/notes-menu) + ) :config + (org-roam-db-autosync-mode 1) (require 'org-roam-protocol) -) + ;; Open the org-roam backlinks buffer in a sidebar on the right. I have it set to take up a quarter of the width, which looks nice on my tiny computer, but if i ever use a bigger display, i'll probably have to set it to some amount of characters instead. + (add-to-list 'display-buffer-alist '( + "^\\*org-roam\\*" + display-buffer-in-side-window + (side . right) + (window-width . 0.25))) + (transient-define-prefix noa/notes-menu () + "Access org-roam functionality." + [("l" "Toggle the backlinks sidebar" org-roam-buffer-toggle :transient nil)] + + ["Creating notes" + ("f" "Open or create a note" org-roam-node-find :transient nil) + ("i" "Insert a new or existing note at point" org-roam-node-insert :transient nil) + ("c" "Org-roam capture" org-roam-capture :transient nil) + ] + + [ + "Interact with the daily journal" + ("j" "Add to the daily journal" org-roam-dailies-capture-today :transient nil) + ("d" "More daily entry commands" noa/dailies-menu :transient nil) + ]) + (transient-define-prefix noa/dailies-menu () + "Interact with daily entries." + [ + ("." "Go to the dailies directory" org-roam-dailies-find-directory :transient nil) + ("b" "Go to the previous daily note" org-roam-dailies-goto-previous-note :transient nil) + ("c" "Go to the daily note for a selected day" org-roam-dailies-goto-date :transient nil) + ("d" "Go to today's daily note" org-roam-dailies-goto-today :transient nil) + ("f" "Go to the next daily note" org-roam-dailies-goto-next-note :transient nil) + ("n" "Add to today's daily note" org-roam-dailies-capture-today :transient nil) + ("t" "Go to tomorrow's daily note" org-roam-dailies-goto-tomorrow :transient nil) + ("v" "Add to the daily note for a selected day" org-roam-dailies-capture-date :transient nil) + ("y" "Go to yesterday's daily note" org-roam-dailies-goto-yesterday :transient nil) + ] + )) + +;; (use-package org-contacts +;; :ensure t +;; :pin gnu +;; :custom +;; (org-contacts-files nil) +;; :config +;; (add-to-list org-roam-capture-templates +;; "c" "Contact" plain "%?" :target (file+head "~/data/notes/contacts/${slug}.org" "#+title: %(org-contacts-template-name)\n") +;; "* %(org-contacts-template-name) +;; :PROPERTIES: +;; :ADDRESS: %^{289 Cleveland St. Brooklyn, 11206 NY, USA} +;; :BIRTHDAY: %^{yyyy-mm-dd} +;; :EMAIL: %(org-contacts-template-email) +;; :NOTE: %^{NOTE} +;; :END:" +;; :empty-lines 1 +;; ) +;; ) + +(use-package transient + :ensure t + :commands transient-define-prefix) + +(use-package helpful + :ensure t + :bind ( + ([remap describe-function] . helpful-callable) + ([remap describe-variable] . helpful-variable) + ([remap describe-key] . helpful-key) + ([remap describe-command] . helpful-command) + ("C-c C-d" . helpful-at-point) + ("C-h F" . helpful-function) + )) + +;; this doesn't seem to work nicely with proportional fonts +(use-package adaptive-wrap + :disabled + :ensure t + :hook (visual-line-mode . adaptive-wrap-prefix-mode)) + +(use-package window + :custom + (switch-to-buffer-obey-display-actions t)) + + +(use-package org-fc + :disabled + :ensure t + :custom + (org-fc-directories (expand-file-name "~/data/notes/"))) + +(use-package artbollocks-mode + :disabled t + :ensure t) (use-package org :custom - (org-startup-indented t) (org-pretty-entities t) (org-use-sub-superscripts "{}") (org-hide-emphasis-markers t) @@ -176,6 +344,19 @@ (org-special-ctrl-a/e t) (org-insert-heading-respect-content t) (org-ellipsis "…") + (org-display-custom-times t) + (org-time-stamp-custom-formats '("%Y-%m-%d" . "%Y-%m-%d %H:%M")) + (org-extend-today-until 4) + (org-adapt-indentation nil) + (org-log-done 'time) + (org-return-follows-link t) + (org-agenda-files '("~/data/notes/notes.org")) + (org-capture-templates + `( + ("j" "Journal" entry (file+datetree "~/data/notes/notes.org") "* %?\n" :empty-lines 1) + + ) + ) (org-agenda-tags-column t) (org-agenda-block-separator ?─) @@ -191,6 +372,7 @@ ) (use-package org-appear + :disabled t :ensure t :after org :hook @@ -203,10 +385,174 @@ (org-mode . org-modern-mode) (org-agenda-finalize . org-modern-agenda) :custom + (org-modern-list '( + (?+ . " • ") + (?- . " • ") + (?* . " • ") + )) + (org-modern-star nil) (org-modern-keyword nil) (org-modern-checkbox nil) (org-modern-table nil)) +(defun read-file-as-string (filename) + "Read file contents from FILENAME." + (with-temp-buffer + (insert-file-contents filename) + (buffer-string))) + +(setq noa/website-header (read-file-as-string "/home/noa/projects/org-website/templates/header.html")) +(setq noa/website-footer (read-file-as-string "/home/noa/projects/org-website/templates/footer.html")) + + + +(defun my-blog-parse-sitemap-list (l) + "Convert the sitemap list in to a list of filenames." + ;; LIST looks like: + ;; (unordered ("[[file:uses.org][Things i use]]") ("[[file:media.org][Media Diary]]") ("[[file:tanklobsters.org][Tank lobsters]]")) + (mapcar + #'(lambda (i) + (let + ( + (link (with-temp-buffer ( + let ( + (org-inhibit-startup nil) + ) + (insert (car i)) + (org-mode) + (goto-char (point-min)) + (org-element-link-parser) + ))) + ) + (when link + (plist-get (cadr link) :path) + ) + ) + ) + (cdr l) + ) +) + +(defun my-blog-sort-article-list (l p) + "sort the article list anti-chronologically." + (sort l #'(lambda (a b) + (let ((date-a (org-publish-find-date a p)) + (date-b (org-publish-find-date b p))) + (not (time-less-p date-a date-b)))))) + +(defun noa/naive-org-first-paragraph (file) + "Naively returns the first paragraph of FILE. + + The way that the first paragraph is determined is to assume that there will be an org metadata block beforehand, so look for the first two consecutive newlines and mark the following paragraph." + (with-temp-buffer + (insert-file-contents file) + (goto-char (point-min)) + (re-search-forward "\n\n") + (mark-paragraph) + (let + ( + (beg (mark)) + (end (point)) + ) + (buffer-substring beg end)))) + +(defun noa/website-sitemap (title list) + "Generate the index page for my website." + ;; LIST looks like: + ;; (unordered ("[[file:uses.org][Things i use]]") ("[[file:media.org][Media Diary]]") ("[[file:tanklobsters.org][Tank lobsters]]")) + (with-temp-buffer + ;; mangle the parsed list given to us into a plain lisp list of files + (let* + ( + (filenames (my-blog-parse-sitemap-list list)) + (project-plist (assoc "website-pages" org-publish-project-alist)) + (articles (my-blog-sort-article-list filenames project-plist)) + ) + (message (concat "PLIST: " (plist-get project-plist :base-directory))) + + (insert "Several parts of this website are broken as i wrangle with the monstrosity that is programming in emacs lisp. The content should still be fine, but for further cosmetics please hold <3\n\n") + (dolist (file filenames) + (let* + ( + (abspath (file-name-concat "/home/noa/data/share" file)) + ;; (abspath (file-name-concat (plist-get project-plist :base-directory) file)) + (relpath (file-relative-name abspath "/home/noa/data/share")) + (title (org-publish-find-title file project-plist)) + (date (format-time-string (car org-time-stamp-formats) (org-publish-find-date file project-plist))) + (preview (noa/naive-org-first-paragraph abspath)) + ) + (insert (concat "* [[file:" relpath "][" title "]]\n")) + (insert (concat + "*" date ":*" + preview + )) + (insert "\n") + ;; (insert (concat "[[file:" relpath "][Read More...]]\n")) + ) + ) + ;; insert a title and save + (insert "#+TITLE: noa.pub\n") + (buffer-string) + ) + ) +) + +(setq org-publish-project-alist `( + ("website" + :components ("website-pages" "website-assets")) + ("website-pages" + :publishing-function org-html-publish-to-html + :base-directory "/home/noa/data/share" + :publishing-directory "/home/noa/projects/org-website" + :base-extension "org" + + :section-numbers nil + :with-toc nil + :with-drawers t + :with-sub-superscript t + :html-link-home "/" + :html-head "<link rel=\"icon\" href=\"data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>𰻝</text></svg>\">" + :html-head-include-default-style nil + :html-head-include-scripts nil + :html-doctype "html5" + ;; :html-validation-link nil + + :html-preamble "" + :html-postamble ,noa/website-footer + + :html-home/up-format "" + :html-link-up "" + + :html-html5-fancy t + :html-indent t + + :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"love.css\" /> +<meta name=\"color-scheme\" content=\"light dark\">" + + :html-divs ( + (preamble "header" "") + (content "main" "") + (postamble "footer" "")) + + :auto-sitemap t + :sitemap-filename "/home/noa/projects/org-website/index.org" + :sitemap-title "noa.pub" + :sitemap-style list + ;; :sitemap-format-entry + :sitemap-function noa/website-sitemap + :sitemap-sort-folders ignore + :sitemap-sort-files anti-chronologically + :sitemap-ignore-case t + ) + ("website-assets" + :publishing-function org-publish-attachment + :base-directory "/home/noa/data/share" + :publishing-directory "/home/noa/projects/org-website" + :base-extension "css\\|js\\|png|\\jpg" + :recursive t))) + +;; TODO: replace with visual-fill-column +;; https://codeberg.org/joostkremers/visual-fill-column (use-package olivetti :ensure t :hook ( @@ -217,8 +563,21 @@ ) ) -(use-package delight - :ensure t) +(use-package ffap + :custom + (ffap-file-name-with-spaces t) + :bind ( + ([remap find-file] . find-file-at-point) + ([remap dired] . dired-at-point) + ) +) + +(use-package eww + :custom + (browse-url-browser-function 'eww-browse-url)) + +(use-package abbrev + :hook (text-mode . abbrev-mode)) (use-package eldoc :delight @@ -230,28 +589,20 @@ :delight :hook (prog-mode . rainbow-mode)) -(use-package hyperspace - :disabled - :ensure t - :bind ("<C-m>" . hyperspace)) - -(use-package hyperbole - :disabled - :ensure t) - ;; use a bar cursor and blink it and don't stop blinking it. i don't know how i feel about this yet to be honest, but it helps me know which window is active so for now i'm keeping it (setopt cursor-type 'bar blink-cursor-mode -1 blink-cursor-interval 0.7) -(setopt - dired-recursive-deletes 'always - dired-recursive-copies 'always - dired-clean-up-buffers-too nil - delete-by-moving-to-trash t - dired-dwim-target t - dired-listing-switches "-alv") +(use-package dired + :custom + (dired-recursive-deletes 'always) + (dired-recursive-copies 'always) + (dired-clean-up-buffers-too nil) + (delete-by-moving-to-trash t) + (dired-dwim-target t) + (dired-listing-switches "-alvFh --group-directories-first")) ;;; indentation: tabs and whitespace settings @@ -286,8 +637,8 @@ ;; we want to make sure that various bits of the interface are hidden. but this isn't an "all gui chrome is useless" rampage. i personally think the scrollbar is useful, i like the visual indication it gives of how far i am through a file. (setopt - menu-bar-mode nil - tool-bar-mode nil + ;; menu-bar-mode nil + ;; tool-bar-mode nil tooltip-mode nil ;; show tooltips in the echo area echo-keystrokes 0.1) ;; show chord progress (almost) immediately @@ -307,17 +658,31 @@ ;; context-menu-mode t) (use-package mouse :custom - (context-menu-mode t)) - - + (context-menu-mode nil)) + +(defun noa/helpline () + (concat + "[C-x C-f] Open \t" + "[M-w] copy \t" + "[C-w] Cut \t" + "[C-s] search \t" + "[C-x C-s] Save \t" + "[C-y] Paste \t" + "[C-/] Undo \t" + "[M-x] Command \t" + ) +) (use-package tab-bar - :disabled :custom (tab-bar-mode t) - (tab-bar-format '(tab-bar-format-align-right tab-bar-format-global tab-bar-format-menu-bar)) - - ) + (tab-bar-format '( + ;; noa/helpline + tab-bar-format-menu-bar + tab-bar-format-align-right + tab-bar-format-global + )) +) (use-package font-lock @@ -326,8 +691,6 @@ (font-lock-maximum-decoration nil)) (setopt - display-time-mode t - display-battery-mode t inhibit-startup-screen t mouse-drag-and-drop-region nil mouse-yank-at-point t @@ -359,19 +722,35 @@ auto-save-default t auto-save-no-message t version-control t + ;; this will auto save to the current file auto-save-visited-mode t) (add-hook 'focus-out-hook (lambda () (interactive) (save-some-buffers t))) (add-hook 'mouse-leave-buffer-hook (lambda () (interactive) (save-some-buffers t))) +(use-package keyfreq + :ensure t + :custom + (keyfreq-mode t) + (keyfreq-autosave-mode t) +) + ;;; scrolling (setopt mouse-wheel-scroll-amount '(1 ((shift) . 1)) mouse-wheel-progressive-speed nil mouse-wheel-follow-mouse 't - scroll-step 1 + scroll-step 0 pixel-scroll-precision-mode t pixel-scroll-precision-use-momentum t) +(use-package smartscan + :bind ( + ("M-n" . smartscan-symbol-go-forward) + ("M-p" . smartscan-symbol-go-backward) + ("<down-mouse-3>" . nil) + ("<mouse-3>" . smartscan-symbol-go-forward) + ) +) ;;; sentences (setopt sentence-end-double-space nil) @@ -437,6 +816,11 @@ (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) :custom + (display-battery-mode t) + (display-time-mode t) + (display-time-default-load-average nil) + (display-time-24hr-format t) + ;; Support opening new minibuffers from inside existing minibuffers. (enable-recursive-minibuffers t) (debug-on-error t) @@ -495,8 +879,10 @@ frame-inhibit-implied-resize t ediff-window-setup-function 'ediff-setup-windows-plain) -(load "server") -(unless (server-running-p) (server-start)) +(use-package server + :disabled + :config + (unless (server-running-p) (server-start))) (setopt help-at-pt-display-when-idle t) @@ -552,20 +938,23 @@ (mu4e-confirm-quit nil) (mu4e-headers-leave-behavior 'apply) (mu4e-headers-precise-alignment t) + (mu4e-headers-fields '( + ;; (:human-date . 12) + (:flags . 6) + (:from . 32) + (:subject))) (mu4e-search-threads nil) (mu4e-hide-index-messages t) (mu4e-get-mail-command "mbsync -c ~/.config/mbsyncrc fastmail") - (mu4e-maildir "~/data/mail") + (mu4e-maildir "~/mail") (mu4e-drafts-folder "/Drafts") (mu4e-sent-folder "/Sent") (mu4e-refile-folder "/Archive") (mu4e-trash-folder "/Trash") (mu4e-bookmarks '( - (:name "Waiting room" :query "\"maildir:/Waiting room\"" :key ?w) (:name "Inbox" :query "maildir:/Inbox" :key ?i) (:name "Feeds" :query "maildir:/Feeds" :key ?f) (:name "Paper trail" :query "\"maildir:/Paper trail\"" :key ?p))) - :bind ( :map mu4e-headers-mode-map ("d" . my-move-to-trash) @@ -574,9 +963,29 @@ ) :init (fset 'my-move-to-trash "mTrash") ;; function to move mails to trash - :custom-face + :config + (setq + mu4e-headers-thread-connection-prefix '("│ " . "│ ") + mu4e-headers-thread-last-child-prefix '("└ " . "└ ") + mu4e-headers-thread-blank-prefix '(" " . " ") + mu4e-headers-thread-root-prefix '("□ " . "□ ") + mu4e-headers-thread-child-prefix '("│ " . "│ ") + mu4e-headers-thread-orphan-prefix '("♢ " . "♢ ") + mu4e-headers-thread-duplicate-prefix '("≡ " . "≡ ") + mu4e-headers-thread-first-child-prefix '("⚬ " . "⚬ ") + mu4e-headers-thread-single-orphan-prefix '("♢ " . "♢ ") + ) ) +(use-package message + :custom + (message-fill-column nil) + ;; (message-signature-file) + (message-signature "~noa (https://noa.pub) + • I try to reply to formal emails in three sentences or fewer; excuse my brevity. + • I queue replies and batch send them at intervals; excuse my untimeliness.") +) + (use-package button :custom-face ;;(button ((t (:underline t :foreground unspecified)))) @@ -600,9 +1009,72 @@ ) (use-package valign :ensure t - :after markdown-mode - :hook (markdown-mode . valign-mode)) + :hook (markdown-mode . valign-mode) + :hook (org-mode . valign-mode) + :custom + (valign-fancy-bar t) + (valign-max-table-size 0)) +(use-package eat + :ensure t + :hook ( + (eshell-load . eat-eshell-mode) + (eshell-load . eat-eshell-visual-command-mode) + ) +) + + + +(use-package which-key + :ensure t + :custom + (which-key-popup-type 'side-window) + (which-key-side-window-location 'bottom) + (which-key-side-window-max-height 3) + (which-key-persistent-popup nil) + (which-key-show-prefix 'echo) + (which-key-idle-delay 0) + (which-key-mode t) +) + +(use-package fixed-pitch + :custom + (fixed-pitch-dont-change-cursor t) + (fixed-pitch-blacklist-hooks '( + prog-mode-hook + comint-mode-hook + )) + (fixed-pitch-whitelist-hooks '( + calendar-mode-hook + dired-mode-hook + magit-mode-hook + profiler-report-mode-hook + which-key-init-buffer-hook + jabber-roster-mode-hook + mu4e-headers-mode-hook + )) +) +(custom-set-faces + '(fixed-pitch ((t (:family "Go Mono" :height 110)))) + '(variable-pitch ((t (:family "SN Pro" :height 110)))) +) +;; For some frustrating reason, emacs does not respect fontconfig font settings. What this means in practice is that emacs by default draws cjk characters with the korean variant. Luckily emacs has its own obscure and poorly documented way of doing things, so i can iterate over the relevant charsets and set the font specifically for those characters. +(dolist ( + charset '( + han + ;; symbol + cjk-misc)) + (set-fontset-font t charset ( + font-spec :family "Noto Sans CJK SC" + )) +) + +;; Describe a key based on a string like "C-SPC" +(defun describe-key-shortcut (shortcut) + (interactive "MShortcut: ") + (describe-key (kbd shortcut))) + +(setopt calendar-week-start-day 1) ;;; TOUCHSCREEN ;; this should be obsolete in emacs 30 @@ -684,3 +1156,5 @@ (global-set-key [touchscreen-begin] #'touchscreen-handle-touch-begin) (global-set-key [touchscreen-update] #'touchscreen-handle-touch-update) (global-set-key [touchscreen-end] #'touchscreen-handle-touch-end) + + |