From 435e3479fb6968c3b8d7f6f426f31a011fff536f Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Sun, 16 Jul 2023 22:22:28 -0500 Subject: nyahhhhh ~~~~~~ --- emacs.el | 704 +++++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 530 insertions(+), 174 deletions(-) (limited to 'emacs.el') diff --git a/emacs.el b/emacs.el index ad79286..7e83277 100644 --- a/emacs.el +++ b/emacs.el @@ -8,31 +8,56 @@ (add-hook 'after-init-hook (lambda () (load (locate-user-emacs-file "private") :noerror))) +(require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize) (defun package-ensure (pkg &optional local require) - "Ensure PACKAGE is installed. -If LOCAL is t, add ~/src/PKG.el to `load-path' and generate autoloads. -If REQUIRE is non-nil, require it as well." + "Ensure PKG is installed. +PKG can be a symbol, a string, or a list. A symbol will be +installed using `package-install' from `package-archives'. A +string will use `package-vc-install', which see. If given a +list, it will be interpreted as a full set of arguments to one of +the above functions, depending on the type of its car. + +If LOCAL is t, add ~/src/PKG.el to `load-path' and generate +autoloads. If LOCAL is a string, Add that directory to +`load-path'. + +If REQUIRE is non-nil, require the package after installing it." + (setq pkg (ensure-list pkg)) + (cond - ((stringp local) + (local + (unless (stringp local) + (setq local (expand-file-name + (format "~/src/%s.el" (car pkg))))) (unless (file-directory-p local) (user-error "Package directory does not exist: %s" local)) - (let ((autoload-file (expand-file-name (format "%s-autoloads.el" pkg) local)) + (let ((autoload-file (expand-file-name + (format "%s-autoloads.el" (car pkg)) + local)) (backup-inhibited t)) (add-to-list 'load-path local) (loaddefs-generate local autoload-file) - (load autoload-file nil t))) - (local - (package-ensure pkg (expand-file-name (format "~/src/%s.el" pkg)) require)) - (:else - (unless (package-installed-p pkg) - (unless (ignore-errors (package-install pkg)) - (package-refresh-contents) - (package-install pkg))))) + (load autoload-file nil t)) + (setq pkg (intern (format "%s" (car pkg))))) + + ((symbolp (car pkg)) + (unless (ignore-errors (apply #'package-install pkg)) + (package-refresh-contents) + (apply #'package-install pkg)) + (setq pkg (car pkg))) + + ((stringp (car pkg)) + (let ((pkg-name (intern (file-name-base (car pkg))))) + (unless (package-installed-p pkg-name) + (apply #'package-vc-install pkg)) + (setq pkg pkg-name)))) + (when require (require pkg)) + pkg) (defmacro setf/assoc (alist key val &optional keep) @@ -45,6 +70,11 @@ KEEP is also non-nil." (defmacro comment (&rest _) (declare (indent defun)) nil) +(defun add-local-mode-hook (mode-hook hook func) + "Add FUNC to HOOK locally in buffers with MODE-HOOK." + (add-hook mode-hook + (lambda () (add-hook hook func nil t)))) + (package-ensure 'crux) (crux-reopen-as-root-mode) @@ -54,6 +84,12 @@ KEEP is also non-nil." (keymap-global-set "C-c i" #'crux-find-user-init-file) +(setopt auth-sources '(default + "secrets:passwords" + "~/.authinfo")) + +(setq disabled-command-function nil) + ;;; Theme @@ -67,6 +103,8 @@ KEEP is also non-nil." (scroll-bar-mode -1) (tooltip-mode -1) +(setopt scroll-conservatively 101) + (setopt read-answer-short t) (setopt use-dialog-box nil) (setopt use-file-dialog nil) @@ -109,20 +147,21 @@ KEEP is also non-nil." font-lock-bracket-face)) (face-spec-set face '((t :foreground unspecified :background unspecified)))) - (dolist (face '(font-lock-doc-face - font-lock-string-face)) - (face-spec-set face '((t :foreground unspecified - :background unspecified - :slant italic)))) (when-let ((current (cl-loop for modus in '(modus-vivendi modus-operandi) if (memq modus custom-enabled-themes) return modus finally return nil))) - (face-spec-set 'font-lock-comment-face - `((t :foreground ; :inherit doesn't work for some reason?? - ,(if (eq current 'modus-operandi) - "#7c318f" - "#caa6df")))))) + (modus-themes-with-colors + (dolist (face '(font-lock-doc-face + font-lock-string-face)) + (face-spec-set face `((t :foreground unspecified + :background unspecified + :slant italic)))) + ;; (face-spec-set 'font-lock-comment-face + ;; :inherit doesn't work for some reason?? + ;; `((t :foreground + ;; ,fg-alt))) + ))) (advice-add 'load-theme :after #'reset-faces) (load-theme 'modus-vivendi :no-confirm :no-enable) @@ -274,7 +313,11 @@ mouse-3: Toggle minor modes" ;;; Completion and minibuffer (setopt tab-always-indent 'complete) -;; (setopt completion-styles '(basic partial-completion substring flex)) +(setopt completion-styles '(basic partial-completion substring flex)) + +;; XXX: this is 'too good' when I'm in the process of typing out things. +;; (when (package-ensure "https://git.sr.ht/~pkal/typo") +;; (add-to-list 'completion-styles 'typo :append)) (setopt completion-ignore-case t) (setopt read-buffer-completion-ignore-case t) @@ -289,15 +332,30 @@ mouse-3: Toggle minor modes" (setopt completions-format 'one-column) (setopt completions-max-height 20) +;; (defun minibuffer-next-completion-or-line (n) +;; "Move to the next N completion in minibuffer, or Nth next line." +;; (interactive "p") +;; (if (and (eq last-command 'minibuffer-next-completion) +;; (not (minibufferp))) +;; (forward-line n) +;; (minibuffer-next-completion n))) + +;; (defun minibuffer-previous-completion-or-line (n) +;; "Move to the previous N completion, or Nth previous line." +;; (interactive "p") +;; (setq last-command 'minibuffer-next-completion-or-line) +;; (minibuffer-next-completion-or-line (- n))) + (progn (keymap-set minibuffer-local-map "C-p" #'minibuffer-previous-completion) (keymap-set minibuffer-local-map "C-n" #'minibuffer-next-completion) - (keymap-set completion-in-region-mode-map "C-p" - #'minibuffer-previous-completion) - (keymap-set completion-in-region-mode-map "C-n" - #'minibuffer-next-completion)) + ;; (keymap-set completion-in-region-mode-map "C-p" + ;; #'minibuffer-previous-completion) + ;; (keymap-set completion-in-region-mode-map "C-n" + ;; #'minibuffer-next-completion) + ) (setf/assoc display-buffer-alist "\\*Completions\\*" @@ -318,11 +376,6 @@ mouse-3: Toggle minor modes" (add-hook 'completion-list-mode-hook #'truncate-lines-local-mode) (add-hook 'minibuffer-setup-hook #'truncate-lines-local-mode) -(when (package-ensure 'orderless) - (setopt completion-styles '(substring orderless basic)) - (setopt completion-category-overrides - '((file (styles basic partial-completion))))) - (when (package-ensure 'consult nil t) (keymap-global-set "C-x b" #'consult-buffer) (keymap-global-set "C-x 4 b" #'consult-buffer-other-window) @@ -439,6 +492,8 @@ mouse-3: Toggle minor modes" (keymap-global-set "M-/" #'hippie-expand) (keymap-global-set "C-x C-b" #'ibuffer) +(add-hook 'ibuffer-mode-hook #'hl-line-mode) + (defun call-with-region-or-buffer (fn &rest _r) "Call function FN with current region or buffer. Good to use for :around advice." @@ -481,7 +536,7 @@ Good to use for :around advice." (defun org-fk-region (start end) "Get the Flesch-Kincaid score of an `org-mode' region." (interactive "r") - (let ((buf (get-buffer-create "*fk*" t))) + (let ((buf (get-buffer-create " *fk*" t))) (shell-command-on-region start end "pandoc -t plain -f org | ~/src/fk/fk.perl" buf) @@ -506,6 +561,8 @@ Good to use for :around advice." (unless (ignore-errors (dictionary-lookup-definition)) (call-interactively #'dictionary-search)))) +(package-ensure 'markdown-mode) + ;;; Programming (setopt electric-pair-skip-whitespace 'chomp) @@ -533,6 +590,7 @@ Good to use for :around advice." (setopt tab-width 8) (defvar space-indent-modes '(emacs-lisp-mode + lisp-interaction-mode lisp-mode scheme-mode python-mode @@ -552,7 +610,8 @@ Good to use for :around advice." (when (package-ensure 'geiser) (when (executable-find "csi") - (package-ensure 'geiser-chicken)) + (when (package-ensure 'geiser-chicken) + (setf/assoc auto-mode-alist "\\.egg\\'" 'scheme-mode)))) (setopt scheme-program-name (or (executable-find "csi") "scheme")) (add-hook 'scheme-mode-hook #'geiser-mode)) @@ -566,6 +625,19 @@ Good to use for :around advice." _ \n "|#" \n \n)) +;; Emacs lisp + +(keymap-set emacs-lisp-mode-map "C-c C-c" #'eval-defun) +(keymap-set emacs-lisp-mode-map "C-c C-b" #'eval-buffer) +(keymap-set emacs-lisp-mode-map "C-c C-z" #'ielm) ; TODO: better-ize +(keymap-set lisp-interaction-mode-map "C-c C-c" #'eval-defun) +(keymap-set lisp-interaction-mode-map "C-c C-b" #'eval-buffer) +(keymap-set lisp-interaction-mode-map "C-c C-z" #'ielm) ; TODO: better-ize + + +(define-advice eval-buffer (:after (&rest _) message) + (message "Buffer %s evaluated." (current-buffer))) + ;;; Files @@ -640,18 +712,33 @@ Good to use for :around advice." ;;; ... +(defun c-w-dwim (num) + "Delete NUM words backward, or the region if it's active." + (interactive "p") + (if (region-active-p) + (call-interactively #'kill-region) + (call-interactively #'backward-kill-word))) +(keymap-global-set "C-w" #'c-w-dwim) + +(setf/assoc display-buffer-alist + "\\`\\*Warnings\\*" + '((display-buffer-no-window))) + +(winner-mode) + (setopt set-mark-command-repeat-pop t) (when (package-ensure 'embark nil t) - (when (package-installed-p 'consult) - (package-ensure 'embark-consult nil t)) + (when (and (package-installed-p 'consult) + (package-ensure 'embark-consult nil t)) + (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode)) (keymap-global-set "C-." #'embark-act) (keymap-global-set "M-." #'embark-dwim) (keymap-global-set "C-h B" #'embark-bindings) + (setopt prefix-help-command #'embark-prefix-help-command) (setf/assoc display-buffer-alist "\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" - '(nil (window-parameters (mode-line-format . none)))) - (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode)) + '(nil (window-parameters (mode-line-format . none))))) (setopt eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) (setopt eldoc-idle-delay 0.01) @@ -692,23 +779,6 @@ Good to use for :around advice." (delete-trailing-whitespace (line-end-position) (point-max))))) -(defcustom browse-url-safe-browser-functions nil - "\"Safe\" browser functions." - :type '(repeat-function)) - -(defun browse-url-browser-function-safe-p (fn) - "Return t if FN is a \"safe\" browser function." - (memq fn (append browse-url-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))))))) - -(put 'browse-url-browser-function 'safe-local-variable - 'browse-url-browser-function-safe-p) - (defun list-of-strings-p (x) "Is X a list of strings?" (and x @@ -732,29 +802,44 @@ Good to use for :around advice." (repunctuate-sentences :no-query start end) (fill-region start end))) -(defun fill-or-unfill-region (start end) +(defun unfill-region (start end &optional unfill-func) + "Unfill region from START to END." + (let ((fill-column most-positive-fixnum) + (fill-paragraph-function nil)) + (funcall (or unfill-func #'fill-region) start end))) + +(defun fill-or-unfill-region (start end &optional interactive) "Fill or unfill from START to END." - (let ((filled-p (cl-every (lambda (ln) (<= (length ln) fill-column)) - (string-split (buffer-substring start end) - "[\n\r]+")))) - (if filled-p - (let ((fill-column most-positive-fixnum) - (fill-paragraph-function nil)) - (message "Unfilling region") - (fill-double-space-sentences-region start end)) - (message "Filling region") - (fill-double-space-sentences-region start end)))) + (interactive "*r\np") + (if (and interactive + (eq last-command 'fill-or-unfill-region)) + ;; If called interactively more than once, toggle filling mode. + (if (with-current-buffer "*Messages*" + (goto-char (point-max)) + (goto-char (beginning-of-line)) + (looking-at "Unfilling")) + (fill-double-space-sentences-region start end) + (unfill-region start end #'fill-double-space-sentences-region)) + ;; Otherwise, detect filled status based on the length of lines in the + ;; region. If just one of them is longer than `fill-column', consider the + ;; region unfilled. + (let ((filled-p (cl-some (lambda (ln) (<= 1 (length ln) fill-column)) + (string-split (buffer-substring start end) + "[\n\r]+")))) + (if filled-p + (progn + (message "Unfilling region") + (unfill-region start end #'fill-double-space-sentences-region)) + (progn + (message "Filling region") + (fill-double-space-sentences-region start end)))))) (defun fill-or-unfill-dwim () (interactive) (save-mark-and-excursion - (if (region-active-p) - (fill-or-unfill-region (region-beginning) - (region-end)) - (fill-or-unfill-region (progn (backward-paragraph) - (point)) - (progn (forward-paragraph) - (point)))))) + (unless (region-active-p) + (mark-paragraph)) + (call-interactively #'fill-or-unfill-region))) (keymap-global-set "M-q" #'fill-or-unfill-dwim) @@ -788,6 +873,7 @@ In these cases, switch to the last-used buffer." (switch-to-buffer (other-buffer) nil t) (other-window 1))) (keymap-global-set "M-o" #'other-window-dwim) +(keymap-global-set "C-x o" #'other-window-dwim) (defun delete-window-dwim () "Delete the current window or bury its buffer. @@ -1000,6 +1086,31 @@ ORG-EXPORT-ARGS are passed to `org-export-to-buffer'." (org-todo (if (= n-not-done 0) "DONE" "TODO")))) (add-hook 'org-after-todo-statistics-hook #'org-summary-todo) +;; Clean up the buffer view +(defun org-hide-drawers-except-point () + "Hide all drawers except for the one point is in." + ;; Most of this bit is taken from `org-fold--hide-drawers'. + (let ((pt (point)) + (begin (point-min)) + (end (point-max))) + (save-excursion + (goto-char begin) + (while (and (< (point) end) + (re-search-forward org-drawer-regexp end t)) + (if (org-fold-folded-p nil 'drawer) + (goto-char (org-fold-next-folding-state-change 'drawer nil end)) + (let* ((drawer (org-element-at-point)) + (type (org-element-type drawer)) + (el-begin (org-element-property :begin drawer)) + (el-end (org-element-property :end drawer))) + (when (memq type '(drawer property-drawer)) + (org-fold-hide-drawer-toggle + (if (< el-begin pt el-end) 'off 'on) + nil drawer) + (goto-char el-end)))))))) +(add-local-mode-hook 'org-mode-hook 'before-save-hook + #'org-hide-drawers-except-point) + ;; Fix braindead behavior (with-eval-after-load 'org-mouse (defun org--mouse-open-at-point (orig-fun &rest args) @@ -1079,7 +1190,7 @@ the following: `:keys', `:description', `:type', `:target', and ;; (string-chop-newline (buffer-substring-no-properties ;; (line-beginning-position) (point-max))))) - (let ((shell-command-buffer-name (format "*fk/%s*" (buffer-name)))) + (let ((shell-command-buffer-name (format " *fk/%s*" (buffer-name)))) (shell-command-on-region start end "~/src/fk/fk.perl") (with-current-buffer shell-command-buffer-name (buffer-substring-no-properties (point-min) (- (point-max) 1)))) @@ -1153,102 +1264,102 @@ the following: `:keys', `:description', `:type', `:target', and ;;; Jabber -(when (package-ensure 'jabber t t) - (setopt jabber-chat-buffer-format "*%n*") - (setopt jabber-browse-buffer-format "*%n*") - (setopt jabber-groupchat-buffer-format "*%n*") - (setopt jabber-muc-private-buffer-format "*%n*") - - (face-spec-set 'jabber-activity-face - '((t :inherit jabber-chat-prompt-foreign - :foreground unspecified - :weight normal))) - (face-spec-set 'jabber-activity-personal-face - '((t :inherit jabber-chat-prompt-local - :foreground unspecified - :weight bold))) - (face-spec-set 'jabber-chat-prompt-local - '((t :inherit minibuffer-prompt - :foreground unspecified - :weight normal - :slant italic))) - (face-spec-set 'jabber-chat-prompt-foreign - '((t :inherit warning - :foreground unspecified - :weight normal))) - (face-spec-set 'jabber-chat-prompt-system - '((t :inherit font-lock-doc-face - :foreground unspecified))) - (face-spec-set 'jabber-rare-time-face - '((t :inherit font-lock-comment-face - :foreground unspecified - :underline nil))) - - (setopt jabber-auto-reconnect t) - (setopt jabber-last-read-marker - "-------------------------------------------------------------------") - (setopt jabber-muc-decorate-presence-patterns - '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$" . nil) - ("Mode #.*" . jabber-muc-presence-dim) - ("." . jabber-muc-presence-dim))) - (setopt jabber-activity-make-strings #'jabber-activity-make-strings-shorten) - (setopt jabber-rare-time-format - (format " - - - - - %%H:%d %%F" - (let ((min (string-to-number (format-time-string "%M")))) - (* 5 (floor min 5))))) - (setopt jabber-muc-header-line-format '(" " jabber-muc-topic)) - - (setopt jabber-groupchat-prompt-format "%n. ") - (setopt jabber-chat-local-prompt-format "%n. ") - (setopt jabber-chat-foreign-prompt-format "%n. ") - (setopt jabber-muc-private-foreign-prompt-format "%g/%n. ") - - (defun jabber-connect-all* (&optional arg) - "Connect to all defined jabber accounts. -If called with ARG non-nil, or with \\[universal-argument], -disconnect first." - (interactive "P") - (when arg (jabber-disconnect)) - (jabber-connect-all)) - - (keymap-global-set "C-c C-SPC" #'jabber-activity-switch-to) - (with-eval-after-load 'jabber - (require 'jabber-httpupload nil t) - (map-keymap (lambda (key command) - (define-key jabber-global-keymap (vector (+ key #x60)) command)) - jabber-global-keymap) - (keymap-global-set "C-x C-j" #'dired-jump)) - (keymap-global-set "C-x C-j" #'dired-jump) - (keymap-set jabber-global-keymap "c" #'jabber-connect-all*) - (keymap-global-set "C-c j" jabber-global-keymap) - - (remove-hook 'jabber-alert-muc-hooks #'jabber-muc-echo) - (remove-hook 'jabber-alert-presence-hooks #'jabber-presence-echo) - (add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons) - (add-hook 'jabber-chat-mode-hook #'olivetti-mode) - (add-hook 'jabber-chat-mode-hook - (defun jabber-chat-mode-no-position () - (setq-local mode-line-position nil))) - (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 'jabber-chat-prompt-local)))))) - - (add-hook 'jabber-chat-mode-hook - (defun electric-pair-local-disable () - (electric-pair-local-mode -1))) - - (when (fboundp 'jabber-chat-update-focus) - (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus))) +;; (when (package-ensure 'jabber t t) +;; (setopt jabber-chat-buffer-format "*%n*") +;; (setopt jabber-browse-buffer-format "*%n*") +;; (setopt jabber-groupchat-buffer-format "*%n*") +;; (setopt jabber-muc-private-buffer-format "*%n*") + +;; (face-spec-set 'jabber-activity-face +;; '((t :inherit jabber-chat-prompt-foreign +;; :foreground unspecified +;; :weight normal))) +;; (face-spec-set 'jabber-activity-personal-face +;; '((t :inherit jabber-chat-prompt-local +;; :foreground unspecified +;; :weight bold))) +;; (face-spec-set 'jabber-chat-prompt-local +;; '((t :inherit minibuffer-prompt +;; :foreground unspecified +;; :weight normal +;; :slant italic))) +;; (face-spec-set 'jabber-chat-prompt-foreign +;; '((t :inherit warning +;; :foreground unspecified +;; :weight normal))) +;; (face-spec-set 'jabber-chat-prompt-system +;; '((t :inherit font-lock-doc-face +;; :foreground unspecified))) +;; (face-spec-set 'jabber-rare-time-face +;; '((t :inherit font-lock-comment-face +;; :foreground unspecified +;; :underline nil))) + +;; (setopt jabber-auto-reconnect t) +;; (setopt jabber-last-read-marker +;; "-------------------------------------------------------------------") +;; (setopt jabber-muc-decorate-presence-patterns +;; '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$" . nil) +;; ("Mode #.*" . jabber-muc-presence-dim) +;; ("." . jabber-muc-presence-dim))) +;; (setopt jabber-activity-make-strings #'jabber-activity-make-strings-shorten) +;; (setopt jabber-rare-time-format +;; (format " - - - - - %%H:%d %%F" +;; (let ((min (string-to-number (format-time-string "%M")))) +;; (* 5 (floor min 5))))) +;; (setopt jabber-muc-header-line-format '(" " jabber-muc-topic)) + +;; (setopt jabber-groupchat-prompt-format "%n. ") +;; (setopt jabber-chat-local-prompt-format "%n. ") +;; (setopt jabber-chat-foreign-prompt-format "%n. ") +;; (setopt jabber-muc-private-foreign-prompt-format "%g/%n. ") + +;; (defun jabber-connect-all* (&optional arg) +;; "Connect to all defined jabber accounts. +;; If called with ARG non-nil, or with \\[universal-argument], +;; disconnect first." +;; (interactive "P") +;; (when arg (jabber-disconnect)) +;; (jabber-connect-all)) + +;; (with-eval-after-load 'jabber +;; (keymap-global-set "C-c C-SPC" #'jabber-activity-switch-to) +;; (require 'jabber-httpupload nil t) +;; (map-keymap (lambda (key command) +;; (define-key jabber-global-keymap (vector (+ key #x60)) command)) +;; jabber-global-keymap) +;; (keymap-global-set "C-x C-j" #'dired-jump) +;; (keymap-set jabber-global-keymap "c" #'jabber-connect-all*) +;; (keymap-global-set "C-c j" jabber-global-keymap)) + +;; (remove-hook 'jabber-alert-muc-hooks #'jabber-muc-echo) +;; (remove-hook 'jabber-alert-presence-hooks #'jabber-presence-echo) +;; (add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons) +;; (add-hook 'jabber-chat-mode-hook #'olivetti-mode) +;; (add-hook 'jabber-chat-mode-hook +;; (defun jabber-chat-mode-no-position () +;; (setq-local mode-line-position nil))) +;; (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 'jabber-chat-prompt-local)))))) + +;; (add-hook 'jabber-chat-mode-hook +;; (defun electric-pair-local-disable () +;; (electric-pair-local-mode -1))) + +;; (when (fboundp 'jabber-chat-update-focus) +;; (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus))) ;;; Dired +(keymap-global-set "C-x C-j" #'dired-jump) (with-eval-after-load 'dired (keymap-set dired-mode-map "C-j" #'dired-up-directory)) @@ -1257,6 +1368,23 @@ disconnect first." (setopt browse-url-browser-function #'eww-browse-url) +(defcustom browse-url-safe-browser-functions nil + "\"Safe\" browser functions." + :type '(repeat-function)) + +(defun browse-url-browser-function-safe-p (fn) + "Return t if FN is a \"safe\" browser function." + (memq fn (append browse-url-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))))))) + +(put 'browse-url-browser-function 'safe-local-variable + 'browse-url-browser-function-safe-p) + ;;; EWW (setopt eww-use-browse-url ".") @@ -1266,6 +1394,15 @@ disconnect first." "~/Downloads")) (setopt eww-history-limit nil) +(defun eww-readable/olivetti () + (interactive) + (olivetti-mode +1) + (eww-readable) + (eww-reload t)) + +(with-eval-after-load 'eww + (keymap-set eww-mode-map "R" #'eww-readable/olivetti)) + ;; Use Emacs bookmarks for EWW (defun bookmark-eww--make () "Make eww bookmark record." @@ -1322,9 +1459,9 @@ disconnect first." (hide-minor-mode 'browse-url-transform-mode)) (with-eval-after-load 'browse-url-transform - (setopt eww-url-transformers - '(eww-remove-tracking - browse-url-transform-url))) + (setq eww-url-transformers ; `setopt' causes a warning about custom-type + '(eww-remove-tracking + browse-url-transform-url))) ;; External browsers: firefox > chromium > chrome (setq browse-url-firefox-program @@ -1347,6 +1484,36 @@ disconnect first." (browse-url-chrome-program #'browse-url-chrome) (t #'browse-url-default-browser))) +(defmacro open-url-with (commandline &optional buffer error-buffer) + (let ((buffer (or buffer " *open-url-with*")) + (error-buffer (or error-buffer " *open-url-with/errors*"))) + `(lambda (url &rest _) + (cl-letf (((alist-get ,buffer + display-buffer-alist + nil nil #'equal) + '(display-buffer-no-window))) + (async-shell-command (format ,commandline url) + ,buffer + ,error-buffer))))) + +(defun add-browse-url-handler (regexp opener) + "Add OPENER to open REGEXP urls." + (setf/assoc browse-url-handlers + regexp + opener)) + +(add-browse-url-handler (rx (or (: ".pdf" eos) + (: ".PDF" eos))) + (open-url-with "zathura %s")) +(add-browse-url-handler (rx (or (: ".mp4" eos) + "youtube.com" + "piped.kavin.rocks")) + (open-url-with "mpv %s")) + +(when (package-ensure 'elpher) + (add-browse-url-handler (rx bos "gemini:") + #'elpher-browse-url-elpher)) + ;; Hinting at links (when (package-ensure 'link-hint) (setopt link-hint-avy-style 'at-full) @@ -1428,7 +1595,7 @@ NEW is passed to `eshell'." (pop-to-buffer buf) (eshell arg)) ;; In the eshell buffer - (unless (equal default-directory dir) + (unless (file-equal-p default-directory dir) (eshell/cd dir) (eshell-send-input) (goto-char (point-max))))) @@ -1438,7 +1605,9 @@ NEW is passed to `eshell'." (keymap-set eshell-mode-map "C-z" #'quit-window)) (when (package-ensure 'eat) - (add-hook 'eshell-first-time-mode-hook #'eat-eshell-mode)) + (add-hook 'eshell-first-time-mode-hook #'eat-eshell-mode) + (with-eval-after-load 'eat + (keymap-unset eat-eshell-semi-char-mode-map "M-o" t))) (when (package-ensure 'wiki-abbrev t) (wiki-abbrev-insinuate) @@ -1448,7 +1617,8 @@ NEW is passed to `eshell'." ;;; Dinghie (add-to-list 'mode-line-misc-info - '(buffer-ding-cookie buffer-ding-cookie) + '(buffer-ding-cookie (:propertize buffer-ding-cookie + face error)) :append) (defvar buffer-ding-timer nil @@ -1462,7 +1632,7 @@ NEW is passed to `eshell'." "Unflash the buffer after done `ding'ing." ;; (face-remap-remove-relative buffer-ding-cookie) (setq buffer-ding-cookie nil) - (force-mode-line-update)) + (force-mode-line-update t)) (defun buffer-ding () "Flash the buffer for `ding'." @@ -1481,6 +1651,9 @@ NEW is passed to `eshell'." (force-mode-line-update) (run-with-timer buffer-ding-timeout nil #'buffer-unding)))) +;; (setopt ring-bell-function (lambda () (pulse-momentary-highlight-region +;; (window-start) (window-end)))) + (setopt ring-bell-function #'buffer-ding) (add-hook 'isearch-mode-end-hook #'buffer-unding) @@ -1490,3 +1663,186 @@ NEW is passed to `eshell'." (when (or (null modes) (apply #'derived-mode-p modes)) (funcall fn))))) + +;;; Flash! + +(defun flash-region@ (orig start end &rest args) + (apply orig start end args) + (pulse-momentary-highlight-region start end)) + +(advice-add 'eval-region :around #'flash-region@) +(with-eval-after-load 'geiser + (advice-add 'geiser-eval-region :around #'flash-region@)) + +;;; KeepassXC Integration + +(when (package-ensure 'keepassxc-shim t) + (keepassxc-shim-activate)) + +;;; RCIRC + +(when (require 'rcirc) + (setopt rcirc-default-full-name user-full-name) + (setopt rcirc-default-user-name user-login-name) + (setopt rcirc-authenticate-before-join t) + (setopt rcirc-display-server-buffer nil) + (setopt rcirc-buffer-maximum-lines 1000) + (setopt rcirc-kill-channel-buffers t) + (setopt rcirc-track-ignore-server-buffer-flag t) + + ;; Theming + (setopt rcirc-prompt "%t> ") + (setopt rcirc-default-part-reason "See You Space Cowboy . . .") + (setopt rcirc-default-quit-reason "(TLS connection improperly terminated)") + (setopt rcirc-url-max-length 24) + (setopt rcirc-response-formats + '(("PRIVMSG" . "<%N> %m") + ("NOTICE" . "-%N- %m") + ("ACTION" . "* %N %m") + ("COMMAND" . "%fs%m%f-") + ("ERROR" . "%fw!!! %m") + ("FAIL" . "(%fwFAIL%f-) %m") + ("WARN" . "(%fwWARN%f-) %m") + ("NOTE" . "(%fwNOTE%f-) %m") + (t . "%fp*** %fs%n %r %m"))) + + (face-spec-set 'rcirc-nick-in-message-full-line + '((t :foreground unspecified + :background unspecified + :weight unspecified + :inherit nil))) + + (add-to-list 'rcirc-omit-responses "NAMES") + + (defun chat/setup () + (whitespace-mode -1) + (electric-pair-local-mode -1) + ;; IDK what's the deal with this + (olivetti-mode +1) + (visual-line-mode -1) + (word-wrap-whitespace-mode +1)) + + (setq rcirc-debug-flag t) + + (advice-add 'rcirc :after + (defun enable-rcirc-track-minor-mode (&rest _) + (rcirc-track-minor-mode 1))) + + (add-hook 'rcirc-mode-hook #'chat/setup) + (add-hook 'rcirc-mode-hook #'rcirc-omit-mode) + + ;; "Fix" some things + (setf rcirc-implemented-capabilities + ;; I don't use these, and they mess up display in a few of my chats + (delete "message-tags" rcirc-implemented-capabilities)) + + ;; Adding servers more better-er + (defun rcirc-add-server (name &rest spec) + "Add a server to `rcirc-server-alist' and `rcirc-authinfo' at once. +TODO: fully document" + (let ((name* (if (plist-get spec :host) + (plist-get spec :host) + name)) + (nick (or (plist-get spec :nick) + (bound-and-true-p rcirc-default-nick) + (bound-and-true-p user-login-name))) + (user-name (or (plist-get spec :user-name) + (plist-get spec :user) + (plist-get spec :nick) + (bound-and-true-p rcirc-default-user-name) + (bound-and-true-p rcirc-default-nick) + (bound-and-true-p user-login-name))) + (password (let ((password (or (plist-get spec :password) + (plist-get spec :pass)))) + ;; (cond + ;; ((functionp password) (funcall password)) + ;; ((stringp password) password)) + password + ))) + ;; Add the server to `rcirc-server-alist' + (setf (alist-get name* rcirc-server-alist nil nil #'equal) + (append + (list :nick nick + :user-name user-name) + (when password (list :password password)) + (when-let ((full-name (plist-get spec :full-name))) + (list :full-name full-name)) + (when-let ((channels (plist-get spec :channels))) + (list :channels channels)) + (when-let ((port (plist-get spec :port))) + (list :port port)) + (when-let ((encryption (plist-get spec :encryption))) + (list :encryption encryption)) + (when-let ((server-alias (or (plist-get spec :server-alias) + (and (plist-get spec :host) + name)))) + (list :server-alias server-alias)))) + ;; Add it to `rcirc-authinfo' + (when-let ((auth (plist-get spec :auth))) + (unless password (user-error "Trying to auth without a password")) + (setf (alist-get name* rcirc-authinfo nil nil #'equal) + (cl-case auth + (nickserv (list 'nickserv nick password)) + (bitlbee (list 'bitlbee nick password)) + (quakenet (list 'quakenet user-name password)) + (sasl (list 'sasl user-name password)) + ;; (chanserv) ; These two aren't supported. + ;; (certfp) + (t (user-error "Unsupported :auth type `%s'" + (plist-get plist :auth)))))) + ;; Return the server's name so that we don't leak authinfo + name)) + + (defun rcirc-resolve-passwords (&rest _) + (dolist-with-progress-reporter (s rcirc-server-alist) + "Resolving lambda passwords in `rcirc-server-alist...'" + (let ((pw (plist-get (cdr s) :password))) + (setf (plist-get (cdr s) :password) + (if (functionp pw) (funcall pw) pw)))) + (dolist-with-progress-reporter (i rcirc-authinfo) + "Resolving lambda passwords in `rcirc-authinfo...'" + (let ((pw (cadddr i))) + (setf (cadddr i) + (if-let ((s (assoc (car i) rcirc-server-alist))) + (plist-get (cdr s) :password) + (if (functionp pw) (funcall pw) pw)))))) + + (advice-add 'rcirc :before #'rcirc-resolve-passwords) + + (defun rcirc/password (&rest spec) + (lambda () (auth-info-password (car (apply #'auth-source-search spec))))) + + (setq rcirc-server-alist nil) + + (rcirc-add-server "tilde.town" + :host "localhost" :port 6969 + :channels '("#tildetown" "#newbirc") + :auth 'sasl + :password + (rcirc/password :host "localhost:6969" :user "acdw")) + ;; (rcirc-add-server "43beans.casa" + ;; :host "localhost" :port 6970 + ;; :channels '("#beans") + ;; :auth nil + ;; :password nil) + (rcirc-add-server "tilde.chat" + :host "irc.tilde.chat" :port 6697 :encryption 'tls + ;; :channels left blank. There's some kind of race + ;; condition in SASL and identd that means I authenticate + ;; before being fully authenticated? Or something. + ;; Tilde.chat's NickServ does autojoin, though, so that + ;; works out without an afternoon of debugging. + :auth 'sasl + :password (rcirc/password :host "tilde.chat" :user "acdw")) + (rcirc-add-server "m455.casa" + :port 6697 :encryption 'tls + :channels '("#basement" "#43beans") + :auth 'sasl + :password (rcirc/password :host "m455.casa" :user "acdw")) + (rcirc-add-server "libera.chat" + :host "irc.libera.chat" :port 6697 :encryption 'tls + :channels '("#emacs" "#rcirc") + :auth 'sasl + :password (rcirc/password :host "libera.chat" :user "acdw")) + ;; End of rcirc configuration. + ) -- cgit 1.4.1-21-gabe81