diff options
author | noa@gaiwan.org | 2024-05-30 19:37:58 +0000 |
---|---|---|
committer | noa@gaiwan.org | 2024-05-30 19:37:58 +0000 |
commit | ca81769a4f54ad04439524d0a27cc3a380115d53 (patch) | |
tree | 95cec65b4f38479f355804004acbeb7c5e859f34 /emacs | |
parent | ee8b03b63ef05315afddf977d2bb08d24be000af (diff) |
Add emacs configuration
Diffstat (limited to 'emacs')
-rw-r--r-- | emacs/init.el | 686 |
1 files changed, 686 insertions, 0 deletions
diff --git a/emacs/init.el b/emacs/init.el new file mode 100644 index 0000000..fa52557 --- /dev/null +++ b/emacs/init.el @@ -0,0 +1,686 @@ +(add-to-list 'load-path (expand-file-name (concat user-emacs-directory "site-lisp"))) + +(setq garbage-collection-messages t) + +(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 "<backspace>") '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 "<menu>") nil) +(global-unset-key (kbd "M-x")) +(global-set-key (kbd "<menu>") 'execute-extended-command) + +(setopt + cursor-type 'bar + minibuffer-depth-indicate-mode t) + +(setopt + browse-url-browser-function 'eww-browse-url + 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 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) + +(use-package nano-modeline + :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 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 + :custom + (marginalia-mode t) + (marginalia-max-relative-age most-positive-fixnum) + (marginalia-align 'right)) + + +(global-set-key (kbd "<C-i>") '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-roam + :ensure t + :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) + :config + (require 'org-roam-protocol) +) + +(use-package org + :custom + (org-startup-indented t) + (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-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 + :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-keyword nil) + (org-modern-checkbox nil) + (org-modern-table nil)) + +(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 delight + :ensure t) + +(use-package eldoc + :delight + :custom + (global-eldoc-mode t)) + +(use-package rainbow-mode + :ensure t + :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") + +;;; 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 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)) + + ) + + +(use-package font-lock + :custom + (global-font-lock-mode t) + (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 + 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-<down-mouse-1>")) +(global-set-key (kbd "S-<down-mouse-1>") '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 + 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))) + +;;; scrolling +(setopt + mouse-wheel-scroll-amount '(1 ((shift) . 1)) + mouse-wheel-progressive-speed nil + mouse-wheel-follow-mouse 't + scroll-step 1 + pixel-scroll-precision-mode t + pixel-scroll-precision-use-momentum t) + + +;;; 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<separator>], 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 + ;; 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) + +(load "server") +(unless (server-running-p) (server-start)) + +(setopt help-at-pt-display-when-idle t) + +;;; search +;; search without regard for unicode characters +(setopt search-default-mode 'char-fold-to-regexp + replace-char-fold t + search-highlight t + isearch-lazy-highlight t) +;; when exiting isearch after a match, put the cursor at the beginning of the search rather than the end +(setq isearch-mode-end-hook '(goto-beginning-of-search)) +(defun goto-beginning-of-search () + "positions the point at the beginning of the isearch match" + (when (and (not isearch-mode-end-hook-quit) + isearch-forward) + (goto-char isearch-other-end))) +;; wrap searches +(setopt isearch-wrap-pause 'no-ding) +;; jump straight to previous match when change search direction +(setopt isearch-repeat-on-direction-change 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-search-threads nil) + (mu4e-hide-index-messages t) + (mu4e-get-mail-command "mbsync -c ~/.config/mbsyncrc fastmail") + (mu4e-maildir "~/data/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) + :map mu4e-view-mode-map + ("d" . my-move-to-trash) + ) + :init + (fset 'my-move-to-trash "mTrash") ;; function to move mails to trash + :custom-face + ) + +(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 + :after markdown-mode + :hook (markdown-mode . valign-mode)) + + +;;; 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) |