From 259363fd4f21d796c3c6a35be6398aed3f493a73 Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Tue, 3 Jan 2023 23:02:26 -0600 Subject: bleh --- early-init.el | 2 +- init.el | 756 ++++++++++++++++++++++++++++++------------------- lisp/+emacs.el | 6 +- lisp/+org.el | 56 ++++ lisp/acdw.el | 35 +++ lisp/dawn.el | 67 +++-- lisp/def.el | 142 ++++++++++ lisp/org-word-count.el | 297 +++++++++++++++++++ lisp/yoke.el | 111 ++++---- 9 files changed, 1099 insertions(+), 373 deletions(-) create mode 100644 lisp/def.el create mode 100644 lisp/org-word-count.el diff --git a/early-init.el b/early-init.el index c75d963..8004342 100644 --- a/early-init.el +++ b/early-init.el @@ -59,7 +59,7 @@ restore that." ;;; Set up extra load paths and functionality -(push (locate-user-emacs-file "lisp") load-path) +(push (expand-file-name (locate-user-emacs-file "lisp")) load-path) (require 'acdw) (+define-dir .etc (locate-user-emacs-file "etc") diff --git a/init.el b/init.el index c73e169..ffe7f81 100644 --- a/init.el +++ b/init.el @@ -12,13 +12,14 @@ (yoke +emacs (require* '+emacs '+window '+lisp) ;; Settings - (setf truncate-string-ellipsis "…" + (setc truncate-string-ellipsis "…" ring-bell-function #'ignore read-file-name-completion-ignore-case t comment-auto-fill-only-comments t password-cache t - eww-use-browse-url "." ; use `browse-url' in every link password-cache-expiry (* 60 60) + switch-to-buffer-in-dedicated-window 'pop + switch-to-buffer-obey-display-actions t initial-buffer-choice (defun +initial-buffer-choose () (cond ((equal (get-buffer "*Messages*") @@ -40,7 +41,7 @@ 'custom-type))))))))) (put (car var+pred) 'safe-local-variable (cdr var+pred))) ;; Keys - (define-key* (current-global-map) + (defkeys t "C-x C-k" #'kill-current-buffer "C-/" #'undo-only "C-?" #'undo-redo @@ -68,9 +69,25 @@ "M-o" #'other-window|switch-buffer "C-M-;" #'+lisp-comment-or-uncomment-sexp "C-x 5 z" #'suspend-frame - "M-@" #'dictionary-search - "C-x f" #'find-file) - (define-key* text-mode-map + "C-x f" #'find-file + "C-c t" (defmap toggle-map + "A map for toggling various settings." + "d" (defmap toggle-debug-map + "Easily toggle debug flavors." + "e" #'toggle-debug-on-error + "q" #'toggle-debug-on-quit) + "w" #'toggle-word-wrap + "t" #'toggle-truncate-lines + "c" #'column-number-mode + "l" #'line-number-mode + "v" (defmap toggle-view-map + "Easily toggle UI elements' views." + "c" #'display-fill-column-indicator-mode + "l" #'display-line-numbers-mode + "m" #'menu-bar-mode + "t" #'tool-bar-mode + "s" #'scroll-bar-mode))) + (defkeys text-mode-map "C-M-k" #'kill-paragraph "C-o" (defun open-paragraph (&optional arg) "Open a paragraph after paragraph at point. @@ -123,24 +140,63 @@ Called with prefix ARG, open a paragraph before point." (eval-after init (+custom-load-some-customizations :noerror))) -(yoke modus-themes - (setf modus-themes-bold-constructs t - modus-themes-italic-constructs t - modus-themes-headings '((1 monochrome bold italic) - (2 monochrome bold) - (3 monochrom italic) - (t monochrome))) - (cond ((require 'dawn nil :noerrer) - (add-hook* '+custom-after-load-hook - (defun dawn@custom () - (load-theme 'modus-operandi :noconfirm :noenable) - (load-theme 'modus-vivendi :noconfirm :noenable) - (dawn-schedule #'modus-themes-load-operandi - #'modus-themes-load-vivendi)))) - (:else (load-theme 'modus-operandi)))) +;; (yoke modus-themes +;; (setc modus-themes-bold-constructs t +;; modus-themes-italic-constructs t +;; modus-themes-headings '((1 monochrome bold italic) +;; (2 monochrome bold) +;; (3 monochrom italic) +;; (t monochrome))) +;; (defhook modus-themes-after-load-theme-hook +;; :name modus-monochrome +;; (modus-themes-with-colors +;; (cl-loop for x being the symbols +;; if (string-match-p "\\`font-lock-.*-face\\'" +;; (symbol-name x)) +;; do +;; (custom-set-faces +;; `(,x ((,class :foreground +;; ,(cond +;; ((memq x '(font-lock-string-face +;; font-lock-doc-face +;; font-lock-doc-markup-face)) +;; fg-special-warm) +;; ((memq x '(font-lock-warning-face)) +;; fg-lang-warning) +;; ((memq x '(font-lock-comment-face)) +;; fg-alt) +;; (:else 'unspecified)) +;; :background unspecified +;; :weight +;; ,(cond +;; ((memq x '(font-lock-keyword-face)) +;; 'bold) +;; (:else 'normal)) +;; :slant +;; ,(cond +;; ((memq x '(font-lock-doc-face +;; font-lock-comment-face)) +;; 'italic) +;; (:else 'normal)) +;; :underline +;; ,(cond +;; ((memq x '(font-lock-warning-face)) +;; t) +;; (:else nil))))))))) +;; (when (or (custom-theme-enabled-p 'modus-operandi) +;; (custom-theme-enabled-p 'modus-vivendi)) +;; (modus-monochrome)) +;; (cond ((require 'dawn nil :noerrer) +;; (defhook +custom-after-load-hook +;; :name dawn@custom +;; (load-theme 'modus-operandi :noconfirm :noenable) +;; (load-theme 'modus-vivendi :noconfirm :noenable) +;; (dawn-schedule #'modus-themes-load-operandi +;; #'modus-themes-load-vivendi))) +;; (:else (modus-themes-load-operandi)))) (yoke time - (setf display-time-mail-function + (setc display-time-mail-function (defun +notmuch-new-mail-p () (plist-get (cl-find "inbox+unread" (ignore-errors @@ -148,13 +204,16 @@ Called with prefix ARG, open a paragraph before point." :key (lambda (l) (plist-get l :name)) :test #'equal) :count)) - display-time-use-mail-icon t + display-time-use-mail-icon nil + display-time-mail-string (format "⋅ Mail (%s)" (+notmuch-new-mail-p)) read-mail-command #'+notmuch-goto display-time-format " %a %-e, %H:%M" ;; `display-time-format' makes these unnecessary, but I'll keep em display-time-24hr-format t display-time-day-and-date t display-time-default-load-average nil) + (define-advice display-time-update (:after (&rest _) update-mail-count) + (setq display-time-mail-string (format "⋅ Mail (%s)" (+notmuch-new-mail-p)))) (display-time-mode)) (yoke pita @@ -162,7 +221,7 @@ Called with prefix ARG, open a paragraph before point." (advice-add 'indent-region :before #'with-region-or-buffer)) (yoke (undo-fu-session "https://codeberg.org/ideasman42/emacs-undo-fu-session") - (setf undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'" + (setc undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'" "/git-rebase-todo\\'") undo-fu-session-directory (.etc "undo/" t) undo-fu-session-compression (cond @@ -173,19 +232,16 @@ Called with prefix ARG, open a paragraph before point." (global-undo-fu-session-mode)) (yoke whitespace - (setf whitespace-line-column nil + (setc whitespace-line-column nil whitespace-style '( face trailing tabs tab-mark indentation space-after-tab space-before-tab)) - (defun +whitespace-mode-for-writable-buffers () - "Turn on `whitespace-mode' if the buffer is writable, off otherwise." + (defhook (text-mode-hook prog-mode-hook read-only-mode-hook) + :name +whitespace-mode-for-writable-buffers + :doc "Turn on `whitespace-mode' if the buffer is writable, off otherwise." (whitespace-mode (if buffer-read-only -1 t))) - (add-hook* '(text-mode-hook - prog-mode-hook - read-only-mode-hook) - #'+whitespace-mode-for-writable-buffers) - (add-hook 'before-save-hook #'whitespace-cleanup) + (defhook before-save-hook #'whitespace-cleanup) (define-advice whitespace-cleanup (:around (fn &rest r) preserve-point) (let ((col (current-column))) (apply fn r) @@ -193,9 +249,9 @@ Called with prefix ARG, open a paragraph before point." (set-buffer-modified-p nil)))) (yoke elisp-mode - (setf eval-expression-print-length nil ; remove ellipses from `eval-expression' + (setc eval-expression-print-length nil ; remove ellipses from `eval-expression' eval-expression-print-level nil) - (define-key* '(emacs-lisp-mode-map lisp-interaction-mode-map) + (defkeys (emacs-lisp-mode-map lisp-interaction-mode-map) "C-c C-c" #'eval-defun "C-c C-k" (defun +elisp-eval-region-or-buffer () (interactive) @@ -209,10 +265,10 @@ Called with prefix ARG, open a paragraph before point." "C-c C-z" #'ielm) (define-advice eval-region (:around (fn beg end &rest args) pulse) (apply fn beg end args) - (pulse-momentary-highlight-region beg end))) + (pulse-momentary-highlight-region beg end))) (yoke isearch - (define-key* (current-global-map) + (defkeys t "C-s" #'isearch-forward-regexp "C-r" #'isearch-backward-regexp "C-M-s" #'isearch-forward @@ -222,7 +278,7 @@ Called with prefix ARG, open a paragraph before point." (require* '+ispell 'ispell) (add-hook 'before-save-hook #'+ispell-move-buffer-words-to-dir-locals-hook) - (setf ispell-program-name (or (executable-find "ispell") + (setc ispell-program-name (or (executable-find "ispell") (executable-find "aspell"))) (put 'ispell-buffer-session-localwords 'safe-local-variable #'+ispell-safe-local-p)) @@ -232,7 +288,7 @@ Called with prefix ARG, open a paragraph before point." ;; Brand new for Emacs 28: see https://ruzkuku.com/texts/emacs-mouse.html ;; Actually, look at this as well: https://www.emacswiki.org/emacs/Mouse3 (when (fboundp 'context-menu-mode) - (setf context-menu-functions '(context-menu-ffap + (setc context-menu-functions '(context-menu-ffap context-menu-region context-menu-undo ;; context-menu-dictionary @@ -246,7 +302,7 @@ Called with prefix ARG, open a paragraph before point." (yoke dired (require 'dired-x) - (setf dired-recursive-copies 'always + (setc dired-recursive-copies 'always dired-recursive-deletes 'always dired-create-destination-dirs 'always dired-do-revert-buffer t @@ -265,26 +321,25 @@ Called with prefix ARG, open a paragraph before point." dired-dwim-target t) (setq-local-hook dired-mode-hook truncate-lines t) - (define-key* (current-global-map) + (defkeys t "C-x C-j" #'dired-jump [remap list-directory] #'dired) - (eval-after dired - (define-key* dired-mode-map - "" #'dired-up-directory - "C-j" #'dired-up-directory)) - (add-hook* 'dired-mode-hook + (defkeys ((dired-mode-map :after dired)) + "" #'dired-up-directory + "C-j" #'dired-up-directory) + (defhook dired-mode-hook #'dired-hide-details-mode #'hl-line-mode)) (yoke (dired-hacks "https://github.com/Fuco1/dired-hacks") - (define-key* dired-mode-map + (defkeys dired-mode-map "TAB" #'dired-subtree-sycle "i" #'dired-subtree-toggle) - (add-hook* 'dired-mode-hook + (defhook 'dired-mode-hook #'dired-collapse-mode)) (yoke auth-source - (setf auth-sources `(default "secrets:passwords")) + (setc auth-sources `(default "secrets:passwords")) (setq-local-hook authinfo-mode-hook truncate-lines t)) @@ -299,54 +354,54 @@ Called with prefix ARG, open a paragraph before point." consult--regexp-compiler #'consult--default-regexp-compiler) (advice-add #'register-preview :override #'consult-register-window) (define-key* (current-global-map) - ;; Etc - "M-S-x" #'consult-mode-command - ;; C-c bindings (mode-specific-map) - "C-c h" #'consult-history - "C-c b" #'consult-bookmark - "C-c k" #'consult-kmacro - ;; C-x bindings (ctl-x-map) - "C-x M-:" #'consult-complex-command - "C-x b" #'consult-buffer - "C-x 4 b" #'consult-buffer-other-window - "C-x 5 b" #'consult-buffer-other-frame - ;; Custom M-# bindings for fast register access - "M-#" #'consult-register-load - "M-'" #'consult-register-store - "C-M-#" #'consult-register - ;; Other custom bindings - "M-y" #'consult-yank-pop - ;;(" a" . consult-apropos) - ;; M-g bindings (goto-map) - "M-g e" #'consult-compile-error - "M-g f" #'consult-flymake ; or consult-flycheck - "M-g g" #'consult-goto-line - "M-g M-g" #'consult-goto-line - "M-g o" #'consult-outline ; or consult-org-heading - "M-g m" #'consult-mark - "M-g k" #'consult-global-mark - "M-g i" #'consult-imenu - "M-g M-i" #'consult-imenu - "M-g I" #'consult-imenu-multi - ;; M-s bindings (search-map) - "M-s f" #'consult-find - "M-s F" #'consult-locate - "M-s g" #'consult-grep - "M-s G" #'consult-git-grep - "M-s r" #'consult-ripgrep - "M-s l" #'consult-line - "M-s L" #'consult-line-multi - "M-s m" #'consult-multi-occur - "M-s k" #'consult-keep-lines - "M-s u" #'consult-focus-lines - ;; Isearch integration - "M-s e" #'consult-isearch-history) + ;; Etc + "M-S-x" #'consult-mode-command + ;; C-c bindings (mode-specific-map) + "C-c h" #'consult-history + "C-c b" #'consult-bookmark + "C-c k" #'consult-kmacro + ;; C-x bindings (ctl-x-map) + "C-x M-:" #'consult-complex-command + "C-x b" #'consult-buffer + "C-x 4 b" #'consult-buffer-other-window + "C-x 5 b" #'consult-buffer-other-frame + ;; Custom M-# bindings for fast register access + "M-#" #'consult-register-load + "M-'" #'consult-register-store + "C-M-#" #'consult-register + ;; Other custom bindings + "M-y" #'consult-yank-pop + ;;(" a" . consult-apropos) + ;; M-g bindings (goto-map) + "M-g e" #'consult-compile-error + "M-g f" #'consult-flymake ; or consult-flycheck + "M-g g" #'consult-goto-line + "M-g M-g" #'consult-goto-line + "M-g o" #'consult-outline ; or consult-org-heading + "M-g m" #'consult-mark + "M-g k" #'consult-global-mark + "M-g i" #'consult-imenu + "M-g M-i" #'consult-imenu + "M-g I" #'consult-imenu-multi + ;; M-s bindings (search-map) + "M-s f" #'consult-find + "M-s F" #'consult-locate + "M-s g" #'consult-grep + "M-s G" #'consult-git-grep + "M-s r" #'consult-ripgrep + "M-s l" #'consult-line + "M-s L" #'consult-line-multi + "M-s m" #'consult-multi-occur + "M-s k" #'consult-keep-lines + "M-s u" #'consult-focus-lines + ;; Isearch integration + "M-s e" #'consult-isearch-history) (eval-after isearch-mode (define-key* isearch-mode-map - "M-e" #'consult-isearch-history - "M-s e" #'consult-isearch-history - "M-s l" #'consult-line - "M-s L" #'consult-line-multi)) + "M-e" #'consult-isearch-history + "M-s e" #'consult-isearch-history + "M-s l" #'consult-line + "M-s L" #'consult-line-multi)) (eval-after org (define-key org-mode-map (kbd "M-g o") #'consult-org-heading)) (eval-after consult-imenu @@ -368,19 +423,21 @@ Called with prefix ARG, open a paragraph before point." (setf resize-mini-windows 'grow-only vertico-count-format nil vertico-cycle t) - (vertico-mode)) + (vertico-mode) + (add-to-list 'load-path (expand-file-name "vertico/extensions" yoke-dir)) + (require 'vertico-directory) + (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy)) (yoke (embark "https://github.com/oantolin/embark") (require 'embark) (setf prefix-help-command #'embark-prefix-help-command embar-keymap-prompter-key ";") - (define-key* (list (current-global-map) - 'minibuffer-local-map) + (defkeys (t minibuffer-local-map) "C-." #'embark-act "M-." #'embark-dwim " B" #'embark-bindings) (define-key* embark-file-map - "l" #'vlf) + "l" #'vlf) (eval-after (embark consult) (require 'embark-consult) (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode))) @@ -391,18 +448,23 @@ Called with prefix ARG, open a paragraph before point." (yoke (wgrep "https://github.com/mhayashi1120/Emacs-wgrep") (require 'wgrep) (define-key* grep-mode-map - "C-x C-q" #'wgrep-change-to-wgrep-mode)) + "C-x C-q" #'wgrep-change-to-wgrep-mode)) (yoke (slime "https://github.com/slime/slime") :when (executable-find "sbcl") - (setf inferior-lisp-program (executable-find "sbcl")) + (setc inferior-lisp-program (executable-find "sbcl")) + (defhook lisp-mode-hook + :name slime-mode-setup + (load (expand-file-name "~/quicklisp/slime-helper.el") :noerror) + (slime-mode)) (eval-after slime - (setf slime-completion-at-point-functions + (setc slime-completion-at-point-functions (delq 'slime-c-p-c-completion-at-point slime-completion-at-point-functions)))) (yoke (puni "https://github.com/amaikinono/puni") - (define-key* puni-mode-map + (electric-pair-mode) + (defkeys puni-mode-map "C-)" #'puni-slurp-forward "C-(" #'puni-slurp-backward "C-}" #'puni-barf-forward @@ -411,16 +473,15 @@ Called with prefix ARG, open a paragraph before point." (interactive "p") (insert "()") (backward-char) - (puni-slurp-forward n))) - (electric-pair-mode) - (add-hook* '(prog-mode-hook - ielm-mode-hook - lisp-interaction-mode-hook - lisp-mode-hook scheme-mode-hook) + (ignore-errors (puni-slurp-forward n)))) + (defhook (prog-mode-hook + lisp-interaction-mode-hook emacs-lisp-mode-hook + lisp-mode-hook scheme-mode-hook + ielm-mode-hook eval-expression-minibuffer-setup-hook) #'puni-mode)) (yoke (hungry-delete "https://github.com/nflath/hungry-delete") - (setq hungry-delete-chars-to-skip " \t" + (setc hungry-delete-chars-to-skip " \t" hungry-delete-join-reluctantly nil) (eval-after hungry-delete (add-to-list* 'hungry-delete-except-modes @@ -432,15 +493,15 @@ Called with prefix ARG, open a paragraph before point." hd-fn fn) arg)) - (define-key* puni-mode-map + (defkeys puni-mode-map [remap puni-backward-delete-char] - (defun puni@hungry-delete-backward (arg) + (defun +puni|hungry-delete-backward (arg) (interactive "p") (+hungry-delete-or #'hungry-delete-backward #'puni-backward-delete-char arg)) [remap puni-forward-delete-char] - (defun puni@hungry-delete-forward (arg) + (defun +puni|hungry-delete-forward (arg) (interactive "p") (+hungry-delete-or #'hungry-delete-forward #'puni-forward-delete-char @@ -468,18 +529,18 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." :load "lisp") (dash "https://github.com/magnars/dash.el") (with-editor "https://github.com/magit/with-editor" - :load "lisp")) + :load "lisp")) (autoload #'transient--with-suspended-override "transient") (autoload #'magit "magit" nil :interactive) - (define-key* (current-global-map) + (defkeys t "C-x g" #'magit)) (yoke (git-modes "https://github.com/magit/git-modes") (require 'git-modes)) (yoke (visual-fill-column "https://codeberg.org/joostkremers/visual-fill-column") - (setq visual-fill-column-center-text t) - (add-hook* 'visual-fill-column-mode-hook #'visual-line-mode) + (setc visual-fill-column-center-text t) + (add-hook 'visual-fill-column-mode-hook #'visual-line-mode) (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)) (yoke (org "https://git.savannah.gnu.org/git/emacs/org-mode.git" @@ -487,10 +548,10 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." :depends ((org-contrib "https://git.sr.ht/~bzg/org-contrib" :load "lisp")) ;; DON'T load system org - (setq load-path + (setc load-path (cl-remove-if (lambda (path) (string-match-p "lisp/org\\'" path)) load-path)) - (setq org-adapt-indentation nil + (setc org-adapt-indentation nil org-auto-align-tags t org-archive-mark-done t org-fold-catch-invisible-edits 'show-and-error @@ -549,25 +610,27 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." ("=" org-verbatim) ("~" org-code) ("+" org-strikethrough))) - (add-hook* 'org-mode-hook + (defhook org-mode-hook #'variable-pitch-mode #'visual-fill-column-mode #'turn-off-auto-fill #'org-indent-mode #'prettify-symbols-mode - #'abbrev-mode) - (define-local-before-save-hook org-mode - (org-hide-drawer-all) - (org-align-tags 'all)) + #'abbrev-mode + (defhook ((before-save-hook nil :local)) + :name before-save@org-mode + (+org-hide-drawers-except-point) + (org-align-tags 'all))) (eval-after org (require '+org) - (define-key* org-mode-map + (org-clock-persistence-insinuate) + (+org-agenda-inhibit-hooks-mode) + (defkeys org-mode-map "C-M-k" #'kill-paragraph "C-M-t" #'transpose-paragraphs "RET" #'+org-return-dwim "S-" #'+org-table-copy-down|+org-return - "C-c C-o" #'+org-open-at-point-dwim) - (org-clock-persistence-insinuate)) + "C-c C-o" #'+org-open-at-point-dwim)) (eval-after ol ; org-link (defmacro define-org-link-type (type args &rest body) "Define an org link TYPE with ARGS that does something. @@ -583,50 +646,49 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." (define-org-link-type sms (number _)) (define-org-link-type tel (number _)))) -(yoke org-agenda nil - (setq org-agenda-skip-deadline-if-done t - org-agenda-skip-scheduled-if-done t - org-agenda-span 10 - 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 ─────────────────────────────────────────────────" - org-agenda-include-diary nil ; I use the org-diary features - org-agenda-todo-ignore-deadlines 'near - org-agenda-todo-ignore-scheduled 'future - org-agenda-include-deadlines t - org-deadline-warning-days 0 - org-agenda-show-future-repeats 'next - org-agenda-window-setup 'current-window - org-agenda-file-skip-regexp "sync-conflict") - (defcustom org-agenda-file-skip-regexp nil - "Files matching this regexp are removed from `org-agenda-files'." - :group 'org-agenda) - (define-advice org-agenda-files (:filter-return (files) skip-regexp) - (when org-agenda-file-skip-regexp - (setq files (seq-remove (lambda (file) - (string-match-p org-agenda-file-skip-regexp - file)) - files))) - files) - (setq-local-hook org-agenda-mode-hook - truncate-lines t - electric-pair-pairs (append electric-pair-pairs - (mapcar (lambda (e) - (let ((ch (string-to-char (car e)))) - (cons ch ch))) - org-emphasis-alist))) - (add-hook* 'org-agenda-mode-hook - #'hl-line-mode) - (add-hook 'org-agenda-after-show-hook #'org-narrow-to-subtree) - (define-key* (current-global-map) - "C-c c" #'org-capture - "C-c a" #'org-agenda) - (eval-after org-capture - '+org-capture)) +(yoke org-word-count ; in lisp/ + (eval-after org + (require 'org-word-count) + (add-hook 'org-mode-hook #'org-word-count-mode))) + +(yoke org-agenda + (setq org-agenda-skip-deadline-if-done t + org-agenda-skip-scheduled-if-done t + org-agenda-span 10 + 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 ─────────────────────────────────────────────────" + org-agenda-include-diary nil ; I use the org-diary features + org-agenda-todo-ignore-deadlines 'near + org-agenda-todo-ignore-scheduled 'future + org-agenda-include-deadlines t + org-deadline-warning-days 0 + org-agenda-show-future-repeats 'next + org-agenda-window-setup 'current-window + org-agenda-file-skip-regexp "sync-conflict") + (defcustom org-agenda-file-skip-regexp nil + "Files matching this regexp are removed from `org-agenda-files'." + :group 'org-agenda) + (define-advice org-agenda-files (:filter-return (files) skip-regexp) + (when org-agenda-file-skip-regexp + (setq files (seq-remove (lambda (file) + (string-match-p org-agenda-file-skip-regexp + file)) + files))) + files) + (setq-local-hook org-agenda-mode-hook + truncate-lines t) + (add-hook 'org-agenda-mode-hook #'hl-line-mode) + (add-hook 'org-agenda-after-show-hook #'org-narrow-to-subtree) + (defkeys t + "C-c c" #'org-capture + "C-c a" #'org-agenda) + (eval-after org-capture + (require '+org-capture))) (yoke ox ; org-export (eval-after org (require 'ox)) @@ -650,12 +712,13 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." :depends ((bbdb "https://git.savannah.nongnu.org/git/bbdb.git" :load "lisp") (bbdb-vcard "https://github.com/tohojo/bbdb-vcard/")) - (setf bbdb-complete-mail-allow-cycling t) - (add-hook* '+custom-after-load-hook - (defun _work@after-custom () - (require* 'private '_work) - (require* 'bbdb 'bbdb-message) - (bbdb-initialize 'gnus 'message)))) + (setf bbdb-complete-mail-allow-cycling t + bbdb-file (private/ "bbdb")) + (defhook +custom-after-load-hook + :name _work@after-custom + (require* 'private '_work) + (require* 'bbdb 'bbdb-message) + (bbdb-initialize 'gnus 'message))) (yoke (org-taskwise "https://codeberg.org/acdw/org-taskwise.el")) @@ -668,8 +731,8 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." map) "Keymap for scule twiddling.") (define-key* (current-global-map) - "M-c" scule-map - "M-u" #'universal-argument) + "M-c" scule-map + "M-u" #'universal-argument) (define-key universal-argument-map (kbd "M-u") #'universal-argument-more)) (yoke (titlecase "https://codeberg.org/acdw/titlecase.el") @@ -680,17 +743,17 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." word-boundary))) (eval-after scule (define-key* scule-map - "M-t" #'titlecase-dwim))) + "M-t" #'titlecase-dwim))) (yoke (flyspell-correct "https://github.com/duckwork/flyspell-correct") (eval-after flyspell (require* 'flyspell-correct `(+flyspell-correct ,(locate-user-emacs-file "lisp/+flyspell-correct"))) (define-key* flyspell-mode-map - "C-;" #'flyspell-correct-wrapper - "" #'+flyspell-correct-buffer - "C-," nil - "C-." nil)) + "C-;" #'flyspell-correct-wrapper + "" #'+flyspell-correct-buffer + "C-," nil + "C-." nil)) (add-hook 'org-mode-hook #'flyspell-mode) (setq flyspell-correct--cr-key ";")) @@ -699,51 +762,54 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." (f "https://github.com/rejeep/f.el") (s "https://github.com/magnars/s.el") (elisp-refs "https://github.com/Wilfred/elisp-refs")) - (define-key* (current-global-map) + (defkeys t " f" #'helpful-callable " v" #'helpful-variable " k" #'helpful-key " ." #'helpful-at-point " o" #'helpful-symbol) (unless (featurep 'info-look) - (run-with-idle-timer 1 nil (lambda () + (run-with-idle-timer 5 nil (lambda () (require 'info-look) (let ((inhibit-message t)) (info-lookup-setup-mode 'symbol 'emacs-lisp-mode))))) - (setf (alist-get "\\*helpful" display-buffer-alist nil nil #'string=) - '((display-buffer-in-side-window) - (side . bottom) - (window-height . 20)))) + (defhook window-configuration-change-hook + :name side-window-setup + (setf fit-window-to-buffer-horizontally t + (alist-get (rx (or "*helpful" "*Help" "*info")) + display-buffer-alist nil nil #'string=) + `(display-buffer-in-side-window + ,@(if (< (frame-text-width) (frame-text-height)) + '((side . bottom) (window-height . 24)) + '((side . right) (window-width . fit-window-to-buffer))))))) (yoke (hippie-completing-read "https://codeberg.org/acdw/hippie-completing-read.el") (define-key* (current-global-map) - "M-/" #'hippie-completing-read)) + "M-/" #'hippie-completing-read)) (yoke dictionary ; Comes with Emacs 29! - (setq dictionary-server (if (or (executable-find "dictd") + (defkeys (t (org-mode-map :after org)) + "C-c d" #'dictionary-search) + (defkeys ((embark-identifier-map :after embark)) + "@" #'dictionary-search) + (setc dictionary-server (if (or (executable-find "dictd") (file-exists-p "/usr/sbin/dictd")) ; oh debian "localhost" "dict.org")) (setf (alist-get "^\\*Dictionary\\*" display-buffer-alist nil nil #'string=) '((display-buffer-in-side-window) (side . bottom) - (window-height . 20))) - (eval-after org - (define-key* org-mode-map - "M-@" #'dictionary-search)) - (eval-after embark - (define-key* embark-identifier-map - "@" #'dictionary-search))) + (window-height . 20)))) (yoke (anzu "https://github.com/emacsorphanage/anzu") (require 'anzu) (global-anzu-mode) - (define-key* (current-global-map) + (defkeys t [remap query-replace] #'anzu-query-replace-regexp [remap query-replace-regexp] #'anzu-query-replace) - (define-key* isearch-mode-map + (defkeys (isearch-mode-map (isearch-mb-minibuffer-map :after isearch-mb)) [remap isearch-query-replace] #'anzu-isearch-query-replace-regexp [remap isearch-query-replace-regexp] #'anzu-isearch-query-replace) (defun anzu-qr@window (fn &rest r) @@ -768,10 +834,13 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." (funcall fn server uri)) (eval-after embark (define-key* embark-region-map - "U" #'0x0-dwim))) + "U" #'0x0-dwim))) (yoke (filldent "https://codeberg.org/acdw/filldent.el") - (define-key* (current-global-map) + (define-advice canonically-space-region (:around (orig &rest r) double-space) + (let ((sentence-end-double-space t)) + (apply orig r))) + (defkeys t "M-q" #'filldent-unfill-toggle)) (yoke (avy "https://github.com/abo-abo/avy") @@ -787,9 +856,9 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." (cdr (ring-ref avy-ring 0)))) t)) (define-key* (current-global-map) - "M-j" #'avy-goto-char-timer) + "M-j" #'avy-goto-char-timer) (define-key* isearch-mode-map - "M-j" #'avy-isearch)) + "M-j" #'avy-isearch)) (yoke (frowny "https://codeberg.org/acdw/frowny.el") (setf frowny-eyes (rx (any ":=") (opt "'") (? "-"))) @@ -847,23 +916,29 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." (eval-after elisp-mode (require 'macrostep)) (define-key* '(emacs-lisp-mode-map lisp-interaction-mode-map) - "C-c e" #'macrostep-expand)) + "C-c e" #'macrostep-expand)) + +(yoke (expand-region "https://github.com/magnars/expand-region.el") + (define-advice er/clear-history (:after (&rest _) refold-org) + (when (derived-mode-p 'org-mode) + (+org-hide-drawers-except-point) + (org-link-descriptive-ensure) + (font-lock-update))) + (defkeys t + "C-=" #'er/expand-region)) (yoke (embrace "https://github.com/cute-jumper/embrace.el") :depends ((expand-region "https://github.com/magnars/expand-region.el")) - (define-key* (current-global-map) - "C-=" #'er/expand-region - "C-," #'embrace-commander) - (eval-after org - (define-key* org-mode-map - "C-=" #'er/expand-region - "C-," #'embrace-commander)) + (defkeys t + "C-\"" #'embrace-commander) (dolist (fnhook '((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))) (apply #'add-hook fnhook)) (eval-after org + (defkeys org-mode-map + "C-\"" #'embrace-commander) (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))) @@ -880,12 +955,12 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." (forward-char 1)) (self-insert-command n ,char))))) (define-key* org-mode-map - "*" (org-insert-or-embrace "*") - "/" (org-insert-or-embrace "/") - "_" (org-insert-or-embrace "_") - "=" (org-insert-or-embrace "=") - "~" (org-insert-or-embrace "~") - "+" (org-insert-or-embrace "+")))) + "*" (org-insert-or-embrace "*") + "/" (org-insert-or-embrace "/") + "_" (org-insert-or-embrace "_") + "=" (org-insert-or-embrace "=") + "~" (org-insert-or-embrace "~") + "+" (org-insert-or-embrace "+")))) (yoke (notmuch "~/usr/share/emacs/site-lisp") (eval-after bbdb @@ -897,10 +972,13 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." notmuch-address-use-company (featurep 'company) notmuch-search-oldest-first nil notmuch-archive-tags '("-inbox" "-unread") - notmuch-draft-tags '("+draft" "-inbox" "-unread")) + notmuch-draft-tags '("+draft" "-inbox" "-unread") + mail-user-agent 'notmuch-user-agent + bbdb-mail-user-agent 'notmuch-user-agent + message-mail-user-agent t) (define-key* (current-global-map) - "C-c m" #'notmuch-mua-new-mail - "C-c n" #'+notmuch-goto) + "C-c m" #'notmuch-mua-new-mail + "C-c n" #'+notmuch-goto) ;; Reading mail (setf notmuch-show-indent-content nil) (add-hook* '(notmuch-show-mode-hook @@ -908,11 +986,15 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." #'visual-fill-column-mode) (eval-after notmuch (define-key* notmuch-search-mode-map - "RET" #'notmuch-search-show-thread - "M-RET" #'notmuch-tree-from-search-thread - "!" #'+notmuch-search-mark-spam) + "RET" #'notmuch-search-show-thread + "M-RET" #'notmuch-tree-from-search-thread + "!" #'+notmuch-search-mark-spam) (define-key* notmuch-tree-mode-map - "!" #'+notmuch-search-mark-spam-then-next)) + "!" #'+notmuch-search-mark-spam-then-next + "M-<" (notmuch-tree--define-do-in-message-window + notmuch-tree-beginning-of-message beginning-of-buffer) + "M->" (notmuch-tree--define-do-in-message-window + notmuch-tree-end-of-message end-of-buffer))) ;; Writing mail (setf message-kill-buffer-on-exit t message-auto-save-directory nil) @@ -935,7 +1017,24 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." (advice-add 'notmuch-bury-or-kill-this-buffer :after (defun +display-time@notmuch (&rest _) ;; (display-time-event-handler) - (display-time-update))) + (setq display-time-mail-string + (replace-regexp-in-string "(.*)" + (format "(%s)" (+notmuch-new-mail-p)) + display-time-mail-string)) + (display-time-update) + (force-mode-line-update))) + (add-to-list 'notmuch-message-headers "List-Post" :append) + (define-advice notmuch-mua-new-reply (:around (orig &rest r) list-aware) + "Make `notmuch-mua-new-reply' list-aware." + (let ((ml (notmuch-show-get-header :List-Post))) + (apply orig r) + (when ml + (with-buffer-modified-unmodified + (message-remove-header "To") + (message-add-header + (format "To: %s" + (replace-regexp-in-string "" "\\1" ml))) + (message-goto-body))))) (setf notmuch-saved-searches (list (list :name "inbox+unread" :query (+notmuch-query-concat @@ -1012,8 +1111,8 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." :depends ((frame-cmds "https://github.com/emacsmirror/frame-cmds") (frame-fns "https://github.com/emacsmirror/frame-fns")) (define-key* (current-global-map) - "M-+" #'zoom-frm-in - "M-_" #'zoom-frm-out)) + "M-+" #'zoom-frm-in + "M-_" #'zoom-frm-out)) (yoke (jabber "https://codeberg.org/acdw/emacs-jabber") :depends ((srv "https://github.com/legoscia/srv.el") @@ -1045,21 +1144,23 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." ;; (replace-regexp-in-string "%.*" "" s)) ;; (:else s))))) ;; jids)) - jabber-rare-time-format " - - - - - - %H:00 %F") - (defun +electric-pair-disable-local-mode () - (electric-pair-local-mode -1)) - (add-hook* '(jabber-chat-mode-hook - jabber-browse-mode-hook - jabber-roster-mode-hook - jabber-console-mode-hook) - #'visual-fill-column-mode - #'+electric-pair-disable-local-mode) + jabber-rare-time-format " - - - - - - %H:%M %F") + (defhook (jabber-chat-mode-hook + jabber-browse-mode-hook + jabber-roster-mode-hook + jabber-console-mode-hook) + :name jabber-ui-setup + (electric-pair-local-mode -1) + (auto-fill-mode -1) + #'visual-fill-column-mode) + (setq-local-hook jabber-chat-mode-hook + wrap-prefix (format "%13s" " ")) (defun +jabber-fix-keybinds-dammit () "Jabber autoloads keybinds which is really annoying." (define-key* (current-global-map) - "C-x C-j" #'dired-jump - "C-c j" jabber-global-keymap - "C-c C-SPC" #'jabber-activity-switch-to)) + "C-x C-j" #'dired-jump + "C-c j" jabber-global-keymap + "C-c C-SPC" #'jabber-activity-switch-to)) (eval-after init (+jabber-fix-keybinds-dammit)) (eval-after jabber (require 'jabber-httpupload nil :noerror) @@ -1076,25 +1177,25 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." (hi-lock-unface-buffer regexp) (highlight-regexp regexp 'hi-blue)))))) (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus) - (+jabber-fix-keybinds-dammit)) - ;; (add-hook* 'jabber-activity-mode-hook - ;; (defun +jabber-activity-mode@move-to-end-of-mode-line () - ;; (setf global-mode-string - ;; (append (delete '(t jabber-activity-mode-string) - ;; global-mode-string) - ;; '((t jabber-activity-mode-string)))))) - (setq-local-hook jabber-chat-mode-hook - wrap-prefix (format "%10s " " ") - mode-line-buffer-identification - (pcase (buffer-name) - ((rx "%") ; biboumi irc channel - ;; xmpp-muc:#scheme%irc.libera.chat@irc.hmm.st - (propertized-buffer-identification - (replace-regexp-in-string "xmpp-muc:\\([^%]*\\)%\\([^@]*\\)@.*" - "\\1@\\2" - (buffer-name)))) - (_ ; xmpp channel - (propertized-buffer-identification "%12b")))) + (+jabber-fix-keybinds-dammit) + (defkeys jabber-chat-mode-map + "C-l" (defun +jabber-recenter-last-read () + (interactive) + (cond + ((eq last-command '+jabber-recenter-last-read) + (setq this-command #'recenter) + (recenter -1)) + (:else + (save-excursion + (condition-case e + (re-search-backward jabber-last-read-marker) + (search-failed nil) + (:success + (recenter 3))))))))) + (defun jabber-chat-kill-buffers () + "Kill all `jabber-chat-mode' buffers." + (interactive) + (mapc-buffers (lambda () (message "%S" (buffer-name))) '(jabber-chat-mode))) (defun jabber-chat@after-modus-themes-load () (modus-themes-with-colors (custom-set-faces @@ -1147,26 +1248,26 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." link-hint-avy-all-windows t) (global-set-key (kbd "M-l") +link-hint-map) (define-key* +link-hint-map - "M-l" #'+link-hint-open-link "l" #'+link-hint-open-link - "M-o" #'+link-hint-open-secondary "o" #'+link-hint-open-secondary - "M-m" #'+link-hint-open-multiple-links "m" #'+link-hint-open-multiple-links - "M-w" #'link-hint-copy-link "w" #'link-hint-copy-link - "M-c" #'+link-hint-open-chrome "c" #'+link-hint-open-chrome)) + "M-l" #'+link-hint-open-link "l" #'+link-hint-open-link + "M-o" #'+link-hint-open-secondary "o" #'+link-hint-open-secondary + "M-m" #'+link-hint-open-multiple-links "m" #'+link-hint-open-multiple-links + "M-w" #'link-hint-copy-link "w" #'link-hint-copy-link + "M-c" #'+link-hint-open-chrome "c" #'+link-hint-open-chrome)) (yoke (elpher "git://thelambdalab.xyz/elpher.git") (eval-after elpher (define-key* elpher-mode-map - "l" #'elpher-back))) + "l" #'elpher-back))) (yoke (epithet "https://github.com/oantolin/epithet") - (add-hook* '(Info-selection-hook - help-mode-hook - occur-mode-hook - shell-mode-hook) + (defhook (Info-selection-hook + help-mode-hook + occur-mode-hook + shell-mode-hook) #'epithet-rename-buffer) (cond ((boundp 'eww-auto-rename-buffer) - (setf eww-auto-rename-buffer 'title)) - (:else (add-hook 'eww-after-render-hook #'epithet-rename-buffer)))) + (setc eww-auto-rename-buffer 'title)) + (:else (defhook eww-after-render-hook #'epithet-rename-buffer)))) (yoke browse-url (require '+browse-url) @@ -1183,7 +1284,12 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." (music-url-p . +browse-url-with-mpv) (image-url-p . +browse-image-with-mpv) (blobp . +browse-url-download) - (external-url-p . ,browse-url-secondary-browser-function))) + (external-url-p . ,browse-url-secondary-browser-function) + ;; HERE FOR REFERENCE --- OPEN MASTO URLS SOME WAY + (,(defun mastodon-url-p (url) + "Try to determine whether URL is a mastodon URL." + (string-match-p "/@[^/]+\\(/\\|/[[:digit:]]+\\)?$" url)) + . ,browse-url-secondary-browser-function))) (+browse-url-make-external-viewer-handler "mpv" '("--cache-pause-wait=30" "--cache-pause-initial=yes") "Video URL: " @@ -1197,17 +1303,19 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." url)) (defun music-url-p (url) "Is URL music?" (string-match-p (rx "soundcloud.com" "bandcamp.com" - (seq "." (or "ogg" "mp3" "opus" "m4a") eos)) + (seq "." (or "ogg" "mp3" "opus" "m4a" "flac") eos)) url)) (defun image-url-p (url) "Is URL an image?" - (string-match-p (rx "." (or "jpeg" "jpg" "png" "bmp" "webp") eos) + (string-match-p (rx + (or (: "." (or "jpeg" "jpg" "png" "bmp" "webp") eos) + "pbs.twimg.com")) url)) (defun external-url-p (url) "Should URL open in an external browser?" (string-match-p (rx (or "github.com" "gitlab.com" "codeberg.org" "tildegit.org" "git.tilde.town" "google.com" "imgur.com" "twitch.tv" "pixelfed" "instagram.com" "bibliogram.art" "reddit.com" "teddit.net" - "twitter.com" "nitter" "t.co" + ;; "twitter.com" "nitter" "t.co" "streamable.com" "spotify.com" "hetzner.cloud" "melpa.org")) url)) @@ -1216,38 +1324,74 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." eos))) url)) (eval-after chd - (add-to-list 'browse-url-handlers (cons chd/url-regexps #'browse-url-chrome))) + (add-to-list 'browse-url-handlers (cons chd/url-regexps #'chd/browse-url))) (require 'browse-url-transform) - (setf browse-url-transform-alist `(("twitter\\.com" . "nitter.net") + (setf browse-url-transform-alist `(;; Privacy-respecting alternatives + ("twitter\\.com" . "nitter.snopyta.org") ("\\(?:\\(?:old\\.\\)?reddit\\.com\\)" . "libreddit.de") ("medium\\.com" . "scribe.rip") - ("www\\.npr\\.org" . "text.npr.org"))) + ;; Text-mode of non-text-mode sites + ("www\\.npr\\.org" . "text.npr.org") + ;; Ask for raw versions of paste sites + ("^.*dpaste\\.com.*$" . "\\&.txt") + ("bpa\\.st/\\(.*\\)" . "bpa.st/raw/\\1") + ("\\(paste\\.debian\\.net\\)/\\(.*\\)" + . "\\1/plain/\\2") + ("\\(pastebin\\.com\\)/\\\(.*\\)" + . "\\1/raw/\\2") + ("gist\\.github\\.com/\\(.*\\)" + . "gist.githubusercontent.com/\\1/raw/"))) (browse-url-transform-mode)) (yoke eww - (defun +eww-browse-with-external-browser (&optional url) - "Browse URL with an external browser and close eww." - (interactive nil eww-mode) - (condition-case e - ;; This is wrapped in a `condition-case' so that the eww window won't - ;; close if there's an error calling the browser. - (funcall browse-url-secondary-browser-function - (or url (plist-get eww-data :url))) - (:success - (when (null url) ; interactive - (quit-window))) - (t (signal (car e) (cdr e))))) - (define-key* eww-mode-map - "&" #'+eww-browse-with-external-browser)) + (setc eww-use-browse-url ".") + (eval-after eww + (defhook eww-mode-hook + #'visual-fill-column-mode + (defhook ((visual-fill-column-mode-hook nil :local)) + :name eww-mode-refresh@visual-fill-column + (eww-reload t))) + (defkeys eww-mode-map + "&" + (defun +eww-browse-with-external-browser (&optional url) + "Browse URL with an external browser and close eww." + (interactive nil eww-mode) + (condition-case e + ;; This is wrapped in a `condition-case' so that the eww window + ;; won't close if there's an error calling the browser. + (funcall browse-url-secondary-browser-function + (or url (plist-get eww-data :url))) + (:success + (when (null url) ; interactive + (quit-window))) + (t (signal (car e) (cdr e))))))) + (eval-after (eww link-hint) + (defkeys eww-mode-map + "f" #'+link-hint-open-link))) (yoke tab-bar (setf tab-bar-show t global-mode-string - '((jabber-activity-mode jabber-activity-mode-string) - " ⋅" + '((jabber-activity-mode + (:eval + (let ((str (or (bound-and-true-p jabber-activity-mode-string) + ""))) + (concat (truncate-string-to-width str 20 nil nil t) + (if (< 0 (length str)) " ⋅" ""))))) display-time-string "|")) + (eval-after jabber + (defhook jabber-activity-mode-hook + (setf global-mode-string + '((jabber-activity-mode + (:eval + (let ((str (or (bound-and-true-p jabber-activity-mode-string) + ""))) + (concat (truncate-string-to-width str 20 nil nil t) + (if (< 0 (length str)) " ⋅" ""))))) + display-time-string + "|")))) (add-to-list 'tab-bar-format 'tab-bar-format-align-right :append) (add-to-list 'tab-bar-format 'tab-bar-format-global :append) (tab-bar-mode)) @@ -1257,3 +1401,27 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive." :depends ((tablist "https://github.com/politza/tablist/")) :when (executable-find "epdfinfo") ; installed from Debian repos (pdf-tools-install)) + +(yoke which-function + (setf (alist-get 'which-function-mode mode-line-misc-info) + '((which-func-mode ; Only display if buffer supports it + (:eval (when (which-function) + (list "" which-func-format " ")))))) + (which-function-mode)) + +(yoke (zzz-to-char "https://github.com/mrkkrp/zzz-to-char") + :depends ((avy "https://github.com/abo-abo/avy")) + (setf zzz-to-char-reach 120) + (defkeys t + [remap zap-to-char] + (defun +zzz-to-char (&optional prefix) + "Run `zzz-up-to-char', or `zzz-to-char' with PREFIX." + (interactive "P") + (call-interactively (cond (prefix #'zzz-to-char) + (:else #'zzz-up-to-char)))))) + +(yoke sh-mode + (defhook sh-mode-hook + :name turn-off-sh-electric-here-document-mode + (sh-electric-here-document-mode -1))) + diff --git a/lisp/+emacs.el b/lisp/+emacs.el index 870e4e2..97377a3 100644 --- a/lisp/+emacs.el +++ b/lisp/+emacs.el @@ -55,7 +55,7 @@ Do this only if the buffer is not visiting a file." cursor-type 'bar custom-file (.etc "custom.el") delete-old-versions t - echo-keystrokes 0.1 + echo-keystrokces 0.1 ediff-window-setup-function 'ediff-setup-windows-plain eldoc-echo-area-use-multiline-p nil eldoc-idle-delay 0.1 @@ -103,7 +103,7 @@ Do this only if the buffer is not visiting a file." ;; 'command-completion-default-include-p) ;; 'command-completion-default-include-p) read-process-output-max 1048576 ; We’re in the future man. Set that to at least a megabyte - recenter-positions '(top middle bottom) + recenter-positions '(top 2 middle bottom) regexp-search-ring-max 100 regexp-search-ring-max 200 save-interprogram-paste-before-kill t @@ -239,7 +239,7 @@ spaces. If N is negative, it will not delete newlines and leave N spaces. See docstring of `cycle-spacing' for the meaning of PRESERVE-NL-BACK and MODE." (interactive "*p") - (cycle-spacing (- n) preserve-nl-back mode)) + (cycle-spacing (- n))) (defun +save-buffers-quit (&optional arg) "Silently save each buffer, then kill the current connection. diff --git a/lisp/+org.el b/lisp/+org.el index 70962d6..7698ec9 100644 --- a/lisp/+org.el +++ b/lisp/+org.el @@ -208,4 +208,60 @@ and POST-PROCESS are passed to `org-export-to-file'." ;; `org-verbatim' and `org-code' are apparently already things, so we skip them ;; here. +;;; Inhibit hooks on `org-agenda' +;; It's really annoying when I call `org-agenda' and five hundred Ispell +;; processes are created because I have `flyspell-mode' in the hook. This mode +;; inhibits those hooks when entering the agenda, but runs them when opening the +;; actual buffer. + +(defun +org-agenda-inhibit-hooks (fn &rest r) + "Advice to inhibit hooks when entering `org-agenda'." + (let ((org-mode-hook nil)) + (apply fn r))) + +(defvar-local +org-hook-has-run-p nil + "Whether `org-mode-hook' has run in the current buffer.") + +(defun +org-agenda-switch-run-hooks (&rest _) + "Advice to run `org-mode-hook' when entering org-mode. +This should only fire when switching to a buffer from `org-agenda'." + (unless +org-hook-has-run-p + (run-hooks 'org-mode-hook) + (setq +org-hook-has-run-p t))) + +(define-minor-mode +org-agenda-inhibit-hooks-mode + "Inhibit `org-mode-hook' when opening `org-agenda'." + :lighter " A/h" + :global t + (cond (+org-agenda-inhibit-hooks-mode + (advice-add 'org-agenda :around #'+org-agenda-inhibit-hooks) + (advice-add 'org-agenda-switch-to :after #'+org-agenda-switch-run-hooks)) + (:else + (advice-remove 'org-agenda #'+org-agenda-inhibit-hooks) + (advice-remove 'org-agenda-switch-to #'+org-agenda-switch-run-hooks)))) + +;;; Drawers +(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)))))))) + + (provide '+org) diff --git a/lisp/acdw.el b/lisp/acdw.el index 75e1755..a9ef893 100644 --- a/lisp/acdw.el +++ b/lisp/acdw.el @@ -1,6 +1,8 @@ ;;; acdw.el -- bits and bobs -*- lexical-binding: t; -*- ;; by C. Duckworth (require 'cl-lib) +;; def.el is here +(require 'def) ;;; Define both a directory and a function expanding to a file in that directory @@ -197,6 +199,22 @@ If body executes without errors, MESSAGE...Done will be displayed." `(let* ((,this ,(car clauses))) (if ,this ,this (either ,@(cdr clauses))))))) +(defun mapc-buffers (fn &optional pred) + "Perform FN on buffers matching PRED. +If PRED is nil or absent, perform FN on all buffers. Both FN and +PRED are called within a `with-current-buffer' form and without +arguments." + (let ((pred (cond + ((listp pred) + (lambda () (apply #'derived-mode-p pred))) + ((functionp pred) pred) + ((null pred) (lambda () t)) + (:else (user-error "Bad predicate"))))) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when (funcall pred) + (funcall fn)))))) + ;; https://emacs.stackexchange.com/a/39324/37239 ;; XXX: This shit don't work rn (defun ignore-invisible-overlays (fn) @@ -233,5 +251,22 @@ When called with prefix ARG, unconditionally switch buffer." (switch-to-buffer (other-buffer) nil t) (other-window 1))) +;;; Set variables more better-er +;; Now this doesn't do `setf'-style stuff. + +(defmacro setc (&rest args) + "Customize user options using ARGS like `setq'." + (declare (debug setq)) + (unless (zerop (mod (length args) 2)) + (user-error "Dangling argument: %S" var)) + (let (form) + (while args + (push `(customize-set-variable + ',(pop args) + ,(pop args) + "Set by `setc'.") + form)) + `(progn ,@(nreverse form)))) + (provide 'acdw) ;;; acdw.el ends here diff --git a/lisp/dawn.el b/lisp/dawn.el index 806c422..30aab7c 100644 --- a/lisp/dawn.el +++ b/lisp/dawn.el @@ -1,4 +1,13 @@ -;;; dawn.el --- Do things at dawn (and dusk) -*- lexical-binding: t; -*- +;;; dawn.el --- Lightweight dawn/dusk task scheduling -*- lexical-binding: t; -*- + +;; Copyright (C) 2022 Case Duckworth + +;; Author: Case Duckworth +;; Maintainer: Case Duckworth +;; URL: https://codeberg.org/acdw/dusk.el +;; Version: 0.3.0 +;; Keywords: calendar, themes, convenience +;; Package-Requires: ((emacs "24.3")) ;;; Commentary: @@ -12,6 +21,8 @@ (require 'cl-lib) (require 'solar) +;;; Timers + (defvar dawn--dawn-timer nil "Timer for dawn-command.") @@ -21,16 +32,19 @@ (defvar dawn--reset-timer nil "Timer to reset dawn at midnight.") +;;; Functions + (defun dawn-encode-time (f) - "Encode fractional time F." - (let ((hhmm (cl-floor f)) - (date (cdddr (decode-time)))) - (encode-time - (append (list 0 - (round (* 60 (cadr hhmm))) - (car hhmm) - ) - date)))) + "Encode fractional time F. +If F is nil, return nil." + (when f + (let ((hhmm (cl-floor f)) + (date (cdddr (decode-time)))) + (encode-time + (append (list 0 + (round (* 60 (cadr hhmm))) + (car hhmm)) + date))))) (defun dawn-midnight () "Return the time of the /next/ midnight." @@ -46,22 +60,34 @@ "Return the time of today's sunset." (dawn-encode-time (caadr (solar-sunrise-sunset (calendar-current-date))))) +;;; Interface + +;;;###autoload (defun dawn-schedule (dawn-command dusk-command) "Run DAWN-COMMAND at sunrise, and DUSK-COMMAND at dusk. -RESET is an argument for internal use." +Requires `calendar-longitude' and `calendar-latitude' to be set; +if they're not, it will prompt the user for them or error." (when (or (null calendar-longitude) - (null calendar-latitude)) - (user-error "`dawn' won't work without setting %s!" - (cond ((and (null calendar-longitude) - (null calendar-latitude)) - "`calendar-longitude' and `calendar-latitude'") - ((null calendar-longitude) - "`calendar-longitude'") - ((null calendar-latitude) - "`calendar-latitude'")))) + (null calendar-latitude)) + (or (solar-setup) + (user-error "`dawn' won't work without setting %s!" + (cond ((and (null calendar-longitude) + (null calendar-latitude)) + "`calendar-longitude' and `calendar-latitude'") + ((null calendar-longitude) + "`calendar-longitude'") + ((null calendar-latitude) + "`calendar-latitude'"))))) (let ((dawn (dawn-sunrise)) (dusk (dawn-sunset))) (cond + ((or (null dawn) (null dusk)) + ;; There is no sunrise or sunset, due to how close we are to the poles. + ;; In this case, we must figure out whether it's day or night. + (pcase (caddr (solar-sunrise-sunset (calendar-current-date))) + ("0:00" (funcall dusk-command)) ; 0 hours of daylight + ("24:00" (funcall dawn-command)) ; 24 hours of daylight + )) ((time-less-p nil dawn) ;; If it isn't dawn yet, it's still dark. Run DUSK-COMMAND and schedule ;; DAWN-COMMAND and DUSK-COMMAND for later. @@ -76,7 +102,6 @@ RESET is an argument for internal use." (t ;; Otherwise, it's past dusk, so run DUSK-COMMAND. (funcall dusk-command))) ;; Schedule a reset at midnight, to re-calculate dawn/dusk times. - ;(unless reset) (run-at-time (dawn-midnight) nil #'dawn-schedule dawn-command dusk-command))) diff --git a/lisp/def.el b/lisp/def.el new file mode 100644 index 0000000..0bf91b2 --- /dev/null +++ b/lisp/def.el @@ -0,0 +1,142 @@ +;;; def.el --- defining macros -*- lexical-binding: t; -*- + +;;; Code: + +(require 'cl-lib) + +;;; Utility + +(defun def--assert-args (pred args &optional error-type &rest error-args) + "Assert that ARGS follows PRED. +If it doesn't, raise an error. ERROR-TYPE will be the type of +that error (defaults to `user-error'), and it and ERROR-ARGS are +passed in a list to `signal'." + (unless (funcall pred args) + (funcall #'signal + (or error-type 'user-error) + (or error-args + (list "Wrong arguments" args))))) + +(defmacro o (&rest fns) + "Compose FNS into a new function for one argument." + (if (null fns) + `(lambda (&rest args) args) + `(lambda (&rest args) + (apply + #',(car fns) + (ensure-list (apply (o ,@(cdr fns)) args)))))) + +;; TODO: I need to figure out where this function goes. +(defun def--delete2 (list &rest elems) + "Delete each element of ELEMS, and the next item, from LIST." + (let ((r nil)) + (while (consp list) + (if (member (car list) elems) + (setf list (cdr list)) + (setf r (cons (car list) r))) + (setf list (cdr list))) + (reverse r))) + +;;; Keybindings + +(defmacro defkeys (maps &rest bindings) + "Define key BINDINGS in MAPS. +If MAPS is nil or t, bind to `current-global-map'. Otherwise, +bind each of BINDINGS to the map or list of maps provided. + +BINDINGS is a `setq'-style list of pairs of keys and definitions. +The key part of each binding can be a string, in which case it's +passed to `kbd', or a vector or anything else `define-key' +accepts in the KEY position. The definition part, likewise, can +be any form `define-key' accepts in that position, with this +addition: if the form is a `defun' form, it will be defined +before any keys are bound." + (declare (indent 1)) + (def--assert-args (o cl-evenp length) bindings + 'wrong-number-of-arguments 'defkeys 'evenp (length bindings)) + `(progn + ,@(cl-loop + for map in (ensure-list maps) + for first-map-p = t then nil + append + (cl-loop + for (keys def) on bindings by #'cddr + for defp = (memq (car-safe def) '(defmap defun defmacro)) + if (and defp first-map-p) collect def into defuns + append + (cl-loop + for key in (ensure-list keys) + collect (list 'define-key + (if (memq map '(t nil)) + '(current-global-map) + (or (car-safe map) map)) + (if (stringp key) + `(kbd ,key) + key) + (if defp + (cl-case (car def) + ((defmap) (cadr def)) + ((defun defmacro) `#',(cadr def)) + (otherwise (error "Bad def type: %S" + (car def)))) + def))) + into keydefs + finally return + (let ((all (append defuns keydefs))) + (if-let ((after (plist-get (cdr-safe map) :after))) + `((eval-after ,after + ,@all)) + all)))))) + +(defmacro defmap (name docstring &rest bindings) + "Define a keymap named NAME, with BINDINGS." + (declare (indent 1) (doc-string 2)) + `(,(if (boundp name) 'setq 'defvar) ,name + ;;; ^ probably a terrible hack + (let ((map (make-sparse-keymap))) + (defkeys map ,@bindings) + map) + ,@(unless (boundp name) (list docstring)))) + +;;; Hooks + +(defmacro defhook (hooks &rest body) + "Define a function to hook into HOOKS. +NAME and ARGS are passed to the generated `defun' form. +Each hook in HOOKS can be the name of a hook or a list of the form +(HOOK DEPTH LOCAL), where each argument is the same as in +`add-hook'." + (declare (indent 1)) + (let* ((name (or (plist-get body :name) + (intern (format "%s/h" + (mapconcat + (lambda (h) + (string-remove-suffix + "-hook" (symbol-name (or (car-safe h) + h)))) + (ensure-list hooks) + "|"))))) + (args (or (plist-get body :args) nil)) + (doc (or (plist-get body :doc) nil)) + (forms ; (DEFUN . FUNCS) + (cl-loop for form in (def--delete2 body :name :args :doc) + if (eq (car form) 'function) + collect form into funcs + else collect form into defuns + finally return (cons defuns funcs))) + (defun-forms (car forms)) + (func-forms (cdr forms))) + `(progn + ,@(when defun-forms + `((defun ,name ,args ,@(when doc (list doc)) ,@defun-forms))) + ,@(cl-loop for hook in (ensure-list hooks) + for h = (or (car-safe hook) hook) + for ha = (cdr-safe hook) + if defun-forms + collect `(add-hook ',h #',name ,@ha) + append + (cl-loop for fn in func-forms + collect `(add-hook ',h ,fn ,@ha)))))) + +(provide 'def) +;;; def.el ends here diff --git a/lisp/org-word-count.el b/lisp/org-word-count.el new file mode 100644 index 0000000..d6d2598 --- /dev/null +++ b/lisp/org-word-count.el @@ -0,0 +1,297 @@ +;;; org-word-count.el --- org-word-count in the modeline -*- lexical-binding: t; -*- + +;;; Commentary: + +;;; Code: + +(require 'org) +(require 'cl-lib) + +(defgroup org-word-count nil + "Extra fast word-counting in `org-mode'." + :group 'org) + +(defvar-local org-word-count-word-count nil + "Running total of words in this buffer.") + +(defvar-local org-word-count-string nil + "String for the modeline.") + +(defcustom org-word-count-format "%sw " + "Format for org word count in modeline." + :type 'string) + +(defcustom org-word-count-huge-string "huge" + "String to display with a huge buffer." + :type 'string) + +(defcustom org-word-count-update-after-funcs '(org-narrow-to-subtree + org-narrow-to-block + org-narrow-to-element + org-capture-narrow) + "Functions after which to update the word count." + :type '(repeat function)) + +(defcustom org-word-count-deletion-idle-timer 0.25 + "Length of time, in seconds, to wait before updating word-count." + :type 'number) + +(defcustom org-word-count-huge-change 5000 + "Number of characters that constitute a \"huge\" insertion." + :type 'number) + +(defcustom org-word-count-huge-buffer 10000 + "Number of words past which we're not going to try to count." + :type 'number) + +(defvar org-word-count-correction -5 + "Number to add to `org-word-count-word-count', for some reason? +`org-word-count-word-count' seems to consistently be off by 5. Thus +this correction. (At some point I should correct the underlying +code... probably).") + +(defvar-local org-word-count-update-timer nil) + +;;; Variables from org-wc + +(defun org-word-count-list-of-strings-p (arg) + (cl-every #'stringp arg)) + +(defun org-word-count--downcase-list-of-strings-set-default (var val) + (set-default var (mapcar #'downcase val))) + +(defcustom org-word-count-ignored-tags '("nowc" "noexport" "ARCHIVE") + "List of tags for which subtrees will be ignored in word counts" + :type '(repeat string) + :safe #'org-word-count-list-of-strings-p) + +(defcustom org-word-count-ignore-commented-trees t + "Ignore trees with COMMENT-prefix if non-nil." + :type 'boolean + :safe #'booleanp) + +(defcustom org-word-count-default-link-count 'description-or-path + "Default way of counting words in links. +This is applied to any link type not specified in any of +‘org-word-count-ignored-link-types’,‘org-word-count-one-word-link-types’, or +‘org-word-count-only-description-link-types’ " + :type '(choice + (const :tag "Count words in description or else path part of links" description-or-path) + (const :tag "Count words only in description part of links" description) + (const :tag "Count links as 0 words" ignore) + (const :tag "Count links as 1 word" oneword) + (const :tag "Count words only in path part of links" path)) + :safe 'symbolp) + +(defcustom org-word-count-ignored-link-types nil + "Link types which won't be counted as a word" + :type '(repeat string) + :safe #'org-word-count-list-of-strings-p) + +(defcustom org-word-count-one-word-link-types '("zotero") + "Link types which will be counted as one word" + :type '(repeat string) + :safe #'org-word-count-list-of-strings-p) + +(defcustom org-word-count-description-or-path-link-types '() + "Link types for which the description or the path should be counted" + :type '(repeat string) + :safe #'org-word-count-list-of-strings-p) + +(defcustom org-word-count-only-description-link-types '("note") + "Link types for which only the description should be counted" + :type '(repeat string) + :safe #'org-word-count-list-of-strings-p) + +(defcustom org-word-count-only-path-link-types '() + "Link types for which only the path should be counted" + :type '(repeat string) + :safe #'org-word-count-list-of-strings-p) + +(defcustom org-word-count-blocks-to-count '("quote" "verse") + "List of blocks which should be included in word count. + +Use lower case block names" + :type '(repeat string) + :safe #'org-word-count-list-of-strings-p + :set #'org-word-count--downcase-list-of-strings-set-default) + +(defun org-word-count-delayed-update (&rest _) + (if org-word-count-update-timer + (setq org-word-count-update-timer nil) + (setq org-word-count-update-timer + (run-with-idle-timer org-word-count-deletion-idle-timer nil + #'org-word-count-update)))) + +(defun org-word-count-force-update () + (interactive) + (message "Counting words...") + (when (timerp org-word-count-update-timer) + (cancel-timer org-word-count-update-timer)) + (org-word-count-update) + (message "Counting words...done")) + +(defun org-word-count-update (&rest _) ; Needs variadic parameters, since it's advice + (dlet ((org-word-count-counting t)) + (org-word-count-buffer) + (org-word-count-modeline) + (setq org-word-count-update-timer nil))) + +(defun org-word-count-changed (start end length) + (org-word-count-delayed-update)) + +(defun org-word-count-buffer () + "Count the words in the buffer." + (when (and (derived-mode-p 'org-mode) + (not (eq org-word-count-word-count 'huge))) + (setq org-word-count-word-count + (cond + ((> (count-words (point-min) (point-max)) + org-word-count-huge-buffer) + 'huge) + (t (org-word-count-aux (point-min) (point-max))))))) + +;;; From org-wc.el: +;; https://github.com/tesujimath/org-wc/ +(defun org-word-count-aux (beg end) + "Return the number of words between BEG and END." + (let ((wc 0) + subtreecount + (latex-macro-regexp "\\\\[A-Za-z]+\\(\\[[^]]*\\]\\|\\){\\([^}]*\\)}")) + (save-excursion + (goto-char beg) + ;; Handle the case where we start in a drawer + (when (org-at-drawer-p) + (org-end-of-meta-data t)) + (while (< (point) end) + (cond + ;; Handle headlines and subtrees + ((org-at-heading-p) + (cond + ;; Ignore commented and org-wc-ignored-tags trees + ((or (and org-word-count-ignore-commented-trees (org-in-commented-heading-p)) + (cl-intersection org-word-count-ignored-tags (org-get-tags) :test #'string=)) + (org-end-of-subtree t t)) + ;; Re-use count for subtrees already counted + ((setq subtreecount (get-text-property (point) :org-wc)) + (cl-incf wc subtreecount) + (org-end-of-subtree t t)) + ;; Skip counting words in headline + (t (org-word-count--goto-char (point-at-eol) end)))) + ;; Ignore most blocks. + ((when (save-excursion + (beginning-of-line 1) + (looking-at org-block-regexp)) + (if (member (downcase (match-string 1)) org-word-count-blocks-to-count) + (progn ;; go inside block and subtract count of end line + (org-word-count--goto-char (match-beginning 4) end) + (cl-decf wc)) + (org-word-count--goto-char (match-end 0) end)))) + ;; Ignore comments. + ((org-at-comment-p) + (org-word-count--goto-char (point-at-eol) end)) + ;; Ignore drawers. + ((org-at-drawer-p) + (org-end-of-meta-data t)) + ;; Ignore all other #+ lines + ((looking-at "#+") + (org-word-count--goto-char (point-at-eol) end)) + ;; Handle links + ((save-excursion + (when (< (1+ (point-min)) (point)) (backward-char 2)) + (looking-at org-link-bracket-re)) + (let* ((type (car (save-match-data (split-string (match-string 1) ":")))) + (pathstart (+ 1 (length type) (match-beginning 1)))) + (cl-case (cond ((member type org-word-count-ignored-link-types) 'ignore) + ((member type org-word-count-one-word-link-types) 'oneword) + ((member type org-word-count-only-description-link-types) + 'description) + ((member type org-word-count-only-path-link-types) 'path) + ((member type org-word-count-description-or-path-link-types) + 'description-or-path) + (t org-word-count-default-link-count)) + (ignore (org-word-count--goto-char (match-end 0) end)) + (oneword (org-word-count--goto-char (match-end 0) end) + (cl-incf wc)) + (description (if (match-beginning 2) + (goto-char (match-beginning 2)) + (org-word-count--goto-char + (match-end 0) end))) + (path (cl-incf wc (count-words-region pathstart + (match-end 1))) + (org-word-count--goto-char (match-end 0) end)) + (description-or-path + (if (match-beginning 2) + (goto-char (match-beginning 2)) + (cl-incf wc (count-words-region pathstart + (match-end 1))) + (org-word-count--goto-char (match-end 0) end))) + (t (user-error "Error in org-word-count link configuration"))))) + ;; Count latex macros as 1 word, ignoring their arguments. + ((save-excursion + (when (< (point-min) (point)) (backward-char)) + (looking-at latex-macro-regexp)) + (org-word-count--goto-char (match-end 0) end) + (cl-incf wc)) + (t + (and (re-search-forward "\\w+\\W*" end 'skip) + (cl-incf wc)))))) + wc)) + +(defun org-word-count--goto-char (char end) + "Moves point to CHAR and from there passes 0+ non-word characters. +Searchers to end as a maximum. + +This ensures that we are in an expected state (at the first word +character after some non-word characters) after moving beyond +headlines, links etc." + (goto-char char) + (re-search-forward "\\W*" end 'skip)) + +(defvar org-word-count-counting nil + "Are we currently counting?") + +(defun org-word-count-recount-widen (&rest _) + (when (and (not org-word-count-counting)) + (org-word-count-update))) + +(defun org-word-count-modeline () + (setq org-word-count-string + (cond + ((eq org-word-count-word-count 'huge) + org-word-count-huge-string) + (org-word-count-word-count + (format org-word-count-format + (max 0 (+ org-word-count-word-count + org-word-count-correction)))))) + (force-mode-line-update)) + +(define-minor-mode org-word-count-mode + "Count words in `org-mode' buffers in the mode-line." + :lighter "" + :keymap (let ((map (make-sparse-keymap))) + (define-key map (kbd "C-c C-.") #'org-word-count-force-update) + map) + (cond (org-word-count-mode + (org-word-count-buffer) + (add-hook 'after-change-functions + #'org-word-count-delayed-update nil t) + (unless (member '(org-word-count-mode org-word-count-string) + mode-line-misc-info) + (add-to-list 'mode-line-misc-info + '(org-word-count-mode org-word-count-string) + nil + #'equal)) + (dolist (fn org-word-count-update-after-funcs) + (advice-add fn :after #'org-word-count-update))) + (:else + (remove-hook 'after-change-functions + #'org-word-count-delayed-update t) + (setf mode-line-misc-info + (delete '(org-word-count-mode org-word-count-string) + mode-line-misc-info)) + (dolist (fn org-word-count-update-after-funcs) + (advice-remove fn #'org-word-count-update))))) + +(provide 'org-word-count) +;;; org-word-count.el ends here diff --git a/lisp/yoke.el b/lisp/yoke.el index f9c4d49..8ca94fd 100644 --- a/lisp/yoke.el +++ b/lisp/yoke.el @@ -84,60 +84,63 @@ Execute BODY afterward. append (list this next) into ret finally return (cond ((eq (car (last ret)) nil) (butlast ret)) - (:else ret))))) - `(cl-block ,pname - (condition-case err - (progn - ;; Pass `:when' or `:unless' clauses - ,@(cond - ((and whenp unlessp) - `((when (or (not ,when) ,unless) - (cl-return-from ,pname - (format "%s (abort) :when %S :unless %S" - ',pname ',when ',unless))))) - (whenp - `((unless ,when (cl-return-from ,pname - (format "%s (abort) :when %S" - ',pname ',when))))) - (unlessp - `((when ,unless (cl-return-from ,pname - (format "%s (abort) :unless %S" - ',pname ',unless)))))) - ;; Evaluate `:pre' forms - ,@pre - ;; Get prerequisite packages - ,@(cl-loop - for (pkg* . yoke-get-args) in depends - collect `(or - (let* ((pkg-spec (yoke-get ,@yoke-get-args - :dir ,(format "%s" pkg*))) - (dir (expand-file-name (or (plist-get (cdr pkg-spec) :load) - "") - (car pkg-spec)))) - (and dir - ,@(if autoload - `((yoke-generate-autoloads ',pkg* dir)) - '(t)) - (add-to-list 'yoke-dirs dir nil #'string=))) - (cl-return-from ,pname - (format "Error fetching prerequiste: %s" - ',pkg*)))) - ;; Download the package, generate autoloads - ,@(when url - `((let* ((pkg-spec (yoke-get ,@url :dir ,(format "%s" pkg))) - (,dirvar (expand-file-name (or (plist-get (cdr pkg-spec) :load) - "") - (car pkg-spec)))) - ,@(when autoload - `((yoke-generate-autoloads ',pkg ,dirvar))) - (add-to-list 'yoke-dirs ,dirvar nil #'string=)))) - ;; Evaluate the body, optionally after the features in `:after' - ,@(cond (after - `((yoke-eval-after ,after ,@body))) - (:else body))) - (:success ',package) - (t (message "%s: %s (%s)" ',pname (car err) (cdr err)) - nil))))) + (:else ret)))) + (r (gensym))) + `(let ((,r (cl-block ,pname +(condition-case err + (progn + ;; Pass `:when' or `:unless' clauses + ,@(cond + ((and whenp unlessp) + `((when (or (not ,when) ,unless) + (cl-return-from ,pname + (format "%s (abort) :when %S :unless %S" + ',pname ',when ',unless))))) + (whenp + `((unless ,when (cl-return-from ,pname + (format "%s (abort) :when %S" + ',pname ',when))))) + (unlessp + `((when ,unless (cl-return-from ,pname + (format "%s (abort) :unless %S" + ',pname ',unless)))))) + ;; Evaluate `:pre' forms + ,@pre + ;; Get prerequisite packages + ,@(cl-loop + for (pkg* . yoke-get-args) in depends + collect `(or + (let* ((pkg-spec (yoke-get ,@yoke-get-args + :dir ,(format "%s" pkg*))) + (dir (expand-file-name (or (plist-get (cdr pkg-spec) :load) + "") + (car pkg-spec)))) + (and dir + ,@(if autoload + `((yoke-generate-autoloads ',pkg* dir)) + '(t)) + (add-to-list 'yoke-dirs dir nil #'string=))) + (cl-return-from ,pname + (format "Error fetching prerequiste: %s" + ',pkg*)))) + ;; Download the package, generate autoloads + ,@(when url + `((let* ((pkg-spec (yoke-get ,@url :dir ,(format "%s" pkg))) + (,dirvar (expand-file-name (or (plist-get (cdr pkg-spec) :load) + "") + (car pkg-spec)))) + ,@(when autoload + `((yoke-generate-autoloads ',pkg ,dirvar))) + (add-to-list 'yoke-dirs ,dirvar nil #'string=)))) + ;; Evaluate the body, optionally after the features in `:after' + ,@(cond (after + `((yoke-eval-after ,after ,@body))) + (:else body))) + (:success ',package) + (t (message "%s: %s (%s)" ',pname (car err) (cdr err)) + nil))))) + (when (stringp ,r) (message "%S" ,r)) + ,r))) (defun yoke-get (url &rest args) "\"Get\" URL and and put it in DIR, then add DIR to `load-path'. -- cgit 1.4.1-21-gabe81