(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 "") '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 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 "") '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 ("" . 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-")) (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 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], 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)