;;; 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") ;; properly distinguish these chords from their ascii legacy (define-key input-decode-map [?\C-i] [C-i]) (define-key input-decode-map [?\C-m] [C-m]) (use-package tubthumping-theme :config (load-theme 'tubthumping t)) (global-set-key (kbd "") 'backward-delete-char-untabify) ;; I make my caps lock a menu key, so i can open the command palette with it ;; https://alexschroeder.ch/wiki/2020-07-16_Emacs_everything (define-key context-menu-mode-map (kbd "") nil) (global-unset-key (kbd "M-x")) (global-set-key (kbd "") 'execute-extended-command) (setopt cursor-type 'bar minibuffer-depth-indicate-mode t) (setopt url-cookie-trusted-urls '() url-cookie-untrusted-urls '(".*")) ;;; candidate completion (use-package vertico :ensure t :custom (vertico-mode t) (vertico-count 12) (vertico-cycle t) (read-buffer-completion-ignore-case t) (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) ("" . vertico-directory-delete-char) ("C-" . vertico-directory-delete-word) ) ;; Tidy shadowed file names :hook (rfn-eshadow-update-overlay . vertico-directory-tidy) ) (use-package orderless :ensure t :custom (completion-styles '(orderless basic)) (completion-category-defaults nil) (completion-category-overrides '((file (styles partial-completion))))) (setopt confirm-nonexistent-file-or-buffer 'after-completion) (defun noa/mode-line-modified () (cond (buffer-read-only "RO") ((buffer-modified-p) "**") (t "RW"))) ;;; Replace the mode line with a header line ;; First, we set the mode line to nil. On my graphical display, this collapses it so all i get is a thin black line separating the buffer from the echo area. (setq-default mode-line-format nil) ;; But the mode line still holds some useful information that i want to see. I would rather that be in the header line, because to me it makes sense for this kind of metadata to be /above/ the buffer it is describing. (setq-default header-line-format '( ;; First, in white on black text, i want the information about the state of the file. This will show three hyphens in the top left corner of the header line. The first two hyphens mean that the file is both writable and unchanged. If the buffer has been changed, they will change to two asterisks. If the buffer is read only, they will change two percentage symbols. And if the buffer is read only and has been changed, the first will change to a percentage symbol, and the second will change to an asterisk. The final hyphen represents that the file is local, specifically that the default-directory variable is local. If it is remote, an at symbol will be displayed instead. (:propertize ("" mode-line-modified mode-line-remote) face highlight ) ;; Next, we want to display the buffer name. For buffers which belong to files, this will usually be the file name, but it is likely to be something more informative for special buffers. " %b" ;; Below that, show a line and column coördinate. There are special minor modes that will enable or disable this for the default mode line, but i ignore that and put the formatting code here directly. The docstring for the mode-line-format variable suggests that the column might not be displayed correctly in some situations without enabling the minor mode, but i haven't noticed that yet so i don't bother. This column number is zero-indexed; a capital c would make it one-indexed. For now i stick with zero-indexed as that's the emacs default and i'm not sure which is better. I guess it makes a bit more sense that the first character on a line is labeled "1". ":%l,%c" " " ;; I don't know exactly what this variable covers, so i keep it here so that if something shows up i know that it gets put here. Because i have a global mode line in my tab bar, some of the things that would otherwise be here (like the time, battery percentage, and notifications for chat buffers) don't show up. mode-line-misc-info mode-line-end-spaces ) ) (use-package nano-modeline :disabled t :ensure t :hook ( (prog-mode . nano-modeline-prog-mode) (text-mode . nano-modeline-text-mode) (org-mode . nano-modeline-org-mode) (pdf-view-mode . nano-modeline-pdf-mode) (mu4e-headers-mode . nano-modeline-mu4e-headers-mode) (mu4e-view-mode . nano-modeline-mu4e-message-mode) (term-mode . nano-modeline-term-mode) (xwidget-webkit-mode . nano-modeline-xwidget-mode) (messages-buffer-mode . nano-modeline-message-mode) (org-capture-mode . nano-modeline-org-capture-mode) (org-agenda-mode . nano-modeline-org-agenda-mode) ) :config (setq-default mode-line-format nil) ) (use-package beacon :ensure t :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)) (use-package shr :custom (shr-max-width nil)) ;; also check out jinx https://github.com/minad/jinx (use-package spell-fu :ensure t :hook (text-mode . spell-fu-mode)) (use-package minimap :ensure t :custom (minimap-width-fraction 0.1) (minimap-minimum-width 10) (minimap-window-location 'right) (minimap-update-delay 0.15)) ;; consult-buffer replaces the buffer menu. as well as listing buffers, it lists bookmarks and recent files. (use-package consult :ensure t :bind ( ([remap switch-to-buffer] . consult-buffer) ([remap yank-pop] . consult-yank-pop) ([remap goto-line] . consult-goto-line)) :custom (completion-in-region-function 'consult-completion-in-region) ) (use-package marginalia :ensure t :after vertico :custom (marginalia-mode t) (marginalia-max-relative-age most-positive-fixnum) (marginalia-align 'right)) (global-set-key (kbd "") 'completion-at-point) ;; Add extensions (use-package cape :ensure t :bind ("C-c p f" . cape-file) ("C-c p :" . cape-emoji) :init (add-hook 'completion-at-point-functions #'cape-file) (add-hook 'completion-at-point-functions #'cape-dabbrev) (add-hook 'completion-at-point-functions #'cape-elisp-block) ;;(add-hook 'completion-at-point-functions #'cape-history) ;;(add-hook 'completion-at-point-functions #'cape-keyword) ;;(add-hook 'completion-at-point-functions #'cape-tex) ;;(add-hook 'completion-at-point-functions #'cape-sgml) ;;(add-hook 'completion-at-point-functions #'cape-rfc1345) ;;(add-hook 'completion-at-point-functions #'cape-abbrev) ;;(add-hook 'completion-at-point-functions #'cape-dict) ;;(add-hook 'completion-at-point-functions #'cape-elisp-symbol) ;;(add-hook 'completion-at-point-functions #'cape-line) ) ;; (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 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-pretty-entities t) (org-use-sub-superscripts "{}") (org-hide-emphasis-markers t) (org-startup-with-inline-images t) (org-image-actual-width '(300)) (org-auto-align-tags nil) (org-tags-column 0) (org-catch-invisible-edits 'show-and-error) (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 ?─) (org-agenda-time-grid '((daily today require-timed) (800 1000 1200 1400 1600 1800 2000) " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄")) (org-agenda-current-time-string "◀── now ─────────────────────────────────────────────────") :custom-face ;;(org-ellipsis ((t (:inherit default :box nil)))) ) (use-package org-appear :disabled t :ensure t :after org :hook (org-mode . org-appear-mode)) (use-package org-modern :ensure t :after org :hook (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 "𰻝\">" :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 " " :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 ( (text-mode . olivetti-mode) (eww-after-render . olivetti-mode) (nov-post-html-render . olivetti-mode) (mu4e-view-mode . olivetti-mode) ) ) (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 :custom (global-eldoc-mode t)) (use-package rainbow-mode :ensure t :delight :hook (prog-mode . rainbow-mode)) ;; 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) (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 ;; my rules for inserting tabs are that the tab key should insert tabs. i personally prefer tabs. spaces don't work for me because then people go trigger happy with alignment, which doesn't really work with proportional fonts. ;; todo: elastic tabstops ;; https://nickgravgaard.com/elastic-tabstops/ ;; https://github.com/tenbillionwords/spaceship-mode ;; always insert \t for tab. (setopt electric-indent-mode nil backward-delete-char-untabify-method nil tab-width 8) ;; do a naive duplicate whitespace on return, vi/nano style. (define-key global-map (kbd "TAB") 'self-insert-command) (defun naive-return-and-indent () "insert a newline and copy the indentation of the previous line" (interactive) (open-line 1) (let* ((start (progn (beginning-of-line) (point))) (indent (progn (back-to-indentation) (point))) (end (progn (end-of-line) (point))) (whitespace (buffer-substring start indent))) (delete-trailing-whitespace start end) (beginning-of-line 2) (insert whitespace))) (define-key global-map (kbd "RET") 'naive-return-and-indent) ;;; interface ;; 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 tooltip-mode nil ;; show tooltips in the echo area echo-keystrokes 0.1) ;; show chord progress (almost) immediately (use-package paren :custom (show-paren-mode t) ) ;; populate and enable the context menu ;; (setopt context-menu-functions '( ;; context-menu-ffap ;; occur-context-menu ;; context-menu-region ;; context-menu-undo ;; goto-address-context-menu) ;; context-menu-mode t) (use-package mouse :custom (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 :custom (tab-bar-mode t) (tab-bar-format '( ;; noa/helpline tab-bar-format-menu-bar tab-bar-format-align-right tab-bar-format-global )) ) (use-package font-lock :custom (global-font-lock-mode t) (font-lock-maximum-decoration nil)) (setopt inhibit-startup-screen t mouse-drag-and-drop-region nil mouse-yank-at-point t delete-selection-mode nil ;; deleting should be an explicit action ) (global-set-key (kbd "C-t") 'tab-new) ;; shift click to select region with the mouse. This annoyingly rings the bell for an error (global-unset-key (kbd "S-")) (global-set-key (kbd "S-") 'mouse-save-then-kill) ;;; packages (setopt package-archives '( ("gnu" . "https://elpa.gnu.org/packages/") ("nongnu" . "https://elpa.nongnu.org/nongnu/") ("melpa-stable" . "https://stable.melpa.org/packages/") ("melpa" . "https://melpa.org/packages/"))) ;;; saving ;; backups are pointless in long emacs sessions imo, but autosaves are useful (setopt make-backup-files nil backup-by-copying t create-lockfiles nil auto-save-mode 1 auto-save-interval 20 ;; every twenty keystrokes auto-save-timeout 5 ;; every 5 seconds 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 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) ("" . nil) ("" . smartscan-symbol-go-forward) ) ) ;;; sentences (setopt sentence-end-double-space nil) ;;; spellcheck (setopt flyspell-mode t ispell-program-name "aspell" ispell-dictionary "en_GB" ispell-extra-args '("--sug-mode=ultra") require-final-newline t window-min-height 1 window-combination-resize t window-resize-pixelwise t frame-resize-pixelwise t) ;;; history (setopt history-length 250 kill-ring-max 25) (use-package savehist :custom (savehist-file "~/.config/emacs/savehist") (savehist-additional-variables '( kill-ring command-history set-variable-value-history custom-variable-history query-replace-history read-expression-history minibuffer-history read-char-history face-name-history bookmark-history file-name-history)) (savehist-mode t)) (use-package frame :custom (window-divider-mode t) (window-divider-default-right-width 1) (window-divider-default-bottom-width 1) (window-divider-default-places t) ) (use-package emacs :init ;; Add prompt indicator to `completing-read-multiple'. ;; We display [CRM], e.g., [CRM,] if the separator is a comma. (defun crm-indicator (args) (cons (format "[CRM%s] %s" (replace-regexp-in-string "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" crm-separator) (car args)) (cdr args))) (advice-add #'completing-read-multiple :filter-args #'crm-indicator) ;; Do not allow the cursor in the minibuffer prompt (setq minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt)) (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) ;; Hide commands in M-x which do not work in the current mode. (read-extended-command-predicate 'command-completion-default-include-p)) (setopt recentf-max-menu-items 25 recentf-save-file "~/.config/emacs/recentf" recentf-mode 1 bookmark-default-file "~/.config/emacs/bookmarks") ;;; miscellaneous (setopt save-place-mode 1) (use-package goto-addr :custom (global-goto-address-mode t) (goto-address-mail-face 'link) (goto-address-mail-mouse-face 'link) (goto-address-url-face 'link) (goto-address-url-mouse-face 'link) ) (setenv "PAGER" "cat") (setenv "TERM" "dumb") (setenv "GPG_AGENT_INFO" nil) (defalias 'yes-or-no-p 'y-or-n-p) (setq disabled-command-function nil) (setopt custom-file (make-temp-file "custom")) (setq inhibit-startup-echo-area-message "noa") ;; #userfreedom (use-package simple :delight visual-line-mode :custom (global-visual-line-mode t)) (setopt kill-whole-line t uniquify-after-kill-buffer-p t uniquify-buffer-name-style 'forward uniquify-ignore-buffers-re "^\\*" uniquify-separator "/") (setopt save-interprogram-paste-before-kill t apropos-do-all t mouse-yank-at-point t require-final-newline t visible-bell t load-prefer-newer t frame-inhibit-implied-resize t ediff-window-setup-function 'ediff-setup-windows-plain) (use-package server :disabled :config (unless (server-running-p) (server-start))) (setopt help-at-pt-display-when-idle t) (use-package ctrlf :ensure t :bind ( ([remap isearch-forward] . ctrlf-forward-default) ([remap isearch-backward] . ctrlf-backward-default) ([remap isearch-forward-regexp] . ctrlf-forward-alternate) ([remap isearch-backward-regexp] . ctrlf-backward-alternate) ([remap isearch-forward-symbol] . ctrlf-forward-symbol) ([remap isearch-forward-symbol-at-point] . ctrlf-forward-symbol-at-point) ) :custom (ctrlf-go-to-end-of-match nil "It makes more sense to go to the start of the match, because i start searching where i want to be.") ) (global-set-key (kbd "M-o") 'other-window) (global-set-key (kbd "C-x k") 'kill-this-buffer) ;; undo C-/ ;; redo C-S-/ (setopt undo-no-redo t) ;; mu4e (use-package mu4e :custom (mu4e-headers-skip-duplicates t) (mu4e-view-show-images t) (mu4e-view-show-addresses t) (mu4e-compose-format-flowed nil) (mu4e-change-filenames-when-moving t) (mu4e-use-fancy-chars nil) (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 "~/mail") (mu4e-drafts-folder "/Drafts") (mu4e-sent-folder "/Sent") (mu4e-refile-folder "/Archive") (mu4e-trash-folder "/Trash") (mu4e-bookmarks '( (: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) :map mu4e-view-mode-map ("d" . my-move-to-trash) ) :init (fset 'my-move-to-trash "mTrash") ;; function to move mails to trash :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)))) ) (use-package faces :custom-face ;; (link ((t (:underline t :foreground unspecified)))) ;; (fringe ((t (:background unspecified)))) ;; (mode-line ((t (:background unspecified :box 2)))) ;; (mode-line-active ((t (:inverse-video t)))) ;; (mode-line-inactive ((t (:background unspecified :box 2 :weight unspecified)))) ;; (help-key-binding ((t (:inherit default :background unspecified :foreground unspecified :box 1)))) ) (global-set-key (kbd "M-z") 'zap-up-to-char) (use-package markdown-mode :ensure t :mode ("\\.md\\'" . markdown-mode) ) (use-package valign :ensure t :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 ;; Copyright 2024-present Naheel Azawy. All rights reserved. (defvar touchscreen-last-time) (defvar touchscreen-last-pos-pixel) (defvar touchscreen-last-dist 0) (defvar touchscreen-begin-char) (defun touchscreen-time () "Time in seconds." (time-convert (current-time) 'integer)) (defun touchscreen-handle-touch-begin (input) "Handle touch begining at input INPUT." (interactive "e") (let* ((event (nth 1 input)) (pos-pixel (nth 3 event)) (pos-char (nth 6 event)) (win (nth 1 event))) ;; (message (format "%s" input)) (if (not (equal (selected-window) win)) ;; switch window (select-window win)) ;; set globals (setq touchscreen-last-time (touchscreen-time)) (setq touchscreen-last-pos-pixel pos-pixel) (setq touchscreen-begin-char pos-char) )) (defun touchscreen-handle-touch-update (input) "Handle touch update at input INPUT." (interactive "e") (let* ((event (nth 0 (nth 1 input))) (pos-pixel (nth 3 event)) (pos-char (nth 6 event)) (diff-time (- (touchscreen-time) touchscreen-last-time)) (diff-pixel (- (cdr touchscreen-last-pos-pixel) (cdr pos-pixel))) (diff-char (abs (- touchscreen-begin-char pos-char)))) (if (= (length (nth 1 input)) 2) ;; pinch zoom (let* ((event2 (nth 1 (nth 1 input))) (pos-pixel2 (nth 3 event2)) (dist (sqrt (+ (expt (- (car pos-pixel2) (car pos-pixel)) 2) (expt (- (cdr pos-pixel2) (cdr pos-pixel)) 2)))) (dist-diff (- dist touchscreen-last-dist))) (setq touchscreen-last-dist dist) (if (> dist-diff 0) (text-scale-increase 0.1) (if (< dist-diff 0) (text-scale-decrease 0.1))) ) (if (> diff-time 1) ;; TODO: set marker on long press (goto-char pos-char)) (if (> diff-char 1) ;; scroll (progn (move-to-window-line nil) (if (> diff-pixel 0) (pixel-scroll-pixel-up diff-pixel) (if (< diff-pixel 0) (pixel-scroll-pixel-down (* -1 diff-pixel)))) (setq touchscreen-last-time (touchscreen-time)) (setq touchscreen-last-pos-pixel pos-pixel)) )))) (defun touchscreen-handle-touch-end (input) "Handle touch end at input INPUT." (interactive "e") (let* ((event (nth 1 input)) (pos-char (nth 6 event))) (if (= touchscreen-begin-char pos-char) ;; move cursor (goto-char pos-char)))) (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)