summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--emacs/init.el686
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)