;;; init.el --- An Emacs of one's own -*- lexical-binding: t -*- ;; Bankruptcy: 9.4 ;;; Code: (load (locate-user-emacs-file "basics")) ; super basic stuff ;;; Built-ins (use-package emacs ; Misc. config :custom-face (fixed-pitch ((t :family ,(find-font "Comic Code" "DejaVu Sans Mono") :height 100))) (variable-pitch ((t :family ,(find-font "Atkinson Hyperlegible" "DejaVu Serif") :height 1.4))) (default ((t :family ,(find-font "Comic Code" "DejaVu Sans Mono") :height 100))) (font-lock-comment-face ((t :slant italic))) :config (setopt tab-bar-show 1 recenter-positions '(top middle bottom)) (keymap-global-unset "C-\\")) (use-package faces :defer 2 ; This is sort of expensive :config ;; Emojis (cl-loop with ffl = (font-family-list) for font in '("Noto Emoji" "Noto Color Emoji" "Segoe UI Emoji" "Apple Color Emoji" "FreeSans" "FreeMono" "FreeSerif" "Unifont" "Symbola") if (member font ffl) do (set-fontset-font t 'symbol font)) ;; International scripts (cl-loop with ffl = (font-family-list) for (charset . font) in '((latin . "Noto Sans") (han . "Noto Sans CJK SC Regular") (kana . "Noto Sans CJK JP Regular") (hangul . "Noto Sans CJK KR Regular") (cjk-misc . "Noto Sans CJK KR Regular") (khmer . "Noto Sans Khmer") (lao . "Noto Sans Lao") (burmese . "Noto Sans Myanmar") (thai . "Noto Sans Thai") (ethiopic . "Noto Sans Ethiopic") (hebrew . "Noto Sans Hebrew") (arabic . "Noto Sans Arabic") (gujarati . "Noto Sans Gujarati") (devanagari . "Noto Sans Devanagari") (kannada . "Noto Sans Kannada") (malayalam . "Noto Sans Malayalam") (oriya . "Noto Sans Oriya") (sinhala . "Noto Sans Sinhala") (tamil . "Noto Sans Tamil") (telugu . "Noto Sans Telugu") (tibetan . "Noto Sans Tibetan")) if (member font ffl) do (set-fontset-font t charset font))) (use-package text-mode :config (add-hook 'text-mode-hook #'abbrev-mode)) (use-package prog-mode :config (add-hook 'prog-mode-hook #'auto-fill-mode) (add-hook 'prog-mode-hook (defun prog@indent-tabs-maybe () (indent-tabs-mode (if (derived-mode-p 'emacs-lisp-mode 'python-mode 'haskell-mode) -1 1))))) (use-package eshell :preface ;; TODO: Break this out into its own package (eshell-pop?). This may not work ;; the way I want it to sometimes .. but then, I don't know how I want it to ;; work sometimes either. More testing, clearly, is needed. (defvar eshell-buffer-format "*eshell:%s*" "Format for eshell buffer names.") (defun eshell-rename-pwd () (rename-buffer (format eshell-buffer-format default-directory) t)) (defun eshell-last-dir () (goto-char (point-max)) (insert "cd -") (eshell-send-input)) (defun eshellp (buffer-or-name) (with-current-buffer buffer-or-name (derived-mode-p 'eshell-mode))) (defun +eshell (&optional new) (interactive "P") (let ((dir default-directory) (bname (format eshell-buffer-format default-directory)) (display-comint-buffer-action 'pop-to-buffer)) (if-let ((buf (and (not new) (or (get-buffer bname) (seq-find #'eshellp (reverse (buffer-list))))))) (pop-to-buffer buf) (eshell new)) (eshell-rename-pwd) (unless (equal default-directory dir) (goto-char (point-max)) (insert (format "cd %s" dir)) (eshell-send-input)))) (defun +eshell-quit (&optional choose) (interactive "P") (if choose (let* ((bufs (mapcar #'buffer-name (seq-filter #'eshellp (buffer-list)))) (buf (get-buffer (completing-read "Eshell: " bufs nil t nil nil (car bufs))))) (quit-window) (pop-to-buffer buf)) (quit-window))) :init (add-hook 'eshell-post-command-hook #'eshell-rename-pwd) :commands eshell :bind (("C-z" . +eshell) :map eshell-mode-map ("C-z" . +eshell-quit) ("C-o" . eshell-last-dir)) :config (add-hook 'eshell-mode-hook (defun eshell-setup () (setq-local imenu-generic-expression '(("Prompt" " $ \\(.*\\)" 1)))))) (use-package auth-source :config (setopt auth-sources '(default "secrets:passwords")) (add-hook 'auth-info-hook #'truncate-lines-local-mode)) (use-package fringe :config (fringe-mode '(nil . 0))) (use-package flyspell :hook org-mode-hook) (use-package browse-url :preface (defcustom +browse-url-other-safe-browser-functions nil "Other safe browser functions." :type '(repeat function)) (defun +browse-url-browser-function-safe-p (f) "Return t if F is a safe browser function." (memq f (append +browse-url-other-safe-browser-functions (mapcar (lambda (i) (plist-get (cdr i) :value)) (seq-filter (lambda (i) (eq (car i) 'function-item)) (cdr (get 'browse-url-browser-function 'custom-type))))))) :config (put 'browse-url-browser-function 'safe-local-variable '+browse-url-browser-function-safe-p)) (use-package dired :bind (("C-x C-j" . dired-jump) ([remap list-directory] . dired) :map dired-mode-map ("C-j" . dired-up-directory) ("" . dired-up-directory)) :config (require 'dired-x) (setopt dired-recursive-copies 'always dired-recursive-deletes 'always dired-create-destination-dirs 'always dired-do-revert-buffer t dired-hide-details-hide-symlink-targets nil dired-isearch-filenames 'dwim delete-by-moving-to-trash t dired-auto-revert-buffer t dired-listing-switches "-AlFhv --group-directories-first" ls-lisp-dirs-first t dired-ls-F-marks-symlinks t dired-clean-confirm-killing-deleted-buffers nil dired-no-confirm '(byte-compile load chgrp chmod chown copy move hardlink symlink shell touch) dired-dwim-target t) (add-hook 'dired-mode-hook #'dired-hide-details-mode) (add-hook 'dired-mode-hook #'hl-line-mode) (add-hook 'dired-mode-hook #'truncate-lines-local-mode)) ;;; Locally-developed packages (use-package dawn :load-path "~/src/emacs/dawn/" :after custom-allowed :config (add-hook 'custom-allowed-after-load-hook (defun dawn-modus () (dawn-schedule-themes 'modus-operandi 'modus-vivendi)))) (use-package electric-cursor :load-path "~/src/emacs/electric-cursor/" :config (setopt electric-cursor-alist '((overwrite-mode . box) (t . bar))) (electric-cursor-mode)) (use-package mode-line-bell :load-path "~/src/emacs/mode-line-bell/" :config (setopt mode-line-bell-flash-time 0.25) (mode-line-bell-mode)) (use-package titlecase :load-path "~/src/emacs/titlecase.el/" :preface (defun +titlecase-sentence-style-dwim (&optional arg) "Titlecase a sentence. With prefix ARG, toggle the value of `titlecase-downcase-sentences' before sentence-casing." (interactive "P") (let ((titlecase-downcase-sentences (if arg (not titlecase-downcase-sentences) titlecase-downcase-sentences))) (titlecase-dwim 'sentence))) (defun +titlecase-org-headings () (interactive) (require 'org) (save-excursion (goto-char (point-min)) ;; See also `org-map-tree'. I'm not using that function because I want to ;; skip the first headline. A better solution would be to patch ;; `titlecase-line' to ignore org-mode metadata (TODO cookies, tags, etc). (let ((level (funcall outline-level)) (org-special-ctrl-a/e t)) (while (and (progn (outline-next-heading) (> (funcall outline-level) level)) (not (eobp))) (titlecase-region (progn (org-beginning-of-line) (point)) (progn (org-end-of-line) (point))))))) :bind (:map scule-map ("M-t" . titlecase-dwim))) (use-package scule :load-path "~/src/emacs/scule/" :config (defvar-keymap scule-map :doc "Keymap to twiddle scules." :repeat t ; TODO: doesn't work "M-u" #'scule-upcase "M-l" #'scule-downcase "M-c" #'scule-capitalize) (keymap-global-set "M-c" scule-map) ;; Use M-u for prefix keys (keymap-global-set "M-u" #'universal-argument) (keymap-set universal-argument-map "M-u" #'universal-argument-more)) (use-package filldent :load-path "~/src/emacs/filldent/" :bind ("M-q" . filldent-dwim)) (use-package frowny :load-path "~/src/emacs/frowny/" :config (global-frowny-mode)) (use-package jabber :load-path "~/src/emacs/emacs-jabber/" :preface (defvar jabber-prefix-width 10 "Width of jabber prompts and other prefixes.") (defun jabber-ui-setup () "Setup the `jabber' user interface." (visual-fill-column-mode) (electric-pair-local-mode -1) (auto-fill-mode -1) (setq-local wrap-prefix (make-string (+ 3 jabber-prefix-width) #x20) visual-fill-column-extra-text-width `(,(+ 3 jabber-prefix-width) . 1))) :custom-face (jabber-activity-face ((t :inherit jabber-chat-prompt-foreign :foreground unspecified :weight normal))) (jabber-activity-personal-face ((t :inherit font-lock-warning-face :foreground unspecified :weight bold))) (jabber-chat-prompt-local ((t :inherit font-lock-warning-face :foreground unspecified))) (jabber-chat-prompt-foreign ((t :inherit font-lock-constant-face :foreground unspecified))) (jabber-chat-prompt-system ((t :inherit font-lock-doc-face :foreground unspecified))) (jabber-rare-time-face ((t :inherit font-lock-comment-face :foreground unspecified :underline nil))) :bind-keymap ("C-c j" . jabber-global-keymap) :bind (("C-c C-SPC" . jabber-activity-switch-to)) :config (setopt jabber-account-list '(("acdw@hmm.st")) jabber-auto-reconnect t jabber-last-read-marker (make-string (- fill-column 8) ?—) jabber-muc-decorate-presence-patterns '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$" . nil) ("Mode #.*" . jabber-muc-presence-dim) ("." . jabber-muc-presence-dim)) jabber-activity-make-strings #'jabber-activity-make-strings-shorten jabber-last-read-marker (make-string fill-column ?-) jabber-rare-time-format " - - - - - - %H:%M %F" ;; buffer name formats jabber-chat-buffer-format "%n " jabber-browse-buffer-format "%n " jabber-groupchat-buffer-format "%n " jabber-muc-private-buffer-format "%n " ;; "prompt" (speaker) formats jabber-groupchat-prompt-format (format "%%>%dn | " jabber-prefix-width) jabber-chat-local-prompt-format (format "%%>%dn | " jabber-prefix-width) jabber-chat-system-prompt-format (let ((s "")) (while (< (length s) jabber-prefix-width) (setq s (concat s " *"))) s) jabber-chat-foreign-prompt-format (format "%%>%dn | " jabber-prefix-width) jabber-muc-private-foreign-prompt-format "\n%g/%n | ") (add-hook 'jabber-chat-mode-hook #'jabber-ui-setup) (keymap-global-set "C-x C-j" #'dired-jump) ; Extremely annoying fix (require 'jabber-httpupload nil t) (add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons) (remove-hook 'jabber-alert-muc-hooks 'jabber-muc-echo) (remove-hook 'jabber-alert-presence-hooks 'jabber-presence-echo) (add-hook 'jabber-alert-muc-hooks (defun jabber@highlight-acdw (&optional _ _ buf _ _) (when buf (with-current-buffer buf (let ((regexp (rx word-boundary "acdw" ; maybe get from the config? word-boundary))) (hi-lock-unface-buffer regexp) (highlight-regexp regexp 'font-lock-warning-face)))))) (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus)) (use-package keepassxc-shim :load-path "~/src/emacs/keepassxc-shim/" :config (keepassxc-shim-activate)) ;;; External packages (use-package form-feed :ensure t :hook (emacs-lisp-mode-hook jabber-chat-mode-hook)) (use-package minions :ensure t :config (minions-mode)) (use-package visual-fill-column :ensure t :init (setopt visual-fill-column-center-text t visual-fill-column-extra-text-width '(3 . 3)) :config (add-hook 'visual-fill-column-mode-hook #'visual-line-mode) (add-hook 'eww-mode-hook #'visual-fill-column-mode) (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)) (use-package mlscroll :ensure t :defer 1 :after modus-themes :preface (define-advice load-theme (:after (&rest _) mlscroll) (mlscroll-mode -1) (when (seq-intersection '(modus-vivendi modus-operandi) custom-enabled-themes) (modus-themes-with-colors (setq mlscroll-in-color fg-dim mlscroll-out-color bg-inactive))) (run-with-idle-timer 1 nil #'mlscroll-mode)) :config (load-theme@mlscroll)) (use-package cape :ensure t :config (add-hook 'completion-at-point-functions #'cape-file 90) (add-hook 'completion-at-point-functions #'cape-dabbrev 91) (advice-add 'emacs-completion-at-point :around #'cape-wrap-nonexclusive)) (use-package ws-butler :ensure t :config (setopt ws-butler-trim-predicate (lambda (begin end) (not (eq 'font-lock-string-face (get-text-property end 'face))))) (ws-butler-global-mode)) (use-package wgrep :ensure t :config (setopt wgrep-enable-key (kbd "C-x C-q")) :bind (:map grep-mode-map ("C-x C-q" . wgrep-change-to-wgrep-mode))) (use-package avy :ensure t :init (setopt avy-background t avy-keys (string-to-list "asdfghjklqwertyuiopzxcvbnm")) :bind (("M-j" . avy-goto-char-timer) :map isearch-mode-map ("M-j" . avy-isearch))) (use-package zzz-to-char :ensure t :bind (("M-z" . zzz-to-char))) (use-package anzu :ensure t :bind (("M-%" . anzu-query-replace-regexp) ("C-M-%" . anzu-query-replace))) (use-package isearch-mb :ensure t :config (setopt isearch-lazy-count t isearch-regexp-lax-whitespace t search-whitespace-regexp "\\W+" search-default-mode t ; Search regexp by default isearch-wrap-pause 'no) (define-advice isearch-cancel (:before (&rest _) add-search-to-history) "Add search string to history when canceling." (unless (equal "" isearch-string) (isearch-update-ring isearch-string isearch-regexp))) (define-advice perform-replace (:around (orig &rest r) no-anykey-exit) "Don't exit replace for any key that's not in `query-replace-map'." (save-window-excursion (cl-letf* ((lookup-key-orig (symbol-function 'lookup-key)) ((symbol-function 'lookup-key) (lambda (map key &optional accept-default) (or (apply lookup-key-orig map key accept-default) (when (eq map query-replace-map) 'help))))) (apply orig r)))) ;; Consult (autoload 'consult-line "consult" nil t) (autoload 'consult-isearch-history "consult" nil t) (add-to-list 'isearch-mb--after-exit #'consult-line) (add-to-list 'isearch-mb--with-buffer #'consult-isearch-history) (keymap-set isearch-mb-minibuffer-map "M-s l" #'consult-line) (keymap-set isearch-mb-minibuffer-map "M-r" #'consult-isearch-history) ;; Anzu (autoload 'anzu-isearch-query-replace "anzu" nil t) (autoload 'anzu-isearch-query-replace-regexp "anzu" nil t) (add-to-list 'isearch-mb--after-exit #'anzu-isearch-query-replace) (add-to-list 'isearch-mb--after-exit #'anzu-isearch-query-replace-regexp) (keymap-set isearch-mb-minibuffer-map "M-%" #'anzu-isearch-query-replace-regexp) (keymap-set isearch-mb-minibuffer-map "C-M-%" #'anzu-isearch-query-replace) (isearch-mb-mode)) (use-package paredit :ensure t :hook ( emacs-lisp-mode-hook ielm-mode-hook eval-expression-minibuffer-setup-hook lisp-interaction-mode-hook lisp-mode-hook scheme-mode-hook fennel-mode-hook fennel-repl-mode-hook geiser-mode-hook geiser-repl-mode-hook) :config (keymap-set paredit-mode-map "C-j" (defun +paredit-newline () (interactive) (call-interactively (if (derived-mode-p 'lisp-interaction-mode) #'eval-print-last-sexp #'paredit-newline)))) (keymap-set paredit-mode-map "RET" nil) (keymap-set paredit-mode-map "M-s" nil) (add-to-list 'paredit-space-for-delimiter-predicates (defun paredit@dont-space-@ (endp delimiter) "Don't add a space after @ in `paredit-mode'." (let ((point (point))) (or endp (seq-every-p (lambda (prefix) (and (> point (length prefix)) (let ((start (- point (length prefix))) (end point)) (not (string= (buffer-substring start end) prefix))))) ;; Add strings to this list to inhibit adding a space ;; after them. '(",@")))))) (with-eval-after-load 'eldoc (eldoc-add-command #'paredit-backward-delete #'paredit-close-round))) (use-package hungry-delete :ensure t :config (setopt hungry-delete-chars-to-skip " \t" hungry-delete-skip-regexp (format "[%s]" hungry-delete-chars-to-skip) hungry-delete-join-reluctantly nil) (add-to-list 'hungry-delete-except-modes 'eshell-mode) (add-to-list 'hungry-delete-except-modes 'nim-mode) (add-to-list 'hungry-delete-except-modes 'python-mode) ;; Keys (with-eval-after-load 'paredit (define-key paredit-mode-map [remap paredit-backward-delete] (defun paredit/hungry-delete-backward (arg) (interactive "*p") (if (looking-back hungry-delete-skip-regexp) (hungry-delete-backward (or arg 1)) (paredit-backward-delete arg)))) (define-key paredit-mode-map [remap paredit-forward-delete] (defun paredit/hungry-delete-forward (arg) (interactive "*p") (if (looking-at hungry-delete-skip-regexp) (hungry-delete-forward (or arg 1)) (paredit-forward-delete arg)))) ;; Mode (global-hungry-delete-mode))) (use-package macrostep :ensure t :after elisp-mode :bind ( :map emacs-lisp-mode-map ("C-c e" . macrostep-expand) :map lisp-interaction-mode-map ("C-c e" . macrostep-expand))) (use-package package-lint :ensure t) (use-package sly :ensure t :preface (setopt inferior-lisp-program (choose-executable "sbcl")) :when inferior-lisp-program :bind (:map sly-mode-map ("C-c C-z" . sly-mrepl)) :config (setopt sly-net-coding-system 'utf-8-unix) (sly-symbol-completion-mode -1)) (use-package eat :ensure t :hook (eshell-load-hook . eat-eshell-mode)) (use-package pdf-tools :ensure t :mode ("\\.[pP][dD][fF]\\'" . pdf-view-mode) :magic ("%PDF" . pdf-view-mode) :config (pdf-tools-install)) (use-package keychain-environment :ensure t :when (executable-find "keychain") :hook (after-init-hook . keychain-refresh-environment)) (use-package web-mode :ensure t :mode ("\\.phtml\\'" "\\.tpl\\.php\\'" "\\.[agj]sp\\'" "\\.as[cp]x\\'" "\\.erb\\'" "\\.mustache\\'" "\\.djhtml\\'" "\\.html?\\'")) (use-package nginx-mode :ensure t :mode "/nginx/sites-\\(?:available\\|enabled\\)/") (use-package markdown-mode :ensure t :mode "\\.\\(?:md\\|markdown\\|mkd\\|mdown\\|mkdn\\|mdwn\\)\\'" :config (setopt markdown-command (choose-executable '("pandoc" "--from=markdown" "--to=html5") "markdown")) (add-hook 'markdown-mode-hook #'visual-fill-column-mode)) (use-package edit-indirect :bind (("C-c '" . edit-indirect-region))) (use-package transpose-frame :ensure t :bind (("C-x 5 t" . transpose-frame) ("C-x 5 h" . flop-frame) ; horizontal ("C-x 5 v" . flip-frame) ; vertical )) (use-package magit :pin melpa-stable :ensure t :bind ("C-x g" . magit)) (use-package eradio :ensure t :preface (defun eradio-toggle|play (&optional arg) "Run `eradio-toggle', or `eradio-play' with prefix ARG." (interactive "P") (if arg (eradio-play) (eradio-toggle))) :bind (("C-c r p" . eradio-toggle|play) ("C-c r s" . eradio-stop)) :config (setopt eradio-player '("mpv" "--no-video" "--no-terminal") eradio-channels ;; At some point I should actually ... write this in to this file or ;; something. But until I decide to quit using radish altogether, this ;; what I got. (with-current-buffer (find-file-noselect "~/etc/radish/stations") (let (chans) (dolist (line (string-split (buffer-substring-no-properties (point-min) (point-max)) "\n") chans) (unless (string-match-p "^#" line) (let* ((ll (string-split line "\t")) (url (cl-first ll)) (name (cl-second ll)) (tags (cl-third ll))) (when (and name (string-match-p "[^ \n\t]*://[^ \n\t]*" url)) (push (cons (format "%s - %s" name tags) url) chans))))))))) (use-package wiki-abbrev :after org ; Don't need abbrevs til I load org. :load-path "~/src/emacs/wiki-abbrev.el/" :config (setopt wiki-abbrev-file (etc/ "wiki-abbrevs")) (wiki-abbrev-insinuate)) (use-package flyspell-correct :ensure t :preface (defun +flyspell-correct-buffer (&optional prefix) "Run `flyspell-correct-wrapper' on all misspelled words in the buffer. With PREFIX, prompt to change the current dictionary." (interactive "P") (flyspell-buffer) (when prefix (let ((current-prefix-arg nil)) (call-interactively #'ispell-change-dictionary))) (flyspell-correct-move (point-min) :forward :rapid)) :after flyspell :bind (:map flyspell-mode-map ("C-;" . flyspell-correct-wrapper) ("" . +flyspell-correct-buffer)) :config (setq flyspell-correct--cr-key ";") (keymap-unset flyspell-mode-map "C-," t) (keymap-unset flyspell-mode-map "C-." t)) (use-package dired-subtree :ensure t :after dired :bind (:map dired-mode-map (("TAB" . dired-subtree-cycle) ("i" . dired-subtree-toggle)))) (use-package dired-hide-dotfiles ;; I could maybe use a more general package for this ... see ;; https://emacs.grym.io/#orgbbda609 :ensure t :bind (:map dired-mode-map ("." . dired-hide-dotfiles-mode))) (use-package embrace :ensure t :preface (defmacro org-insert-or-embrace (char) "Define a function to insert CHAR, or `embrace' the region with it." (let* ((fn-name (intern (format "org-insert-or-embrace-%s" char))) (char (cond ((characterp char) char) ((stringp char) (string-to-char char)) (t (user-error "Bad format for char: %S" char))))) `(defun ,fn-name (n) ,(format "Insert N %ss, or surround the region with them." (char-to-string char)) (interactive "p") (if (region-active-p) (dotimes (_ n) (embrace--add-internal (region-beginning) (region-end) ,char) (forward-char 1)) (self-insert-command n ,char))))) (with-eval-after-load 'org (require 'embrace) (keymap-set org-mode-map "*" (org-insert-or-embrace "*")) (keymap-set org-mode-map "/" (org-insert-or-embrace "/")) (keymap-set org-mode-map "_" (org-insert-or-embrace "_")) (keymap-set org-mode-map "=" (org-insert-or-embrace "=")) (keymap-set org-mode-map "~" (org-insert-or-embrace "~")) (keymap-set org-mode-map "+" (org-insert-or-embrace "+"))) :bind (("C-\"" . embrace-commander)) :hook ((org-mode-hook . embrace-org-mode-hook) (ruby-mode-hook . embrace-ruby-mode-hook) (emacs-lisp-mode-hook . embrace-emacs-lisp-mode-hook) (latex-mode-hook . embrace-LaTeX-mode-hook))) ;;; Applications (use-package acdw-mail :load-path "lisp/" :demand t :bind (("C-c n" . +notmuch-goto))) (use-package acdw-org :load-path "lisp/") (use-package _work :load-path "~/Sync/emacs/private/")