aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/calendar-sync-source.el69
-rw-r--r--modules/flyspell-and-abbrev.el7
-rw-r--r--modules/keyboard-macros.el12
-rw-r--r--modules/lorem-optimum.el18
-rw-r--r--modules/org-babel-config.el3
-rw-r--r--modules/org-contacts-config.el37
-rw-r--r--modules/org-drill-config.el55
-rw-r--r--modules/org-export-config.el16
-rw-r--r--modules/org-reveal-config.el33
-rw-r--r--modules/org-webclipper.el20
-rw-r--r--modules/text-config.el6
11 files changed, 133 insertions, 143 deletions
diff --git a/modules/calendar-sync-source.el b/modules/calendar-sync-source.el
index 15c91c59..10dea66e 100644
--- a/modules/calendar-sync-source.el
+++ b/modules/calendar-sync-source.el
@@ -138,39 +138,25 @@ Checks `cj/debug-modules' for symbol `calendar-sync' or t (all)."
;;; .ics Fetch
-(defun calendar-sync--fetch-ics (url callback)
- "Fetch .ics file from URL asynchronously using curl.
-Calls CALLBACK with the .ics content as string (normalized to Unix line endings)
-or nil on error. CALLBACK signature: (lambda (content) ...).
-
-The fetch happens asynchronously and doesn't block Emacs. The callback is
-invoked when the fetch completes, either successfully or with an error."
- (condition-case err
- (let ((buffer (generate-new-buffer " *calendar-sync-curl*")))
- (make-process
- :name "calendar-sync-curl"
- :buffer buffer
- :command (list "curl" "-s" "-L" "--fail"
- "--connect-timeout" "10"
- "--max-time" (number-to-string calendar-sync-fetch-timeout)
- url)
- :sentinel
- (lambda (process event)
- (when (memq (process-status process) '(exit signal))
- (let ((buf (process-buffer process)))
- (when (buffer-live-p buf)
- (let ((content
- (with-current-buffer buf
- (if (and (eq (process-status process) 'exit)
- (= (process-exit-status process) 0))
- (calendar-sync--normalize-line-endings (buffer-string))
- (calendar-sync--log-silently "calendar-sync: Fetch error: curl failed: %s" (string-trim event))
- nil))))
- (kill-buffer buf)
- (funcall callback content))))))))
- (error
- (calendar-sync--log-silently "calendar-sync: Fetch error: %s" (error-message-string err))
- (funcall callback nil))))
+(defun calendar-sync--fetch-sentinel-finish (success event temp-file buffer callback)
+ "Finish an async .ics fetch.
+SUCCESS is non-nil when curl exited cleanly, EVENT the process event
+string, TEMP-FILE the curl output path, BUFFER the process buffer, and
+CALLBACK the continuation. On success CALLBACK receives TEMP-FILE (the
+caller owns deleting it); on failure the error is logged, TEMP-FILE is
+removed, and CALLBACK receives nil. Extracted from the sentinel so the
+success, failure, and cleanup branches are unit-testable without a live
+curl process."
+ (when (buffer-live-p buffer)
+ (unless success
+ (calendar-sync--log-silently "calendar-sync: Fetch error: curl failed: %s"
+ (string-trim event)))
+ (kill-buffer buffer))
+ (if success
+ (funcall callback temp-file)
+ (when (file-exists-p temp-file)
+ (delete-file temp-file))
+ (funcall callback nil)))
(defun calendar-sync--fetch-ics-file (url callback)
"Fetch .ics from URL to a temp file asynchronously.
@@ -190,19 +176,10 @@ owns deleting the temp file after a successful callback."
:sentinel
(lambda (process event)
(when (memq (process-status process) '(exit signal))
- (let ((buf (process-buffer process))
- (success (and (eq (process-status process) 'exit)
- (= (process-exit-status process) 0))))
- (when (buffer-live-p buf)
- (unless success
- (calendar-sync--log-silently "calendar-sync: Fetch error: curl failed: %s"
- (string-trim event)))
- (kill-buffer buf))
- (if success
- (funcall callback temp-file)
- (when (file-exists-p temp-file)
- (delete-file temp-file))
- (funcall callback nil)))))))
+ (calendar-sync--fetch-sentinel-finish
+ (and (eq (process-status process) 'exit)
+ (= (process-exit-status process) 0))
+ event temp-file (process-buffer process) callback)))))
(error
(calendar-sync--log-silently "calendar-sync: Fetch error: %s" (error-message-string err))
(funcall callback nil))))
diff --git a/modules/flyspell-and-abbrev.el b/modules/flyspell-and-abbrev.el
index b73bfdf3..ebe4898e 100644
--- a/modules/flyspell-and-abbrev.el
+++ b/modules/flyspell-and-abbrev.el
@@ -197,9 +197,10 @@ Without prefix argument, it's created in the global abbrev table.
Press C-' repeatedly to step through misspellings one at a time."
(interactive "P")
(cj/--require-spell-checker)
- ;; Run flyspell-buffer only if buffer hasn't been checked yet
- (unless (bound-and-true-p flyspell-mode)
- (flyspell-buffer))
+ ;; Enable Flyspell for the buffer type so the mode sticks and the buffer is
+ ;; scanned once. A bare flyspell-buffer here never turned the mode on, so
+ ;; the guard never tripped and every C-' press re-scanned the whole buffer.
+ (cj/flyspell-on-for-buffer-type)
(let ((misspelled-word (cj/flyspell-goto-previous-misspelling (point))))
(if (not misspelled-word)
diff --git a/modules/keyboard-macros.el b/modules/keyboard-macros.el
index 4e801096..bca36eed 100644
--- a/modules/keyboard-macros.el
+++ b/modules/keyboard-macros.el
@@ -43,7 +43,7 @@
;;; Code:
(require 'subr-x) ;; for string-trim
-(eval-when-compile (require 'user-constants))
+(require 'user-constants) ;; for macros-file, read at runtime
(defvar cj/macros-loaded nil
"Whether saved keyboard macros have been loaded from file.")
@@ -130,15 +130,7 @@ With prefix arg, open the macros file for editing after saving."
(keymap-global-set "C-<f3>" #'cj/kbd-macro-start-or-end)
(keymap-global-set "<f3>" #'call-last-kbd-macro)
(keymap-global-set "M-<f3>" #'cj/save-maybe-edit-macro)
- (keymap-global-set "s-<f3>" #'cj/open-macros-file)
- (add-hook 'kill-emacs-hook #'cj/save-last-kbd-macro-on-exit))
-
-;; Add hook to save any unnamed macros on exit if desired
-(defun cj/save-last-kbd-macro-on-exit ()
- "Save the last keyboard macro before exiting Emacs if it's not saved."
- (when last-kbd-macro
- (when (y-or-n-p "Save last keyboard macro before exiting? ")
- (call-interactively #'cj/save-maybe-edit-macro))))
+ (keymap-global-set "s-<f3>" #'cj/open-macros-file))
;; Auto-call setup after init
(if after-init-time
diff --git a/modules/lorem-optimum.el b/modules/lorem-optimum.el
index 8aa96345..14f1d666 100644
--- a/modules/lorem-optimum.el
+++ b/modules/lorem-optimum.el
@@ -219,8 +219,22 @@ Builds and caches the keys list lazily if not already cached."
(message "Lorem-optimum learned from file: %s" file))
(defun cj/lipsum (n)
- "Return N words of lorem ipsum."
- (cj/markov-generate cj/lipsum-chain n '("Lorem" "ipsum")))
+ "Return N words of lorem ipsum.
+Interactively, prompt for N and echo the generated words.
+
+Signal a `user-error' when the Markov chain is empty (for example when the
+training file `cj/lipsum-default-file' is missing). Without this, callers
+such as `cj/lipsum-insert' would insert nil and raise a cryptic wrong-type
+error far from the cause. Train the chain with `cj/lipsum-learn-file',
+`cj/lipsum-learn-buffer', or `cj/lipsum-learn-region', or restore the file."
+ (interactive "nNumber of words: ")
+ (let ((text (cj/markov-generate cj/lipsum-chain n '("Lorem" "ipsum"))))
+ (unless (and (stringp text) (not (string-empty-p text)))
+ (user-error "Lorem-optimum chain is empty; train it with cj/lipsum-learn-file or restore %s"
+ cj/lipsum-default-file))
+ (when (called-interactively-p 'any)
+ (message "%s" text))
+ text))
(defun cj/lipsum-insert (n)
"Insert N words of lorem ipsum at point."
diff --git a/modules/org-babel-config.el b/modules/org-babel-config.el
index 79661013..51919da1 100644
--- a/modules/org-babel-config.el
+++ b/modules/org-babel-config.el
@@ -173,8 +173,5 @@ session when working in trusted files, and back on when done."
;; requires ob-racket, not yet in repositories
;; (add-to-list 'org-structure-template-alist '("sicp" . "src racket :lang sicp"))
-;; drop Org’s default footnote list at the end
-(setq org-html-footnote-separator "")
-
(provide 'org-babel-config)
;;; org-babel-config.el ends here.
diff --git a/modules/org-contacts-config.el b/modules/org-contacts-config.el
index 944d75c1..39ff9910 100644
--- a/modules/org-contacts-config.el
+++ b/modules/org-contacts-config.el
@@ -170,29 +170,40 @@ Added: %U"
(require 'system-lib)
+(defun cj/--org-contacts-collect (buffer)
+ "Return an alist of (NAME POSITION INFO) for the contact headings in BUFFER.
+NAME is the heading text, POSITION its buffer position, and INFO the
+EMAIL or PHONE property value (or nil)."
+ (with-current-buffer buffer
+ (org-map-entries
+ (lambda ()
+ (list (nth 4 (org-heading-components))
+ (point)
+ (or (org-entry-get nil "EMAIL")
+ (org-entry-get nil "PHONE"))))
+ nil nil)))
+
(defun cj/org-contacts-find ()
- "Find and open a contact."
+ "Find a contact and jump to its heading.
+Collect the contact headings before prompting, so cancelling the prompt
+leaves point where it was, and jump to the selected heading's stored
+position instead of a text search that could land inside another entry."
(interactive)
- (find-file contacts-file)
- (goto-char (point-min))
- (let* ((alist (org-map-entries
- (lambda ()
- (cons (nth 4 (org-heading-components))
- (or (org-entry-get nil "EMAIL")
- (org-entry-get nil "PHONE"))))
- nil (list contacts-file)))
+ (let* ((buf (find-file-noselect contacts-file))
+ (alist (cj/--org-contacts-collect buf))
(contact (completing-read
"Find contact: "
(cj/completion-table-annotated
'contact
(lambda (cand)
- (let ((info (cdr (assoc cand alist))))
+ (let ((info (nth 2 (assoc cand alist))))
(when (and info (> (length info) 0))
(concat " " (propertize info 'face
'completions-annotations)))))
- alist))))
- (goto-char (point-min))
- (search-forward contact)
+ alist)
+ nil t)))
+ (switch-to-buffer buf)
+ (goto-char (nth 1 (assoc contact alist)))
(org-fold-show-entry)
(org-reveal)))
diff --git a/modules/org-drill-config.el b/modules/org-drill-config.el
index 29f6130a..f53f36b9 100644
--- a/modules/org-drill-config.el
+++ b/modules/org-drill-config.el
@@ -134,25 +134,42 @@ With a prefix arg OTHER-DIR, prompt for the directory instead of `drill-dir'."
;; --------------------------------- Org Drill ---------------------------------
-(use-package org-drill
- ;; :vc (:url "git@cjennings.net:org-drill.git"
- ;; :branch "main"
- ;; :rev :newest)
- :load-path "~/code/org-drill" ;; local dev checkout — switch back to :vc above when done
- :after (org org-capture)
- :demand t
- :commands (org-drill org-drill-resume)
- :custom
- (org-drill-leech-failure-threshold 50 "leech cards = 50 wrong answers")
- (org-drill-leech-method 'warn "leech cards show warnings")
- (org-drill-use-visible-cloze-face-p t "cloze text shows up in a different font")
- (org-drill-hide-item-headings-p t "don't show heading text")
- (org-drill-maximum-items-per-session 100 "drill sessions end after 100 cards")
- (org-drill-maximum-duration 30 "each drill session can last up to 30 mins")
- (org-drill-add-random-noise-to-intervals-p t "vary the days to repetition slightly")
- (org-drill-text-size-during-session 24 "24-point font for comfortable reading")
- (org-drill-use-variable-pitch t "variable-pitch font for readability")
- (org-drill-hide-modeline-during-session t "hide the modeline for a cleaner display"))
+(defconst cj/org-drill-dev-checkout (expand-file-name "org-drill" "~/code/")
+ "Local org-drill development checkout, preferred when it exists.")
+
+(defun cj/--org-drill-source-keywords (&optional checkout)
+ "Return the use-package source keywords for org-drill.
+With CHECKOUT (default `cj/org-drill-dev-checkout') an existing directory,
+load from it via :load-path. Otherwise install from upstream via :vc, so
+drill still loads on a machine without the dev checkout (bare :load-path +
+:demand t would fail to load there)."
+ (let ((dir (or checkout cj/org-drill-dev-checkout)))
+ (if (file-directory-p dir)
+ (list :load-path dir)
+ (list :vc '(:url "git@cjennings.net:org-drill.git"
+ :branch "main"
+ :rev :newest)))))
+
+;; `use-package' keywords must be literals at macro-expansion, so the
+;; source keyword is spliced in through `eval' at load time (same idiom as
+;; the computed flycheck checker path elsewhere in the config).
+(eval
+ `(use-package org-drill
+ ,@(cj/--org-drill-source-keywords)
+ :after (org org-capture)
+ :demand t
+ :commands (org-drill org-drill-resume)
+ :custom
+ (org-drill-leech-failure-threshold 50 "leech cards = 50 wrong answers")
+ (org-drill-leech-method 'warn "leech cards show warnings")
+ (org-drill-use-visible-cloze-face-p t "cloze text shows up in a different font")
+ (org-drill-hide-item-headings-p t "don't show heading text")
+ (org-drill-maximum-items-per-session 100 "drill sessions end after 100 cards")
+ (org-drill-maximum-duration 30 "each drill session can last up to 30 mins")
+ (org-drill-add-random-noise-to-intervals-p t "vary the days to repetition slightly")
+ (org-drill-text-size-during-session 24 "24-point font for comfortable reading")
+ (org-drill-use-variable-pitch t "variable-pitch font for readability")
+ (org-drill-hide-modeline-during-session t "hide the modeline for a cleaner display")))
(provide 'org-drill-config)
;;; org-drill-config.el ends here.
diff --git a/modules/org-export-config.el b/modules/org-export-config.el
index 5a6f09fc..c3d3294c 100644
--- a/modules/org-export-config.el
+++ b/modules/org-export-config.el
@@ -20,7 +20,6 @@
;; - HTML: Web publishing with HTML5 support
;; - Markdown: README files and web content
;; - ODT: Office documents for LibreOffice/MS Word
-;; - Texinfo: GNU documentation and Info files
;;
;; Extended via Pandoc:
;; - Additional formats: DOCX, self-contained HTML5
@@ -28,7 +27,7 @@
;;
;; Key features:
;; - UTF-8 encoding enforced across all backends
-;; - Subtree export as default scope
+;; - Buffer export as default scope
;;
;; Note: reveal.js presentations are handled by org-reveal-config.el (C-; p)
;;
@@ -68,17 +67,8 @@
:config
(setq org-html-postamble nil)
(setq org-html-html5-fancy t)
- (setq org-html-head-include-default-style nil))
-
-
-(use-package ox-texinfo
- :ensure nil ; Built into Org
- :defer t
- :after ox
- :config
- (setq org-texinfo-coding-system 'utf-8)
- (setq org-texinfo-default-class "info")
- (add-to-list 'org-export-backends 'texinfo))
+ (setq org-html-head-include-default-style nil)
+ (setq org-html-footnote-separator "")) ;; no separator between adjacent footnote refs
(use-package ox-pandoc
:defer t
diff --git a/modules/org-reveal-config.el b/modules/org-reveal-config.el
index be702bf7..f842680b 100644
--- a/modules/org-reveal-config.el
+++ b/modules/org-reveal-config.el
@@ -8,9 +8,10 @@
;; Load shape: eager.
;; Eager reason: none; presentation export is a command-loaded deferral
;; candidate for Phase 4.
-;; Top-level side effects: package configuration via use-package.
-;; Runtime requires: none (configures packages via use-package).
-;; Direct test load: yes.
+;; Top-level side effects: registers a presentation prefix keymap under
+;; cj/custom-keymap; package configuration via use-package.
+;; Runtime requires: keybindings.
+;; Direct test load: yes (requires keybindings explicitly).
;;
;; Integrates ox-reveal for creating reveal.js presentations from Org files.
;;
@@ -28,6 +29,8 @@
;;; Code:
+(require 'keybindings) ;; cj/register-prefix-map, cj/custom-keymap
+
;; Forward declarations for byte-compiler (ox-reveal loaded via use-package)
(defvar org-reveal-root)
(defvar org-reveal-single-file)
@@ -238,17 +241,25 @@ reveal.js headers pre-filled."
;; -------------------------------- Keybindings --------------------------------
-(global-set-key (kbd "C-; p SPC") #'cj/reveal-present)
-(global-set-key (kbd "C-; p e") #'cj/reveal-export)
-(global-set-key (kbd "C-; p p") #'cj/reveal-preview-start)
-(global-set-key (kbd "C-; p s") #'cj/reveal-preview-stop)
-(global-set-key (kbd "C-; p h") #'cj/reveal-insert-header)
-(global-set-key (kbd "C-; p H") #'cj/reveal-remove-headers)
-(global-set-key (kbd "C-; p n") #'cj/reveal-new)
+;; A registered prefix keymap, not raw `global-set-key' chains: binding
+;; "C-; p ..." directly depends on keybindings.el having already made "C-;"
+;; a live prefix (otherwise "non-prefix key" errors), while
+;; `cj/register-prefix-map' binds into `cj/custom-keymap' with no load-order
+;; dependency beyond requiring keybindings.
+(defvar-keymap cj/reveal-map
+ :doc "Keymap for reveal.js presentation commands."
+ "SPC" #'cj/reveal-present
+ "e" #'cj/reveal-export
+ "p" #'cj/reveal-preview-start
+ "s" #'cj/reveal-preview-stop
+ "h" #'cj/reveal-insert-header
+ "H" #'cj/reveal-remove-headers
+ "n" #'cj/reveal-new)
+
+(cj/register-prefix-map "p" cj/reveal-map "presentations")
(with-eval-after-load 'which-key
(which-key-add-key-based-replacements
- "C-; p" "presentations"
"C-; p SPC" "present current buffer"
"C-; p e" "export & open"
"C-; p p" "start live preview"
diff --git a/modules/org-webclipper.el b/modules/org-webclipper.el
index 40ceada7..217aecfa 100644
--- a/modules/org-webclipper.el
+++ b/modules/org-webclipper.el
@@ -186,22 +186,6 @@ Return the yanked content as a string so templates can insert it."
;; extract the webpage content from the kill ring
(car kill-ring)))
-;; ----------------------------- Webclipper Keymap -----------------------------
-
-;; keymaps shouldn't be required for webclipper
-;; Setup keymaps
-;;
-;; (defun cj/webclipper-setup-keymaps ()
-;; "Setup webclipper keymaps."
-;; (define-prefix-command 'cj/webclipper-map nil
-;; "Keymap for weblipper operations.")
-;; (define-key cj/custom-keymap "c" 'cj/webclipper-map)
-;; (define-key cj/webclipper-map "n" 'cj/move-org-branch-to-roam))
-
-;; ;; Call keymap setup if cj/custom-keymap is already defined
-;; (when (boundp 'cj/custom-keymap)
-;; (cj/webclipper-setup-keymaps))
-
;; Register protocol handler early for external calls
(with-eval-after-load 'org-protocol
(unless (assoc "webclip" org-protocol-protocol-alist)
@@ -211,9 +195,5 @@ Return the yanked content as a string so templates can insert it."
:function cj/org-protocol-webclip
:kill-client t))))
-;; (with-eval-after-load 'cj/custom-keymap
-;; (require 'org-webclipper)
-;; (cj/webclipper-setup-keymaps))
-
(provide 'org-webclipper)
;;; org-webclipper.el ends here
diff --git a/modules/text-config.el b/modules/text-config.el
index dd7bd3ca..a65002a8 100644
--- a/modules/text-config.el
+++ b/modules/text-config.el
@@ -69,7 +69,7 @@
;; edit selection in new buffer, C-c to finish; replaces with modifications
(use-package edit-indirect
- :bind ("M-S-i" . edit-indirect-region)) ;; was M-I
+ :bind ("M-I" . edit-indirect-region))
;; ------------------------------ Prettify Symbols -----------------------------
;; replacing the word l-a-m-b-d-a with a symbol, just because
@@ -118,8 +118,8 @@ everything else, such as `lambda', use the standard boundary check."
;; an easy way to enter diacritical marks
(use-package accent
- :commands accent-company
- :bind ("C-`" . accent-company))
+ :commands accent-menu
+ :bind ("C-`" . accent-menu))
(provide 'text-config)
;;; text-config.el ends here