;;; init.el --- An Emacs of one's own -*- lexical-binding: t -*- ;; Author: Case Duckworth , with inspo from many others ;; Homepage: https://git.acdw.net/emacs ;; Config-Requires: ((emacs "29.0")) ;; Bankruptcy: 9.4 ;; This configuration is Free Software. Everyone is permitted to do whatever ;; they want with it, without limitation. This software comes without any ;; warranty whatsoever, but with two pieces of advice: ;; ;; - Don't hurt others. ;; - Make good choices. ;;; Code: (load (locate-user-emacs-file "basics")) ; super basic stuff ;;; Built-ins (use-package emacs ; Misc. config :config (setopt tab-bar-show 1 recenter-positions '(top middle bottom) initial-scratch-message (format "%s\n\n" (mapconcat (lambda (s) (format ";; %s" s)) (process-lines "fortune" "-s") "\n"))) (keymap-global-unset "C-\\") (add-hook 'kill-buffer-query-functions (defun scratch@immortal () (if (equal (buffer-name) "*scratch*") (progn (bury-buffer) nil) t)))) (use-package faces :config (add-hook 'server-after-make-frame-hook (defun first-frame@set-fonts () (remove-hook 'server-after-make-frame-hook #'first-frame@set-fonts) (face-spec-set 'fixed-pitch `((t :family ,(find-font "Comic Code" "DejaVu Sans Mono") :height 100))) (face-spec-set 'variable-pitch `((t :family ,(find-font "Atkinson Hyperlegible" "DejaVu Serif") :height 1.4))) (face-spec-set 'default `((t :family ,(find-font "Comic Code" "DejaVu Sans Mono") :height 100))) (face-spec-set 'font-lock-comment-face `((t :slant italic))) ;; 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)))) (unless (daemonp) (run-with-idle-timer 1 nil #'first-frame@set-fonts))) (use-package text-mode :config (add-hook 'text-mode-hook #'abbrev-mode)) (use-package prog-mode :config (setopt tab-width 8 sh-indentation tab-width) (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 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 ispell :config (setopt ispell-program-name (choose-executable "ispell" "aspell")) (add-hook 'before-save-hook #'+ispell-move-buffer-words-to-dir-locals-hook) (put 'ispell-buffer-session-localwords 'safe-local-variable '+ispell-safe-local-p)) (use-package flyspell :hook org-mode-hook) (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)) (use-package dictionary :custom (dictionary-server (if (or (executable-find "dictd") (file-exists-p "/usr/sbin/dictd")) ; debian "localhost" "dict.org")) :bind (("C-c d" . dictionary-search))) (use-package mouse :config (setopt context-menu-functions '(context-menu-undo context-menu-region context-menu-middle-separator context-menu-local context-menu-minor)) (context-menu-mode)) (use-package password-cache :config (setopt password-cache t password-cache-expiry 3600)) (use-package time :config (setopt display-time-format " %H:%M" display-time-interval 60 display-time-use-mail-icon t display-time-mail-function (defun +notmuch-new-mail-p () (plist-get (cl-find "inbox+unread" (ignore-errors (notmuch-hello-query-counts notmuch-saved-searches)) :key (lambda (l) (plist-get l :name)) :test #'equal) :count)) display-time-default-load-average nil) (with-eval-after-load 'notmuch (add-hook 'notmuch-after-tag-hook #'display-time-update)) (display-time-mode)) (use-package tab-bar :config (setopt tab-bar-show t tab-bar-close-button-show t) ;; (add-to-list 'tab-bar-format 'tab-bar-format-menu-bar) (add-to-list 'tab-bar-format 'tab-bar-format-align-right :append) (add-to-list 'tab-bar-format 'tab-bar-format-global :append) (if (daemonp) (add-hook 'server-after-make-frame-hook (defun after-frame@tab-bar () (tab-bar-mode) (remove-hook 'server-after-make-frame-hook #'after-frame@tab-bar))) (run-with-idle-timer 2 nil #'tab-bar-mode))) (use-package info :preface (defun Info-copy-current-node-name-0 () "Call `Info-copy-current-node-name' with a 0 prefix arg." (interactive) (Info-copy-current-node-name 0)) :bind (:map Info-mode-map ("w" . Info-copy-current-node-name-0) ("c" . Info-copy-current-node-name))) ;;; 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 acdw-shell :load-path "lisp/") (use-package acdw-web :load-path "lisp/") (use-package _work :load-path "~/Sync/emacs/private/") ;;; 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) :config (setopt filldent-fill-modes '(web-mode))) (use-package frowny :load-path "~/src/emacs/frowny/" :config (global-frowny-mode)) (use-package jabber :load-path "~/src/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 jabber-chat-prompt-local :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-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-foreign-prompt-format (format "%%>%dn | " jabber-prefix-width) jabber-muc-private-foreign-prompt-format "\n%g/%n | ") ;; jabber muc nick coloring ;; wgreenhous | I found 1.5 ok for saturation and 2.0 for value (modus) ;; (setopt jabber-muc-nick-value 1.0 ;; jabber-muc-nick-saturation 1.0 ;; jabber-muc-colorize-local t ;; jabber-muc-colorize-foreign t) ;; When changing the above values, make sure to ;; (setq jabber-muc-participant-colors nil) (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 'jabber-chat-mode-hook (defun jabber-chat@leave-when-kill () (add-hook 'kill-buffer-hook (defun @jabber-leave@kill () (apply #'jabber-muc-leave (jabber-muc-argument-list))) nil :local))) (when (fboundp 'jabber-chat-update-focus) (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus)) (with-eval-after-load 'consult (defvar jabber-chat-buffer-source `( :name "Jabber" :hidden nil :narrow ?j :category buffer :state ,#'consult--buffer-state :items ,(lambda () (mapcar #'buffer-name (seq-filter (lambda (buf) (with-current-buffer buf (eq major-mode 'jabber-chat-mode))) (buffer-list)))))) (add-to-list 'consult-buffer-sources 'jabber-chat-buffer-source :append) (consult-customize consult-buffer :preview-key (kbd "M-.")))) (use-package keepassxc-shim :load-path "~/src/emacs/keepassxc-shim/" :config (keepassxc-shim-activate)) ;;; External packages (use-package async :ensure t :config ;; https://github.com/jwiegley/emacs-async/issues/64 ;; (setopt message-send-mail-function #'async-smtpmail-send-it) (dired-async-mode) (async-bytecomp-package-mode)) (use-package trashed :ensure t) (use-package form-feed :ensure t :hook (emacs-lisp-mode-hook)) (use-package clean-kill-ring :vc (:url "https://github.com/NicholasBHubbard/clean-kill-ring.el") :config (setopt clean-kill-ring-prevent-duplicates t) (clean-kill-ring-mode)) (use-package minions :ensure t :config (minions-mode)) (use-package visual-fill-column :preface (defcustom visual-fill-column-widen-amount 4 "Amount to widen `fill-column' by in `visual-fill-column-mode'." :type 'natnum :group 'visual-fill-column) (defun visual-fill-column--widen/narrow-handle-arg (cols) (cond ((null cols) visual-fill-column-widen-amount) ((listp cols) (* visual-fill-column-widen-amount (1+ (/ (car cols) 4)))) ((eq '- cols) (- visual-fill-column-widen-amount)) (:else cols))) (defun visual-fill-column-widen (&optional cols) "Widen `fill-column' by COLS, and re-display. If COLS is missing or nil, widen by `visual-fill-column-widen-amount'. When called with a plain \\[universal-argument], multiply that amount by 1 + the amount of \\[universal-argument]s. If called with a numerical prefix argument, widen by that number of columns." (interactive "P") (let ((cols (visual-fill-column--widen/narrow-handle-arg cols))) (cl-incf fill-column cols) (visual-fill-column-adjust) (message "Fill-column: %s" fill-column))) (defun visual-fill-column-narrow (&optional cols) "Narrow `fill-column' by COLS, then redisplay. The prefix argument is as in `visual-fill-column-widen' but negated." (interactive "P") (let ((cols (visual-fill-column--widen/narrow-handle-arg cols))) (cl-decf fill-column cols) (visual-fill-column-adjust) (message "Fill-column: %s" fill-column))) :ensure t :init (setopt visual-fill-column-center-text t visual-fill-column-extra-text-width '(3 . 3)) :config (keymap-set visual-fill-column-mode-map "C-x C->" #'visual-fill-column-widen) (keymap-set visual-fill-column-mode-map "C-x C-<" #'visual-fill-column-narrow) (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 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 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 pandoc-mode :ensure t :hook ((markdown-mode-hook . pandoc-mode) (pandoc-mode-hook . pandoc-load-default-settings))) (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))) (use-package apheleia :ensure t :config (setopt apheleia-hide-log-buffers t) (setf (alist-get 'shfmt apheleia-formatters) '("shfmt" "--case-indent")) (global-set-key (kbd "M-C-\\") (defun +apheleia-format|indent-buffer () (interactive) (if-let ((formatters (apheleia--get-formatters))) (apheleia-format-buffer formatters (lambda () (with-demoted-errors "Apheleia: %s" (when buffer-file-name (let ((apheleia--format-after-save-in-progress t)) (apheleia--save-buffer-silently))) (run-hooks 'apheleia-post-format-hook)))) (indent-region (point-min) (point-max)) (when buffer-file-name (save-buffer)))))) (use-package php-mode :ensure t) (use-package rec-mode :ensure t) (use-package gauche-mode :vc (:url "https://github.com/duckwork/gauche-mode") :mode "\\.scm\\'" :config (setopt gauche-mode-info-language 'en scheme-program-name "gosh -i" scheme-compile-exp-command "%s") (add-to-list 'scheme-source-modes 'gauche-mode) (setf (alist-get "\\*scheme\\*" display-buffer-alist nil nil #'equal) '((display-buffer-reuse-window display-buffer-pop-up-window) (inhibit-same-window . t))) (add-hook 'gauche-mode-hook #'enable-gauche-paredit-mode) (add-hook 'inferior-scheme-mode-hook #'enable-gauche-paredit-mode)) (use-package treesit-auto ;; XXX: I don't really get the utility of this package, but I spent a ton of ;; time figuring out how to properly build it ... so here it is. :when (treesit-available-p) :vc (:url "https://github.com/renzmann/treesit-auto") :config (setopt treesit-extra-load-path ;; https://github.com/casouri/tree-sitter-module/ `(,(expand-file-name "~/misc/tree-sitter-module/dist/"))) (treesit-auto-apply-remap) (define-advice treesit-install-language-grammar (:after (&rest _) apply-remap) (treesit-auto-apply-remap)))