diff options
Diffstat (limited to 'modules')
35 files changed, 613 insertions, 211 deletions
diff --git a/modules/auth-config.el b/modules/auth-config.el index f18c0c1fd..62d773057 100644 --- a/modules/auth-config.el +++ b/modules/auth-config.el @@ -35,6 +35,15 @@ (require 'system-lib) (require 'user-constants) ;; defines authinfo-file, read at load time below +;; Lazily-loaded oauth2-auto / plstore internals used by the cache-fix advice +;; below. oauth2-auto is required at runtime inside the advised function; these +;; declarations satisfy the byte-compiler without forcing an eager load. +(declare-function oauth2-auto--compute-id "oauth2-auto") +(declare-function plstore-get "plstore") +(declare-function plstore-close "plstore") +(defvar oauth2-auto--plstore-cache) +(defvar oauth2-auto-plstore) + (defcustom cj/auth-source-debug-enabled nil "Non-nil means enable verbose auth-source debug logging. diff --git a/modules/browser-config.el b/modules/browser-config.el index 0312cdd18..d596b9e9d 100644 --- a/modules/browser-config.el +++ b/modules/browser-config.el @@ -145,7 +145,8 @@ Persists the choice for future sessions." (defun cj/--do-initialize-browser () "Initialize browser configuration. Returns: (cons \\='loaded browser-plist) if saved choice was loaded, - (cons \\='first-available browser-plist) if using first discovered browser, + (cons \\='first-available browser-plist) if using first + discovered browser, (cons \\='no-browsers nil) if no browsers found." (let ((saved-choice (cj/load-browser-choice))) (if saved-choice diff --git a/modules/calendar-sync.el b/modules/calendar-sync.el index 2ff535668..8d7552d3e 100644 --- a/modules/calendar-sync.el +++ b/modules/calendar-sync.el @@ -223,7 +223,7 @@ Example: -21600 for CST (UTC-6), -28800 for PST (UTC-8)." (defun calendar-sync--format-timezone-offset (offset) "Format timezone OFFSET (in seconds) as human-readable string. -Example: -21600 → 'UTC-6' or 'UTC-6:00'." +Example: -21600 → `UTC-6' or `UTC-6:00'." (if (null offset) "unknown" (let* ((hours (/ offset 3600)) @@ -289,7 +289,7 @@ Example: -21600 → 'UTC-6' or 'UTC-6:00'." "Normalize line endings in CONTENT to Unix format (LF only). Removes all carriage return characters (\\r) from CONTENT. The iCalendar format (RFC 5545) uses CRLF line endings, but Emacs -and 'org-mode' expect LF only. This function ensures consistent line +and `org-mode' expect LF only. This function ensures consistent line endings throughout the parsing pipeline. Returns CONTENT with all \\r characters removed." @@ -423,14 +423,16 @@ Handles both simple values and values with parameters like TZID." (defun calendar-sync--get-recurrence-id-line (event-str) "Extract full RECURRENCE-ID line from EVENT-STR, including parameters. -Returns the complete line like 'RECURRENCE-ID;TZID=Europe/Tallinn:20260203T170000'. +Returns the complete line like +`RECURRENCE-ID;TZID=Europe/Tallinn:20260203T170000'. Returns nil if not found." (when (and event-str (stringp event-str)) (calendar-sync--get-property-line event-str "RECURRENCE-ID"))) (defun calendar-sync--parse-ics-datetime (value) "Parse iCal datetime VALUE into (year month day hour minute) list. -Returns nil for invalid input. For date-only values, returns (year month day nil nil). +Returns nil for invalid input. For date-only values, returns +(year month day nil nil). Handles formats: 20260203T090000Z, 20260203T090000, 20260203." (when (and value (stringp value) @@ -493,7 +495,8 @@ start time fail to parse. The plist holds :recurrence-id (localized), (defun calendar-sync--collect-recurrence-exceptions (ics-content) "Collect all RECURRENCE-ID events from ICS-CONTENT. Returns hash table mapping UID to list of exception event plists. -Each exception plist contains :recurrence-id (parsed), :start, :end, :summary, etc." +Each exception plist contains :recurrence-id (parsed), :start, :end, +:summary, etc." (let ((exceptions (make-hash-table :test 'equal))) (when (and ics-content (stringp ics-content)) (dolist (event-str (calendar-sync--split-events ics-content)) @@ -571,7 +574,8 @@ Returns new list with matching occurrences replaced by exception times." (defun calendar-sync--get-exdates (event-str) "Extract all EXDATE values from EVENT-STR. -Returns list of datetime strings (without TZID parameters), or nil if none found. +Returns list of datetime strings (without TZID parameters), or nil if +none found. Handles both simple values and values with parameters like TZID." (when (and event-str (stringp event-str) (not (string-empty-p event-str))) (let ((exdates '()) @@ -584,7 +588,8 @@ Handles both simple values and values with parameters like TZID." (defun calendar-sync--get-exdate-line (event-str exdate-value) "Find the full EXDATE line containing EXDATE-VALUE from EVENT-STR. -Returns the complete line like 'EXDATE;TZID=America/New_York:20260210T130000'. +Returns the complete line like +`EXDATE;TZID=America/New_York:20260210T130000'. Returns nil if not found." (when (and event-str (stringp event-str) exdate-value) (let ((pattern (format "^\\(EXDATE[^:]*:%s\\)" (regexp-quote exdate-value)))) @@ -618,7 +623,8 @@ Converts TZID-qualified and UTC times to local time." (defun calendar-sync--exdate-matches-p (occurrence-start exdate) "Check if OCCURRENCE-START matches EXDATE. OCCURRENCE-START is (year month day hour minute). -EXDATE is (year month day hour minute) or (year month day nil nil) for date-only. +EXDATE is (year month day hour minute) or (year month day nil nil) for +date-only. Date-only EXDATE matches any time on that day." (and occurrence-start exdate (= (nth 0 occurrence-start) (nth 0 exdate)) ; year @@ -682,7 +688,8 @@ Returns nil if property not found." (defun calendar-sync--get-property-line (event property) "Extract full PROPERTY line from EVENT string, including parameters. -Returns the complete line like 'DTSTART;TZID=Europe/Lisbon:20260202T190000'. +Returns the complete line like +`DTSTART;TZID=Europe/Lisbon:20260202T190000'. Returns nil if property not found." (when (string-match (format "^\\(%s[^\n]*\\)$" (regexp-quote property)) event) (match-string 1 event))) @@ -790,8 +797,8 @@ Returns URL string or nil." (defun calendar-sync--extract-tzid (property-line) "Extract TZID parameter value from PROPERTY-LINE. -PROPERTY-LINE is like 'DTSTART;TZID=Europe/Lisbon:20260202T190000'. -Returns timezone string like 'Europe/Lisbon', or nil if no TZID. +PROPERTY-LINE is like `DTSTART;TZID=Europe/Lisbon:20260202T190000'. +Returns timezone string like `Europe/Lisbon', or nil if no TZID. Returns nil for malformed lines (missing colon separator)." (when (and property-line (stringp property-line) @@ -813,7 +820,7 @@ Returns list (year month day hour minute) in local timezone." (defun calendar-sync--convert-tz-to-local (year month day hour minute source-tz) "Convert datetime from SOURCE-TZ timezone to local time. -SOURCE-TZ is a timezone name like 'Europe/Lisbon' or 'Asia/Yerevan'. +SOURCE-TZ is a timezone name like `Europe/Lisbon' or `Asia/Yerevan'. Returns list (year month day hour minute) in local timezone, or nil on error. Uses Emacs built-in timezone support (encode-time/decode-time with ZONE @@ -837,8 +844,10 @@ TZ database as the `date' command." "Convert PARSED datetime to local time using timezone info. PARSED is (year month day hour minute) or (year month day nil nil). IS-UTC non-nil means the value had a Z suffix. + TZID is a timezone string like \"Europe/Lisbon\", or nil. -Returns PARSED converted to local time, or PARSED unchanged if no conversion needed." +Returns PARSED converted to local time, or PARSED unchanged if no +conversion needed." (cond (is-utc (calendar-sync--convert-utc-to-local @@ -856,7 +865,8 @@ Returns PARSED converted to local time, or PARSED unchanged if no conversion nee "Parse iCal timestamp string TIMESTAMP-STR. Returns (year month day hour minute) or (year month day) for all-day events. Converts UTC times (ending in Z) to local time. -If TZID is provided (e.g., 'Europe/Lisbon'), converts from that timezone to local. +If TZID is provided (e.g., `Europe/Lisbon'), converts from that timezone +to local. Returns nil if parsing fails." (cond ;; DateTime format: 20251116T140000Z or 20251116T140000 @@ -913,7 +923,8 @@ Returns string like '<2025-11-16 Sun 14:00-15:00>' or '<2025-11-16 Sun>'." (defun calendar-sync--date-to-time (date) "Convert DATE to time value for comparison. DATE should be a list starting with (year month day ...). -Only the first three elements are used; extra elements (hour, minute) are ignored." +Only the first three elements are used; extra elements (hour, minute) are +ignored." (let ((day (nth 2 date)) (month (nth 1 date)) (year (nth 0 date))) @@ -1082,7 +1093,8 @@ Returns nil if event lacks required fields (DTSTART, SUMMARY). Skips events with RECURRENCE-ID (individual instances of recurring events are handled separately via exception collection). Handles TZID-qualified timestamps by converting to local time. -Cleans text fields (description, location, summary) via `calendar-sync--clean-text'." +Cleans text fields (description, location, summary) via +`calendar-sync--clean-text'." ;; Skip individual instances of recurring events (they're collected as exceptions) (unless (calendar-sync--get-property event-str "RECURRENCE-ID") (let* ((uid (calendar-sync--get-property event-str "UID")) diff --git a/modules/calibredb-epub-config.el b/modules/calibredb-epub-config.el index 6d5963515..6c69ca0e8 100644 --- a/modules/calibredb-epub-config.el +++ b/modules/calibredb-epub-config.el @@ -77,6 +77,13 @@ (defvar calibredb-show-entry-switch) ; from calibredb-show.el (defvar calibredb-sort-by) ; from calibredb-core.el (defvar calibredb-search-filter) ; from calibredb-search.el +;; calibredb filter-state vars (set by cj/calibredb-clear-filters and friends) +(defvar calibredb-tag-filter-p) ; from calibredb-search.el +(defvar calibredb-favorite-filter-p) ; from calibredb-search.el +(defvar calibredb-author-filter-p) ; from calibredb-search.el +(defvar calibredb-date-filter-p) ; from calibredb-search.el +(defvar calibredb-format-filter-p) ; from calibredb-search.el +(defvar calibredb-search-current-page) ; from calibredb-search.el ;; -------------------------- CalibreDB Ebook Manager -------------------------- diff --git a/modules/chrono-tools.el b/modules/chrono-tools.el index 6f88b2018..744781268 100644 --- a/modules/chrono-tools.el +++ b/modules/chrono-tools.el @@ -22,6 +22,11 @@ (require 'user-constants) +;; Declared by the lazily-loaded `tmr' package; quiet the byte-compiler +;; without forcing the package to load. +(defvar tmr-sound-file) +(defvar tmr-descriptions-list) + ;; -------------------------------- Time Zones --------------------------------- (use-package time-zones diff --git a/modules/config-utilities.el b/modules/config-utilities.el index b3eec5d3d..f448327c1 100644 --- a/modules/config-utilities.el +++ b/modules/config-utilities.el @@ -21,6 +21,19 @@ (require 'find-lisp) (require 'profiler) +;; External variables referenced at runtime only (org and the native +;; compiler are loaded lazily; declare to quiet the byte-compiler). +(defvar comp-async-report-warnings-errors) +(defvar org-ts-regexp) +(defvar org-agenda-files) + +;; External functions referenced at runtime only. +(declare-function org-element-parse-buffer "org-element") +(declare-function org-element-map "org-element") +(declare-function org-element-property "org-element-ast") +(declare-function org-time-string-to-absolute "org") +(declare-function org-alert-check "org-alert") + ;;; -------------------------------- Debug Keymap ------------------------------- (defvar-keymap cj/debug-config-keymap @@ -65,13 +78,15 @@ (with-eval-after-load 'emacsql-sqlite-builtin (cl-defmethod emacsql-close :around ((connection emacsql-sqlite-builtin-connection)) - (when (oref connection handle) + ;; The class is loaded lazily, so the slot is unknown at compile time. + (when (with-no-warnings (oref connection handle)) (cl-call-next-method)))) (with-eval-after-load 'emacsql-sqlite-module (cl-defmethod emacsql-close :around ((connection emacsql-sqlite-module-connection)) - (when (oref connection handle) + ;; The class is loaded lazily, so the slot is unknown at compile time. + (when (with-no-warnings (oref connection handle)) (cl-call-next-method)))) ;;; -------------------------------- Benchmarking ------------------------------- diff --git a/modules/custom-ordering.el b/modules/custom-ordering.el index a2423742d..0a499a35a 100644 --- a/modules/custom-ordering.el +++ b/modules/custom-ordering.el @@ -49,10 +49,10 @@ buffer region and must reject an inverted one before reading it." (defun cj/--ordering-replace-region (start end insertion) "Replace the buffer text between START and END with INSERTION. -Point is left after the inserted text. Shared tail for the interactive ordering commands, -which all compute a transformed string from the original region then swap it -in. INSERTION is evaluated by the caller before this runs, so the transform -reads the pre-deletion text." +Point is left after the inserted text. Shared tail for the interactive +ordering commands, which all compute a transformed string from the +original region then swap it in. INSERTION is evaluated by the caller +before this runs, so the transform reads the pre-deletion text." (delete-region start end) (goto-char start) (insert insertion)) diff --git a/modules/diff-config.el b/modules/diff-config.el index 75869a73f..0c09b9516 100644 --- a/modules/diff-config.el +++ b/modules/diff-config.el @@ -28,6 +28,12 @@ ;;; Code: +(declare-function ediff-setup-keymap "ediff") +(declare-function ediff-next-difference "ediff") +(declare-function ediff-previous-difference "ediff") +(declare-function cj/ediff-hook "diff-config") +(declare-function winner-undo "winner") + (use-package ediff :ensure nil ;; built-in :defer t diff --git a/modules/dirvish-config.el b/modules/dirvish-config.el index 04f9ce20e..f33e8cf74 100644 --- a/modules/dirvish-config.el +++ b/modules/dirvish-config.el @@ -41,6 +41,24 @@ (declare-function cj/drill-this-file "org-drill-config") +;; Dirvish/Dired functions called from lazy-loaded packages. +(declare-function dirvish-peek-mode "dirvish") +(declare-function dirvish-side-follow-mode "dirvish") +(declare-function dirvish-quit "dirvish") +(declare-function dired-get-marked-files "dired") +(declare-function dired-dwim-target-directory "dired-aux") +(declare-function dired-get-file-for-visit "dired") +(declare-function dired-get-filename "dired") +(declare-function dired-mark "dired") +(declare-function dired-current-directory "dired") +(declare-function dired-file-name-at-point "dired-x") +(declare-function dired-find-file "dired") +(declare-function project-roots "project") + +;; External package variables referenced before their package loads. +(defvar ediff-after-quit-hook-internal) +(defvar dirvish-side-attributes) + ;; mark files in dirvish, attach in mu4e (add-hook 'dired-mode-hook 'turn-on-gnus-dired-mode) @@ -349,7 +367,8 @@ Shadows dired's `P' (`dired-do-print') with this type-aware version." (defun cj/dirvish-drill-file () "Open the Org file at point and start an `org-drill' session on it. -Bound to `S' (\"study\") in `dirvish-mode-map'; refuses anything but a `.org' file." +Bound to `S' (\"study\") in `dirvish-mode-map'; refuses anything but +a `.org' file." (interactive) (let ((file (dired-get-filename nil t))) (unless (and file (not (file-directory-p file)) (string-suffix-p ".org" file t)) diff --git a/modules/dwim-shell-config.el b/modules/dwim-shell-config.el index 230a8532c..014194c7b 100644 --- a/modules/dwim-shell-config.el +++ b/modules/dwim-shell-config.el @@ -100,6 +100,16 @@ (require 'cl-lib) (require 'system-lib) ;; cj/confirm-strong (permanent file destruction confirm) +;; Function declarations (lazily-loaded packages and sibling modules). +(declare-function dwim-shell-command-on-marked-files "dwim-shell-command") +(declare-function dwim-shell-command-read-file-name "dwim-shell-command") +(declare-function dwim-shell-command--files "dwim-shell-command") +(declare-function cj/xdg-open "external-open") +(declare-function dwim-shell-commands-menu "dwim-shell-config") + +;; Forward declaration: external variable provided by the dirvish package. +(defvar dirvish-mode-map) + ;; --------------------------- Password-file helpers --------------------------- (defun cj/dwim-shell--password-cleanup-callback (temp-file) diff --git a/modules/elfeed-config.el b/modules/elfeed-config.el index 7712f48db..7b4d7d745 100644 --- a/modules/elfeed-config.el +++ b/modules/elfeed-config.el @@ -29,21 +29,26 @@ (require 'system-lib) (require 'media-utils) +(declare-function elfeed "elfeed") +(declare-function elfeed-update "elfeed") +(declare-function elfeed-entry-link "elfeed") +(declare-function elfeed-untag "elfeed") +(declare-function elfeed-search-selected "elfeed") +(declare-function elfeed-search-tag-all "elfeed") +(declare-function elfeed-search-update-entry "elfeed") +(declare-function elfeed-search-update--force "elfeed") +(declare-function elfeed-search-untag-all-unread "elfeed") +(declare-function eww-browse-url "eww") +(declare-function eww-readable "eww") + ;; ------------------------------- Elfeed Config ------------------------------- (use-package elfeed :bind - ("M-S-r" . cj/elfeed-open) ;; was M-R (:map elfeed-show-mode-map ("w" . eww-open-in-new-buffer)) (:map elfeed-search-mode-map - ("w" . cj/elfeed-eww-open) ;; opens in eww - ("b" . cj/elfeed-browser-open) ;; opens in external browser - ("d" . cj/elfeed-youtube-dl) ;; async download with yt-dlp and tsp - ("v" . cj/play-with-video-player)) ;; async play with mpv - ("V" . cj/select-media-player) ;; Capital V to select player - ("R" . cj/elfeed-mark-all-as-read) ;; capital marks all as read, since upper case marks one as read - ("U" . cj/elfeed-mark-all-as-unread) ;; capital marks all as unread, since lower case marks one as unread + ("V" . cj/select-media-player)) ;; Capital V to select player :config (setq elfeed-db-directory (concat user-emacs-directory ".elfeed-db")) (setq-default elfeed-search-title-max-width 150) @@ -90,19 +95,22 @@ (elfeed) (elfeed-update) (elfeed-search-update--force)) +(keymap-global-set "M-S-r" #'cj/elfeed-open) ;; was M-R ;; -------------------------- Elfeed Filter Functions -------------------------- (defun cj/elfeed-mark-all-as-read () "Remove the \='unread\=' tag from all visible entries in search buffer." (interactive) - (mark-whole-buffer) + (goto-char (point-min)) + (push-mark (point-max) nil t) (elfeed-search-untag-all-unread)) (defun cj/elfeed-mark-all-as-unread () "Add the \='unread\=' tag from all visible entries in the search buffer." (interactive) - (mark-whole-buffer) + (goto-char (point-min)) + (push-mark (point-max) nil t) (elfeed-search-tag-all 'unread)) (defun cj/elfeed-set-filter-and-update (filterstring) @@ -302,5 +310,18 @@ TYPE should be either \='channel or \='playlist." (insert result)) result)) +;; --------------------------- Search-Mode Keybindings ------------------------- +;; Bound here (not in use-package :bind) because these commands are defined in +;; this file; a :bind autoload stub plus the defun triggers a "defined multiple +;; times" byte-compile warning. + +(with-eval-after-load 'elfeed + (keymap-set elfeed-search-mode-map "w" #'cj/elfeed-eww-open) ;; opens in eww + (keymap-set elfeed-search-mode-map "b" #'cj/elfeed-browser-open) ;; opens in external browser + (keymap-set elfeed-search-mode-map "d" #'cj/elfeed-youtube-dl) ;; async download with yt-dlp and tsp + (keymap-set elfeed-search-mode-map "v" #'cj/play-with-video-player) ;; async play with mpv + (keymap-set elfeed-search-mode-map "R" #'cj/elfeed-mark-all-as-read) ;; capital R marks all read (lower case marks one) + (keymap-set elfeed-search-mode-map "U" #'cj/elfeed-mark-all-as-unread)) ;; capital U marks all unread (lower case marks one) + (provide 'elfeed-config) ;;; elfeed-config.el ends here. diff --git a/modules/eww-config.el b/modules/eww-config.el index a41a9a76e..a5271f6bc 100644 --- a/modules/eww-config.el +++ b/modules/eww-config.el @@ -32,6 +32,8 @@ (require 'cl-lib) +(declare-function eww-add-bookmark "eww") + (defgroup my-eww-user-agent nil "EWW-only User-Agent management." :group 'eww) diff --git a/modules/flycheck-config.el b/modules/flycheck-config.el index 5626095c5..1afd3ae6c 100644 --- a/modules/flycheck-config.el +++ b/modules/flycheck-config.el @@ -45,6 +45,14 @@ (require 'keybindings) ;; provides cj/custom-keymap (use-package :map below) +;; ------------------------------- Declarations -------------------------------- + +(declare-function flycheck-mode "flycheck") +(declare-function flycheck-list-errors "flycheck") +(declare-function flycheck-add-mode "flycheck") +(declare-function flycheck-buffer "flycheck") +(declare-function cj/flycheck-prose-on-demand "flycheck-config") + (defun cj/prose-helpers-on () "Ensure that `abbrev-mode' and `flycheck-mode' are on in the current buffer." (interactive) diff --git a/modules/font-config.el b/modules/font-config.el index 1c431c864..3272a946e 100644 --- a/modules/font-config.el +++ b/modules/font-config.el @@ -56,6 +56,9 @@ (require 'host-environment) (require 'keybindings) ;; establishes the C-z prefix used for "C-z F" below +(defvar text-scale-mode-step) +(declare-function cj/disable-emojify-mode "font-config") + ;; ---------------------- HarfBuzz Font Cache Crash Fix ----------------------- ;; Prevents Emacs from compacting font caches during GC. Without this, GC can ;; free font cache entries that HarfBuzz still references, causing SIGSEGV diff --git a/modules/games-config.el b/modules/games-config.el index aa26d31ee..0ff01c809 100644 --- a/modules/games-config.el +++ b/modules/games-config.el @@ -25,6 +25,8 @@ (require 'user-constants) ;; org-dir +(defvar malyon-stories-directory) + (with-eval-after-load 'malyon (setq malyon-stories-directory (concat org-dir "text.games/"))) diff --git a/modules/help-utils.el b/modules/help-utils.el index f9f5d1427..3e31efffe 100644 --- a/modules/help-utils.el +++ b/modules/help-utils.el @@ -32,6 +32,10 @@ ;; ;;; Code: +;; Lazily-loaded functions referenced below. +(declare-function devdocs-go-back "devdocs") +(declare-function devdocs-go-forward "devdocs") + ;; ---------------------------------- Devdocs ---------------------------------- (use-package devdocs diff --git a/modules/httpd-config.el b/modules/httpd-config.el index c90399425..60baf7e82 100644 --- a/modules/httpd-config.el +++ b/modules/httpd-config.el @@ -19,13 +19,13 @@ (use-package simple-httpd :defer 1 :preface - (defconst wwwdir (concat user-emacs-directory "www")) - (defun check-or-create-wwwdir () - (unless (file-exists-p wwwdir) - (make-directory wwwdir))) - :init (check-or-create-wwwdir) + (defconst cj/httpd-wwwdir (concat user-emacs-directory "www")) + (defun cj/httpd-check-or-create-wwwdir () + (unless (file-exists-p cj/httpd-wwwdir) + (make-directory cj/httpd-wwwdir))) + :init (cj/httpd-check-or-create-wwwdir) :config - (setq httpd-root wwwdir) + (setq httpd-root cj/httpd-wwwdir) (setq httpd-show-backtrace-when-error t) (setq httpd-serve-files t)) diff --git a/modules/ledger-config.el b/modules/ledger-config.el index c268fa368..5b2712b57 100644 --- a/modules/ledger-config.el +++ b/modules/ledger-config.el @@ -3,6 +3,14 @@ ;;; Commentary: +;;; Code: + +;; ------------------------------- Declarations -------------------------------- + +(declare-function ledger-mode-clean-buffer "ledger-mode") +(defvar ledger-mode-map) +(defvar company-backends) + ;; -------------------------------- Ledger Mode -------------------------------- ;; edit files in ledger format @@ -16,7 +24,8 @@ (interactive) (save-excursion (when (buffer-modified-p) - (with-demoted-errors (ledger-mode-clean-buffer)) + (with-demoted-errors "Error cleaning ledger buffer: %S" + (ledger-mode-clean-buffer)) (save-buffer)))) :bind (:map ledger-mode-map diff --git a/modules/markdown-config.el b/modules/markdown-config.el index 16935425d..424c09cc8 100644 --- a/modules/markdown-config.el +++ b/modules/markdown-config.el @@ -20,14 +20,13 @@ :mode (("README\\.md\\'" . gfm-mode) ("\\.md\\'" . markdown-mode) ("\\.markdown\\'" . markdown-mode)) - :bind (:map markdown-mode-map - ("<f2>" . cj/markdown-preview)) ;; use same key as compile for consistency :init (setq markdown-command "multimarkdown")) ;; Register markdown as a known org-src-block language so `org-lint' ;; stops warning on `#+begin_src markdown ... #+end_src' and `C-c '' ;; inside such a block opens it in `markdown-mode' instead of falling ;; back to fundamental-mode. +(defvar org-src-lang-modes) (with-eval-after-load 'org (add-to-list 'org-src-lang-modes '("markdown" . markdown))) @@ -40,6 +39,8 @@ ;;;; --------------------- WIP: Markdown-Preview --------------------- +(declare-function imp--notify-clients "impatient-mode") + (defun cj/markdown-preview-server-start () "Start the simple-httpd listener that serves the live markdown preview. Idempotent: re-running while the server is already up is a no-op." @@ -75,5 +76,12 @@ lives in a separate command." (buffer-substring-no-properties (point-min) (point-max)))) (current-buffer))) +;; Bind the preview key after the defun so use-package's `:bind' autoload +;; stub doesn't collide with this file's own definition of the command +;; (that collision is the "defined multiple times" byte-compile warning). +;; Same key as compile, for consistency. +(with-eval-after-load 'markdown-mode + (keymap-set markdown-mode-map "<f2>" #'cj/markdown-preview)) + (provide 'markdown-config) ;;; markdown-config.el ends here diff --git a/modules/mousetrap-mode.el b/modules/mousetrap-mode.el index 99475fcde..3817e0081 100644 --- a/modules/mousetrap-mode.el +++ b/modules/mousetrap-mode.el @@ -67,7 +67,8 @@ Categories can be combined in profiles to allow specific interaction patterns.") "Mouse interaction profiles for different use cases. Each profile specifies which event categories are allowed. -Available categories: primary-click, secondary-click, drags, multi-clicks, scroll. +Available categories: primary-click, secondary-click, drags, +multi-clicks, scroll. Profiles: - disabled: Block all mouse events @@ -88,7 +89,7 @@ Modes not listed here will use `mouse-trap-default-profile'. When checking, the mode hierarchy is respected via `derived-mode-p'.") (defvar mouse-trap-default-profile 'disabled - "Default profile to use when current major mode is not in `mouse-trap-mode-profiles'.") + "Default profile when the major mode is not in `mouse-trap-mode-profiles'.") ;;; Keymap Builder @@ -187,6 +188,11 @@ Used via `emulation-mode-map-alists' so each buffer gets its own keymap.") ;;; Minor Mode Definition +;; Forward declaration: the minor-mode variable is defined by the +;; `define-minor-mode' form below, but referenced earlier in the lighter +;; keymap and lighter-string helpers. +(defvar mouse-trap-mode) + (defvar mouse-trap--lighter-keymap (let ((map (make-sparse-keymap))) (define-key map [mode-line mouse-1] diff --git a/modules/mu4e-org-contacts-setup.el b/modules/mu4e-org-contacts-setup.el index 034e74574..64e9a611f 100644 --- a/modules/mu4e-org-contacts-setup.el +++ b/modules/mu4e-org-contacts-setup.el @@ -7,6 +7,10 @@ ;;; Code: +(defvar mu4e-compose-complete-only-personal) +(defvar mu4e-compose-complete-only-after) +(declare-function cj/activate-mu4e-org-contacts-integration "mu4e-org-contacts-integration") + ;; Load the integration module. Activation only runs when the module loaded ;; cleanly AND mu4e is present; otherwise this file is a no-op so the rest ;; of the config can load without mu4e installed. diff --git a/modules/music-config.el b/modules/music-config.el index 7c3af0e13..0874c4982 100644 --- a/modules/music-config.el +++ b/modules/music-config.el @@ -103,6 +103,37 @@ ;; orderless never see the binding (the lexical-binding foreign-special-var trap). (defvar orderless-smart-case) (defvar emms-source-playlist-ask-before-overwrite) +(defvar emms-playlist-buffer-p) +(defvar emms-playlist-buffer) +(defvar emms-random-playlist) +(defvar emms-playlist-selected-marker) +(defvar emms-source-file-default-directory) +(defvar emms-player-mpv-parameters) +(defvar emms-player-mpv-regexp) +(defvar emms-player-playing-p) +(defvar emms-player-paused-p) +(defvar emms-playlist-mode-map) +(defvar dirvish-mode-map) + +;; Foreign functions used lazily after their packages load. +(declare-function emms-playlist-mode "emms-playlist-mode") +(declare-function emms-playlist-track-at "emms-playlist-mode") +(declare-function emms-playlist-mode-kill-track "emms-playlist-mode") +(declare-function emms-track-name "emms") +(declare-function emms-track-type "emms") +(declare-function emms-track-get "emms") +(declare-function emms-track-simple-description "emms") +(declare-function emms-playlist-current-selected-track "emms") +(declare-function emms-playlist-select "emms") +(declare-function emms-playlist-clear "emms") +(declare-function emms-playlist-save "emms-source-playlist") +(declare-function emms-start "emms") +(declare-function emms-random "emms") +(declare-function emms-next "emms") +(declare-function emms-previous "emms") +(declare-function dired-get-marked-files "dired") +(declare-function dired-get-file-for-visit "dired") +(declare-function face-remap-remove-relative "face-remap") ;;; Settings (no Customize) @@ -619,26 +650,26 @@ Initializes EMMS if needed." ;;; Dired/Dirvish integration -(with-eval-after-load 'dirvish - (defun cj/music-add-dired-selection () - "Add selected files/dirs in Dired/Dirvish to the EMMS playlist. +(defun cj/music-add-dired-selection () + "Add selected files/dirs in Dired/Dirvish to the EMMS playlist. Dirs added recursively." - (interactive) - (unless (derived-mode-p 'dired-mode) - (user-error "This command must be run in a Dired buffer")) - (cj/music--ensure-playlist-buffer) - (let ((files (if (use-region-p) - (dired-get-marked-files) - (list (dired-get-file-for-visit))))) - (when (null files) - (user-error "No files selected")) - (dolist (file files) - (cond - ((file-directory-p file) (cj/music-add-directory-recursive file)) - ((cj/music--valid-file-p file) (emms-add-file file)) - (t (message "Skipping non-music file: %s" file)))) - (message "Added %d item(s) to playlist" (length files)))) + (interactive) + (unless (derived-mode-p 'dired-mode) + (user-error "This command must be run in a Dired buffer")) + (cj/music--ensure-playlist-buffer) + (let ((files (if (use-region-p) + (dired-get-marked-files) + (list (dired-get-file-for-visit))))) + (when (null files) + (user-error "No files selected")) + (dolist (file files) + (cond + ((file-directory-p file) (cj/music-add-directory-recursive file)) + ((cj/music--valid-file-p file) (emms-add-file file)) + (t (message "Skipping non-music file: %s" file)))) + (message "Added %d item(s) to playlist" (length files)))) +(with-eval-after-load 'dirvish (keymap-set dirvish-mode-map "+" #'cj/music-add-dired-selection)) ;;; EMMS setup and keybindings @@ -680,6 +711,130 @@ Dirs added recursively." "C-; m z" "random" "C-; m x" "consume")) +;;; Playlist display helpers +;; +;; Defined at top level (not inside the `emms' use-package `:config') so the +;; byte-compiler sees them; they touch EMMS only at call time, after load. + +(defun cj/music--after-playlist-clear (&rest _) + "Forget the associated M3U file after the playlist is cleared." + (when-let ((buf (get-buffer cj/music-playlist-buffer-name))) + (with-current-buffer buf + (setq cj/music-playlist-file nil)))) + +(defun cj/music--format-duration (seconds) + "Convert SECONDS to a \"M:SS\" string." + (when (and seconds (numberp seconds) (> seconds 0)) + (format "%d:%02d" (/ seconds 60) (mod seconds 60)))) + +(defun cj/music--track-description (track) + "Return a human-readable description of TRACK. +For tagged tracks: \"Artist - Title [M:SS]\". +For file tracks without tags: filename without path or extension. +For URL tracks: decoded URL." + (let ((type (emms-track-type track)) + (title (emms-track-get track 'info-title)) + (artist (emms-track-get track 'info-artist)) + (duration (emms-track-get track 'info-playing-time)) + (name (emms-track-name track))) + (cond + ;; Tagged track with title + (title + (let ((dur-str (cj/music--format-duration duration)) + (parts '())) + (when artist (push artist parts)) + (push title parts) + (let ((desc (string-join (nreverse parts) " - "))) + (if dur-str (format "%s [%s]" desc dur-str) desc)))) + ;; File without tags — show clean filename + ((eq type 'file) + (file-name-sans-extension (file-name-nondirectory name))) + ;; URL — decode percent-encoded characters + ((eq type 'url) + (decode-coding-string (url-unhex-string name) 'utf-8)) + ;; Fallback + (t (emms-track-simple-description track))))) + +;; Multi-line header overlay +(defvar-local cj/music--header-overlay nil + "Overlay displaying the playlist header.") + +(defun cj/music--header-text () + "Build a multi-line header string for the playlist buffer overlay." + (let* ((pl-name (if cj/music-playlist-file + (file-name-sans-extension + (file-name-nondirectory cj/music-playlist-file)) + "Untitled")) + (track-count (count-lines (point-min) (point-max))) + (now-playing (cond + ((not emms-player-playing-p) "Stopped") + (emms-player-paused-p "Paused") + (t (let ((track (emms-playlist-current-selected-track))) + (if track + (cj/music--track-description track) + "Playing"))))) + (mode-indicator + (lambda (key label active) + (let ((face (if active 'cj/music-mode-on-face 'cj/music-mode-off-face))) + (propertize (format "[%s] %s" key label) 'face face))))) + (concat + (propertize "Playlist" 'face 'cj/music-header-face) + (propertize " : " 'face 'cj/music-header-face) + (propertize (format "%s (%d)" pl-name track-count) 'face 'cj/music-header-value-face) + "\n" + (propertize "Current " 'face 'cj/music-header-face) + (propertize " : " 'face 'cj/music-header-face) + (propertize now-playing 'face 'cj/music-header-value-face) + "\n" + (propertize "Mode " 'face 'cj/music-header-face) + (propertize " : " 'face 'cj/music-header-face) + (funcall mode-indicator "r" "repeat" (bound-and-true-p emms-repeat-playlist)) + " " + (funcall mode-indicator "t" "single" (bound-and-true-p emms-repeat-track)) + " " + (funcall mode-indicator "z" "random" (bound-and-true-p emms-random-playlist)) + " " + (funcall mode-indicator "x" "consume" cj/music-consume-mode) + "\n" + (propertize "Keys " 'face 'cj/music-header-face) + (propertize " : " 'face 'cj/music-header-face) + (propertize "a:add c:clear L:load S:save SPC:pause <>:skip ↑↓:move C-↑↓:reorder q:dismiss" + 'face 'cj/music-keyhint-face) + "\n\n"))) + +(defun cj/music--update-header () + "Insert or update the multi-line header overlay in the playlist buffer." + (when-let ((buf (get-buffer cj/music-playlist-buffer-name))) + (with-current-buffer buf + (unless cj/music--header-overlay + (setq cj/music--header-overlay (make-overlay (point-min) (point-min))) + (overlay-put cj/music--header-overlay 'priority 100)) + (move-overlay cj/music--header-overlay (point-min) (point-min)) + (overlay-put cj/music--header-overlay 'before-string + (cj/music--header-text))))) + +(defvar-local cj/music--bg-remap-cookie nil + "Cookie for the active-window background face remapping.") + +(defun cj/music--update-active-bg (&rest _) + "Toggle playlist buffer background based on whether its window is selected." + (when-let ((buf (get-buffer cj/music-playlist-buffer-name))) + (with-current-buffer buf + (let ((active (eq buf (window-buffer (selected-window))))) + (cond + ((and active (not cj/music--bg-remap-cookie)) + (setq cj/music--bg-remap-cookie + (face-remap-add-relative 'default :background "#1d1b19"))) + ((and (not active) cj/music--bg-remap-cookie) + (face-remap-remove-relative cj/music--bg-remap-cookie) + (setq cj/music--bg-remap-cookie nil))))))) + +(defun cj/music--setup-playlist-display () + "Set up header overlay and focus tracking in the playlist buffer." + (setq header-line-format nil) + (cj/music--update-header) + (add-hook 'window-selection-change-functions #'cj/music--update-active-bg nil t)) + (use-package emms :defer t :init @@ -704,7 +859,7 @@ Dirs added recursively." (emms-all) ;; Disable modeline display (keep modeline clean) - (emms-playing-time-disable-display) + (emms-playing-time-display-mode -1) (emms-mode-line-mode -1) ;; MPV configuration @@ -718,134 +873,16 @@ Dirs added recursively." (regexp-opt cj/music-file-extensions) "\\'\\)")) - ;; Keep cj/music-playlist-file in sync if playlist is cleared - (defun cj/music--after-playlist-clear (&rest _) - (when-let ((buf (get-buffer cj/music-playlist-buffer-name))) - (with-current-buffer buf - (setq cj/music-playlist-file nil)))) - - ;; Ensure we don't stack duplicate advice on reload + ;; Keep cj/music-playlist-file in sync if playlist is cleared. + ;; Ensure we don't stack duplicate advice on reload. (advice-remove 'emms-playlist-clear #'cj/music--after-playlist-clear) (advice-add 'emms-playlist-clear :after #'cj/music--after-playlist-clear) ;;; Playlist display ;; Track description: show "Artist - Title [M:SS]" instead of file paths - (defun cj/music--format-duration (seconds) - "Convert SECONDS to a \"M:SS\" string." - (when (and seconds (numberp seconds) (> seconds 0)) - (format "%d:%02d" (/ seconds 60) (mod seconds 60)))) - - (defun cj/music--track-description (track) - "Return a human-readable description of TRACK. -For tagged tracks: \"Artist - Title [M:SS]\". -For file tracks without tags: filename without path or extension. -For URL tracks: decoded URL." - (let ((type (emms-track-type track)) - (title (emms-track-get track 'info-title)) - (artist (emms-track-get track 'info-artist)) - (duration (emms-track-get track 'info-playing-time)) - (name (emms-track-name track))) - (cond - ;; Tagged track with title - (title - (let ((dur-str (cj/music--format-duration duration)) - (parts '())) - (when artist (push artist parts)) - (push title parts) - (let ((desc (string-join (nreverse parts) " - "))) - (if dur-str (format "%s [%s]" desc dur-str) desc)))) - ;; File without tags — show clean filename - ((eq type 'file) - (file-name-sans-extension (file-name-nondirectory name))) - ;; URL — decode percent-encoded characters - ((eq type 'url) - (decode-coding-string (url-unhex-string name) 'utf-8)) - ;; Fallback - (t (emms-track-simple-description track))))) - (setq emms-track-description-function #'cj/music--track-description) - ;; Multi-line header overlay - (defvar-local cj/music--header-overlay nil - "Overlay displaying the playlist header.") - - (defun cj/music--header-text () - "Build a multi-line header string for the playlist buffer overlay." - (let* ((pl-name (if cj/music-playlist-file - (file-name-sans-extension - (file-name-nondirectory cj/music-playlist-file)) - "Untitled")) - (track-count (count-lines (point-min) (point-max))) - (now-playing (cond - ((not emms-player-playing-p) "Stopped") - (emms-player-paused-p "Paused") - (t (let ((track (emms-playlist-current-selected-track))) - (if track - (cj/music--track-description track) - "Playing"))))) - (mode-indicator - (lambda (key label active) - (let ((face (if active 'cj/music-mode-on-face 'cj/music-mode-off-face))) - (propertize (format "[%s] %s" key label) 'face face))))) - (concat - (propertize "Playlist" 'face 'cj/music-header-face) - (propertize " : " 'face 'cj/music-header-face) - (propertize (format "%s (%d)" pl-name track-count) 'face 'cj/music-header-value-face) - "\n" - (propertize "Current " 'face 'cj/music-header-face) - (propertize " : " 'face 'cj/music-header-face) - (propertize now-playing 'face 'cj/music-header-value-face) - "\n" - (propertize "Mode " 'face 'cj/music-header-face) - (propertize " : " 'face 'cj/music-header-face) - (funcall mode-indicator "r" "repeat" (bound-and-true-p emms-repeat-playlist)) - " " - (funcall mode-indicator "t" "single" (bound-and-true-p emms-repeat-track)) - " " - (funcall mode-indicator "z" "random" (bound-and-true-p emms-random-playlist)) - " " - (funcall mode-indicator "x" "consume" cj/music-consume-mode) - "\n" - (propertize "Keys " 'face 'cj/music-header-face) - (propertize " : " 'face 'cj/music-header-face) - (propertize "a:add c:clear L:load S:save SPC:pause <>:skip ↑↓:move C-↑↓:reorder q:dismiss" - 'face 'cj/music-keyhint-face) - "\n\n"))) - - (defun cj/music--update-header () - "Insert or update the multi-line header overlay in the playlist buffer." - (when-let ((buf (get-buffer cj/music-playlist-buffer-name))) - (with-current-buffer buf - (unless cj/music--header-overlay - (setq cj/music--header-overlay (make-overlay (point-min) (point-min))) - (overlay-put cj/music--header-overlay 'priority 100)) - (move-overlay cj/music--header-overlay (point-min) (point-min)) - (overlay-put cj/music--header-overlay 'before-string - (cj/music--header-text))))) - - (defvar-local cj/music--bg-remap-cookie nil - "Cookie for the active-window background face remapping.") - - (defun cj/music--update-active-bg (&rest _) - "Toggle playlist buffer background based on whether its window is selected." - (when-let ((buf (get-buffer cj/music-playlist-buffer-name))) - (with-current-buffer buf - (let ((active (eq buf (window-buffer (selected-window))))) - (cond - ((and active (not cj/music--bg-remap-cookie)) - (setq cj/music--bg-remap-cookie - (face-remap-add-relative 'default :background "#1d1b19"))) - ((and (not active) cj/music--bg-remap-cookie) - (face-remap-remove-relative cj/music--bg-remap-cookie) - (setq cj/music--bg-remap-cookie nil))))))) - - (defun cj/music--setup-playlist-display () - "Set up header overlay and focus tracking in the playlist buffer." - (setq header-line-format nil) - (cj/music--update-header) - (add-hook 'window-selection-change-functions #'cj/music--update-active-bg nil t)) - (add-hook 'emms-playlist-mode-hook #'cj/music--setup-playlist-display) (add-hook 'emms-player-started-hook #'cj/music--record-random-history) (add-hook 'emms-player-started-hook #'cj/music--update-header) @@ -897,8 +934,6 @@ For URL tracks: decoded URL." ("S-<down>" . emms-playlist-mode-shift-track-down) ("C-<up>" . emms-playlist-mode-shift-track-up) ("C-<down>" . emms-playlist-mode-shift-track-down) - ;; Radio - ("R" . cj/music-create-radio-station) ;; Volume ("+" . emms-volume-raise) ("=" . emms-volume-raise) @@ -927,5 +962,10 @@ For URL tracks: decoded URL." (insert content)) (message "Created radio station: %s" (file-name-nondirectory file)))) +;; Bound here rather than in the emms `:bind' so use-package does not emit a +;; redundant autoload that collides with this same-file definition. +(with-eval-after-load 'emms + (keymap-set emms-playlist-mode-map "R" #'cj/music-create-radio-station)) + (provide 'music-config) ;;; music-config.el ends here diff --git a/modules/org-agenda-config-debug.el b/modules/org-agenda-config-debug.el index a9c713a13..4c1b1dd84 100644 --- a/modules/org-agenda-config-debug.el +++ b/modules/org-agenda-config-debug.el @@ -18,6 +18,9 @@ (require 'user-constants) (require 'system-lib) +(defvar org-agenda-files) +(declare-function cj/build-org-agenda-list "org-agenda-config") + ;; ---------------------------- Debug Functions -------------------------------- ;;;###autoload diff --git a/modules/org-capture-config.el b/modules/org-capture-config.el index 2f245185f..9f5bfbe7f 100644 --- a/modules/org-capture-config.el +++ b/modules/org-capture-config.el @@ -30,6 +30,7 @@ (defvar org-complex-heading-regexp-format) (declare-function cj/--drill-pick-file "org-drill-config") +(declare-function cj/org-capture--date-prefix "org-capture-config") (declare-function org-at-encrypted-entry-p "org-crypt") (declare-function org-at-heading-p "org") (declare-function org-back-to-heading "org") @@ -170,7 +171,7 @@ letter upcased: \"~/.emacs.d/\" -> \"Emacs.d\", \"~/code/duet/\" -> \"Duet\"." ROOT is the projectile project root (or nil); INBOX is the global inbox file path. Return a plist (:file F :open-work BOOL :project NAME :warn MSG): - ROOT with a todo.org -> F is that todo.org, :open-work t. -- ROOT without a todo.org -> F is INBOX, :open-work nil, :warn names the project. +- ROOT without a todo.org -> F is INBOX, :open-work nil, :warn names project. - ROOT nil -> F is INBOX, :open-work nil, :warn nil." (if (and (stringp root) (not (string-empty-p root))) (let ((todo (expand-file-name "todo.org" root)) diff --git a/modules/org-config.el b/modules/org-config.el index 8d722ad46..f316ee0df 100644 --- a/modules/org-config.el +++ b/modules/org-config.el @@ -17,6 +17,72 @@ (require 'keybindings) ;; provides cj/custom-keymap (used in :init below) +;; Declare org variables and functions used before org is loaded so this module +;; byte-compiles standalone. Plain `defvar' (no value) marks the symbol special +;; without assigning anything, so org's own defaults still apply at runtime. +(defvar org-dir) +(defvar org-mode-map) +(defvar org-mouse-map) +(defvar org-modules) +(defvar org-startup-folded) +(defvar org-cycle-open-archived-trees) +(defvar org-cycle-hide-drawers) +(defvar org-id-locations-file) +(defvar org-return-follows-link) +(defvar org-list-allow-alphabetical) +(defvar org-startup-indented) +(defvar org-adapt-indentation) +(defvar org-startup-with-inline-images) +(defvar org-image-actual-width) +(defvar org-yank-image-save-method) +(defvar org-bookmark-names-plist) +(defvar org-file-apps) +(defvar org-ellipsis) +(defvar org-hide-emphasis-markers) +(defvar org-hide-leading-stars) +(defvar org-pretty-entities) +(defvar org-pretty-entities-include-sub-superscripts) +(defvar org-fontify-emphasized-text) +(defvar org-fontify-whole-heading-line) +(defvar org-tags-column) +(defvar org-agenda-tags-column) +(defvar org-todo-keywords) +(defvar org-highest-priority) +(defvar org-lowest-priority) +(defvar org-default-priority) +(defvar org-enforce-todo-dependencies) +(defvar org-enforce-todo-checkbox-dependencies) +(defvar org-deadline-warning-days) +(defvar org-treat-insert-todo-heading-as-state-change) +(defvar org-log-into-drawer) +(defvar org-log-done) +(defvar org-use-property-inheritance) + +(declare-function org-current-level "org") +(declare-function org-add-planning-info "org") +(declare-function org-get-heading "org") +(declare-function org-edit-headline "org") +(declare-function org-priority "org") +(declare-function org-heading-components "org") +(declare-function org-todo "org") +(declare-function org-get-todo-state "org") +(declare-function org-back-to-heading "org") +(declare-function org-sort-entries "org") +(declare-function org-eval-in-calendar "org") +(declare-function org-open-at-point "org") +(declare-function org-backward-heading-same-level "org") +(declare-function org-forward-heading-same-level "org") +(declare-function org-reveal "org") +(declare-function org-show-todo-tree "org") +(declare-function org-fold-show-all "org-fold") +(declare-function outline-next-heading "outline") +(declare-function org-element-cache-reset "org-element") +(declare-function org-element-context "org-element") +(declare-function org-element-type "org-element-ast") +(declare-function org-superstar-configure-like-org-bullets "org-superstar") +(declare-function cj/--org-follow-link-same-window "org-config") +(declare-function cj/org-follow-link-at-mouse-same-window "org-config") + ;; ---------------------------- Org General Settings --------------------------- (defun cj/org-general-settings () @@ -250,14 +316,14 @@ whole row line." (keymap-set cj/org-map "<" #'cj/org-narrow-backwards) ;; Sparse trees: lowercase creates, capital of the same letter cancels. - ;; Both `S' and `T' resolve to `org-show-all' -- same cancel command, + ;; Both `S' and `T' resolve to `org-fold-show-all' -- same cancel command, ;; paired with each lowercase create so the mental model is "capital ;; cancels the lowercase command I just ran" without having to recall ;; which letter the cancel actually lives on. (keymap-set cj/org-map "s" #'org-match-sparse-tree) - (keymap-set cj/org-map "S" #'org-show-all) + (keymap-set cj/org-map "S" #'org-fold-show-all) (keymap-set cj/org-map "t" #'org-show-todo-tree) - (keymap-set cj/org-map "T" #'org-show-all) + (keymap-set cj/org-map "T" #'org-fold-show-all) (keymap-set cj/org-map "R" #'org-reveal) :bind ("C-c c" . org-capture) @@ -273,8 +339,7 @@ whole row line." ("C-c N" . org-narrow-to-subtree) ("C-c >" . cj/org-narrow-forward) ("C-c <" . cj/org-narrow-backwards) - ("C-c <ESC>" . widen) - ("C-c C-a" . cj/org-appear-toggle)) + ("C-c <ESC>" . widen)) (:map cj/org-map ("r i" . org-table-insert-row) ("r d" . org-table-kill-row) @@ -401,6 +466,11 @@ especially in tables with long URLs)." (org-appear-mode 1) (message "org-appear enabled (links/emphasis show when editing)"))) +;; Bound here (after the defun) rather than in the org use-package `:bind' so +;; the command isn't autoloaded into a stub that shadows this definition. +(with-eval-after-load 'org + (keymap-set org-mode-map "C-c C-a" #'cj/org-appear-toggle)) + ;; --------------------------------- Org-Tidy ---------------------------------- ;; Hide :PROPERTIES: drawers behind a small inline marker so headings stay @@ -444,7 +514,7 @@ with a file, the function will throw an error." "Clear the org-element cache for the current buffer or all buffers. By default, clear cache for all org buffers. With prefix argument, clear only the current buffer's cache. Useful when encountering parsing errors like -'wrong-type-argument stringp nil' during agenda generation." +\"wrong-type-argument stringp nil\" during agenda generation." (interactive) (if current-prefix-arg (if (derived-mode-p 'org-mode) diff --git a/modules/org-contacts-config.el b/modules/org-contacts-config.el index 556530eb2..64abb9fb5 100644 --- a/modules/org-contacts-config.el +++ b/modules/org-contacts-config.el @@ -22,6 +22,36 @@ (require 'user-constants) +;; Function declarations -- these live in lazily-loaded packages, so the +;; byte-compiler can't see their definitions when this module compiles +;; standalone. +(declare-function org-contacts-db "org-contacts") +(declare-function org-contacts-anniversaries "org-contacts") +(declare-function org-contacts-files "org-contacts") +(declare-function org-columns "org-colview") +(declare-function org-reveal "org") +(declare-function org-fold-show-entry "org-fold") +(declare-function org-heading-components "org") +(declare-function org-map-entries "org") +(declare-function org-entry-get "org") +(declare-function outline-next-heading "outline") +(declare-function calendar-current-date "calendar") +(declare-function mu4e-message-at-point "mu4e-message") +(declare-function mu4e-message-field "mu4e-message") +(declare-function which-key-add-key-based-replacements "which-key") + +;; External package variables referenced below; declared so the compiler +;; treats them as special rather than free. +(defvar org-capture-plist) +(defvar org-capture-templates) +(defvar mu4e~view-message) +(defvar org-agenda-include-diary) +(defvar org-agenda-custom-commands) +(defvar mu4e-org-contacts-file) +(defvar mu4e-headers-actions) +(defvar mu4e-view-actions) +(defvar mu4e-compose-complete-addresses) + ;; Set `org-contacts-files' eagerly at require time. Setting it in the ;; `use-package' form below would only apply when org-contacts loads, which is ;; deferred behind `:after (org mu4e)' -- later than the first @@ -42,10 +72,13 @@ (defun cj/org-contacts-anniversaries-safe () "Safely call org-contacts-anniversaries with required bindings." (require 'diary-lib) - ;; These need to be dynamically bound for diary functions - (defvar date) - (defvar entry) - (defvar original-date) + ;; `date', `entry', and `original-date' are diary special vars that the + ;; diary functions read dynamically. Declare them special locally; the + ;; suppressed warning is the unprefixed-name lint on these calendar names. + (with-suppressed-warnings ((lexical date entry original-date)) + (defvar date) + (defvar entry) + (defvar original-date)) (let ((date (calendar-current-date)) (entry "") (original-date (calendar-current-date))) @@ -186,9 +219,10 @@ Added: %U" (defun cj/--parse-email-string (name email-string) "Parse EMAIL-STRING and return formatted entries for NAME. -EMAIL-STRING may contain multiple emails separated by commas, semicolons, or spaces. -Returns a list of strings formatted as 'Name <email>'. -Returns nil if EMAIL-STRING is nil or contains only whitespace." +EMAIL-STRING may contain multiple emails separated by commas, +semicolons, or spaces. Returns a list of strings formatted as +\"Name <email>\". Returns nil if EMAIL-STRING is nil or contains only +whitespace." (when (and email-string (string-match-p "[^[:space:]]" email-string)) (let ((emails (split-string email-string "[,;[:space:]]+" t))) (mapcar (lambda (email) diff --git a/modules/org-noter-config.el b/modules/org-noter-config.el index 4e5bd1778..b9b7bbff2 100644 --- a/modules/org-noter-config.el +++ b/modules/org-noter-config.el @@ -39,9 +39,32 @@ ;; Forward declarations (declare-function org-id-uuid "org-id") +(declare-function org-entry-get "org") (declare-function nov-mode "ext:nov") (declare-function pdf-view-mode "ext:pdf-view") +;; pdf-tools fit commands (lazily loaded with pdf-tools) +(declare-function pdf-view-fit-width-to-window "pdf-view") +(declare-function pdf-view-fit-height-to-window "pdf-view") +(declare-function pdf-view-fit-page-to-window "pdf-view") +;; face-remap is built in but loaded lazily +(declare-function face-remap-remove-relative "face-remap") +;; org-noter session/sync/skeleton commands (lazily loaded with org-noter) +(declare-function org-noter--get-notes-window "org-noter") +(declare-function org-noter--get-doc-window "org-noter") +(declare-function org-noter-insert-note "org-noter") +(declare-function org-noter-enable-org-roam-integration "org-noter") +(declare-function org-noter-sync-next-note "org-noter") +(declare-function org-noter-sync-prev-note "org-noter") +(declare-function org-noter-sync-current-note "org-noter") +(declare-function org-noter-create-skeleton "org-noter") +(declare-function org-noter-kill-session "org-noter") +(declare-function org-noter-toggle-notes-window-location "org-noter") (defvar nov-file-name) +;; org-noter package variables assigned at session start / config time +(defvar org-noter-notes-window-location) +(defvar org-noter-use-pdftools-link-location) +(defvar org-noter-use-org-id) +(defvar org-noter-use-unique-org-id) ;;; Configuration Variables (defvar cj/org-noter-notes-directory roam-dir diff --git a/modules/org-roam-config.el b/modules/org-roam-config.el index 40df688d9..c6083c8fb 100644 --- a/modules/org-roam-config.el +++ b/modules/org-roam-config.el @@ -32,6 +32,24 @@ ;; capture template never reaches org-roam-dailies (the foreign-special-var trap). (defvar org-roam-dailies-capture-templates) +;; External variables, declared special so byte-compilation doesn't treat them +;; as free references/assignments. Owned by org and org-roam-dailies. +(defvar org-agenda-timegrid-use-ampm) +(defvar org-roam-dailies-map) +(defvar org-last-state) + +;; External functions, declared so the byte-compiler knows they're defined at +;; runtime by their respective packages. +(declare-function org-roam-node-tags "org-roam") +(declare-function org-roam-node-file "org-roam") +(declare-function org-roam-node-list "org-roam") +(declare-function org-roam-dailies--capture "org-roam-dailies") +(declare-function org-capture-get "org-capture") +(declare-function org-at-heading-p "org") +(declare-function org-heading-components "org") +(declare-function org-copy-subtree "org") +(declare-function org-cut-subtree "org") + ;; ---------------------------------- Org Roam --------------------------------- (defconst cj/--org-roam-dailies-head @@ -76,8 +94,6 @@ FILETAGS and TITLE must sit on separate lines so Org parses the :bind (("C-c n l" . org-roam-buffer-toggle) ("C-c n f" . org-roam-node-find) ("C-c n p" . cj/org-roam-find-node-project) - ("C-c n r" . cj/org-roam-find-node-recipe) - ("C-c n t" . cj/org-roam-find-node-topic) ("C-c n i" . org-roam-node-insert) ("C-c n w" . cj/org-roam-find-node-webclip) :map org-mode-map @@ -191,6 +207,11 @@ created in that subdirectory of `org-roam-directory'." (interactive) (cj/org-roam-find-node "Recipe" "r" (concat roam-dir "templates/recipe.org") "recipes/")) +;; Bound after their defuns (not in the use-package :bind) so the byte-compiler +;; doesn't see both a :bind autoload and the real defun as two definitions. +(keymap-global-set "C-c n r" #'cj/org-roam-find-node-recipe) +(keymap-global-set "C-c n t" #'cj/org-roam-find-node-topic) + ;; ---------------------- Org Capture After Finalize Hook ---------------------- (defun cj/org-roam-add-node-to-agenda-files-finalize-hook () diff --git a/modules/pdf-config.el b/modules/pdf-config.el index ca2312307..233a610d5 100644 --- a/modules/pdf-config.el +++ b/modules/pdf-config.el @@ -14,6 +14,22 @@ ;; ;;; Code: +;; ------------------------------- Declarations -------------------------------- + +(declare-function pdf-tools-install "pdf-tools") +(declare-function pdf-view-midnight-minor-mode "pdf-view") +(declare-function pdf-view-enlarge "pdf-view") +(declare-function pdf-view-shrink "pdf-view") +(declare-function pdf-view-next-page "pdf-view") +(declare-function pdf-view-previous-page "pdf-view") +(declare-function image-next-line "image-mode") +(declare-function image-previous-line "image-mode") +(declare-function image-bob "image-mode") +(declare-function image-eob "image-mode") +(declare-function org-store-link "ol") +(declare-function cj/open-file-with-command "system-utils") +(declare-function cj/org-noter-insert-note-dwim "org-noter-config") + ;; --------------------------------- PDF Tools --------------------------------- (use-package pdf-tools @@ -61,9 +77,9 @@ (define-key pdf-view-mode-map "i" #'cj/org-noter-insert-note-dwim) ;; Page change: C-up/C-down go to top of prev/next page (define-key pdf-view-mode-map (kbd "C-<down>") - (lambda () (interactive) (pdf-view-next-page-command) (image-bob))) + (lambda () (interactive) (pdf-view-next-page) (image-bob))) (define-key pdf-view-mode-map (kbd "C-<up>") - (lambda () (interactive) (pdf-view-previous-page-command) (image-eob)))) + (lambda () (interactive) (pdf-view-previous-page) (image-eob)))) ;; ------------------------------ PDF View Restore ----------------------------- diff --git a/modules/prog-general.el b/modules/prog-general.el index 968032831..99b3cbfab 100644 --- a/modules/prog-general.el +++ b/modules/prog-general.el @@ -59,13 +59,16 @@ (declare-function treesit-auto-add-to-auto-mode-alist "treesit-auto") (declare-function treesit-auto-recipe-lang "treesit-auto") (declare-function highlight-indent-guides-mode "highlight-indent-guides") +(declare-function electric-pair-default-inhibit "elec-pair") +(declare-function yas-reload-all "yasnippet") +(declare-function yas-activate-extra-mode "yasnippet") ;; Forward declarations for treesit-auto variables (defvar treesit-auto-recipe-list) +(defvar electric-pair-inhibit-predicate) ;; Forward declarations for functions defined later in this file (declare-function cj/project-switch-actions "prog-general") -(declare-function cj/deadgrep--initial-term "prog-general") (defun cj/find-project-root-file (regexp) "Return first file in the current Projectile project root matching REGEXP. diff --git a/modules/selection-framework.el b/modules/selection-framework.el index a567e8003..464654a20 100644 --- a/modules/selection-framework.el +++ b/modules/selection-framework.el @@ -26,6 +26,12 @@ ;; ;;; Code: +;; External variables and lazily-loaded functions referenced below. +(defvar xref-show-xrefs-function) +(defvar xref-show-definitions-function) +(declare-function consult-dir-projectile-dirs "consult-dir") +(declare-function prescient-persist-mode "prescient") + ;; ---------------------------------- Vertico ---------------------------------- ;; Vertical completion UI diff --git a/modules/tramp-config.el b/modules/tramp-config.el index 23010b3e4..e3b835f1f 100644 --- a/modules/tramp-config.el +++ b/modules/tramp-config.el @@ -23,6 +23,15 @@ ;;; Code: +;; Silence byte-compiler "assignment to free variable" warnings for vars +;; defined by lazily-loaded packages (tramp, dirtrack, magit). These are +;; only set inside the use-package :config block, after the package loads. +(defvar tramp-copy-size-limit) +(defvar tramp-use-ssh-controlmaster-options) +(defvar tramp-cleanup-idle-time) +(defvar dirtrack-list) +(defvar magit-git-executable) + (use-package tramp :defer .5 :ensure nil ;; built-in diff --git a/modules/user-constants.el b/modules/user-constants.el index dab12dcbe..b392212ed 100644 --- a/modules/user-constants.el +++ b/modules/user-constants.el @@ -154,15 +154,18 @@ Syncthing-synced `org-dir' — see the 2026-06-10 transport migration.") (defvar gcal-file (expand-file-name "data/gcal.org" user-emacs-directory) "The location of the org file containing Google Calendar information. -Stored in .emacs.d/data/ so each machine syncs independently from Google Calendar.") +Stored in .emacs.d/data/ so each machine syncs independently from +Google Calendar.") (defvar pcal-file (expand-file-name "data/pcal.org" user-emacs-directory) "The location of the org file containing Proton Calendar information. -Stored in .emacs.d/data/ so each machine syncs independently from Proton Calendar.") +Stored in .emacs.d/data/ so each machine syncs independently from +Proton Calendar.") (defvar dcal-file (expand-file-name "data/dcal.org" user-emacs-directory) "The location of the org file containing DeepSat Calendar information. -Stored in .emacs.d/data/ so each machine syncs independently from Google Calendar.") +Stored in .emacs.d/data/ so each machine syncs independently from +Google Calendar.") (defvar reference-file (expand-file-name "reference.org" org-dir) "The location of the org file containing reference information.") diff --git a/modules/vc-config.el b/modules/vc-config.el index 654116c59..fcca7e07b 100644 --- a/modules/vc-config.el +++ b/modules/vc-config.el @@ -27,6 +27,27 @@ (require 'user-constants) ;; provides code-dir (require 'keybindings) ;; provides cj/custom-keymap +;; Forward declaration: cj/vc-map is defined later in this file (see +;; `defvar-keymap' below) but referenced earlier in a use-package :bind form. +(defvar cj/vc-map) + +;; External package variables (assigned in :config blocks of lazily-loaded +;; packages, so not loaded at byte-compile time). +(defvar forge-pull-notifications) +(defvar forge-topic-list-limit) + +;; External package functions (from lazily-loaded packages). +(declare-function git-gutter:next-hunk "git-gutter") +(declare-function git-gutter:previous-hunk "git-gutter") +(declare-function git-timemachine--start "git-timemachine") +(declare-function git-timemachine--revisions "git-timemachine") +(declare-function git-timemachine-show-revision "git-timemachine") +(declare-function forge-current-repository "forge") +(declare-function forge-create-issue "forge") + +;; Defined later in this file; referenced earlier in `cj/git-timemachine'. +(declare-function cj/git-timemachine-show-selected-revision "vc-config") + ;; ---------------------------- Magit Configuration ---------------------------- (use-package magit diff --git a/modules/video-audio-recording.el b/modules/video-audio-recording.el index 4c934ef17..1672529f7 100644 --- a/modules/video-audio-recording.el +++ b/modules/video-audio-recording.el @@ -174,9 +174,10 @@ Checks if process is actually alive, not just if variable is set." (defun cj/recording-process-sentinel (process event) "Sentinel for recording processes — handles unexpected exits. PROCESS is the ffmpeg shell process, EVENT describes what happened. -This is called by Emacs when the process changes state (exits, is killed, etc.). -It clears the process variable and updates the modeline so the recording indicator -disappears even if the recording crashes or is killed externally." +This is called by Emacs when the process changes state (exits, is +killed, etc.). It clears the process variable and updates the modeline +so the recording indicator disappears even if the recording crashes or +is killed externally." (when (memq (process-status process) '(exit signal)) (cond ((eq process cj/audio-recording-ffmpeg-process) |
