diff options
Diffstat (limited to 'config.org')
| -rw-r--r-- | config.org | 230 |
1 files changed, 225 insertions, 5 deletions
@@ -181,10 +181,25 @@ This section covers global editing behavior and a few startup-time tuning choices. #+begin_src emacs-lisp + (require 'abbrev) + (set-language-environment "UTF-8") (set-default-coding-systems 'utf-8) (prefer-coding-system 'utf-8) + (setq abbrev-file-name (expand-file-name "abbrev_defs" user-emacs-directory) + save-abbrevs 'silently) + (when (file-exists-p abbrev-file-name) + (quietly-read-abbrev-file abbrev-file-name)) + + (defun ss/enable-prose-abbrev-mode () + "Enable abbrev mode in prose buffers. +We keep this mode-local so code buffers stay on their own completion rules." + (abbrev-mode 1)) + + (dolist (hook '(text-mode-hook org-mode-hook)) + (add-hook hook #'ss/enable-prose-abbrev-mode)) + (setq auto-save-default nil backup-inhibited t echo-keystrokes 0.1 @@ -226,7 +241,7 @@ folders from note helpers. * Minibuffer completion -This keeps completion close to standard Emacs behaviour while improving the +Completion stays close to standard Emacs behaviour while improving the minibuffer prompts used throughout the notes workflow. Vertico provides the UI, Orderless handles flexible matching, and Marginalia adds lightweight annotations. @@ -252,11 +267,216 @@ annotations. :after vertico :init (marginalia-mode 1)) + + (use-package corfu + :ensure t + :pin gnu + :init + ;; Enable Corfu globally so text and Org buffers get in-buffer completion + ;; popups when a CAPF provides candidates. + (global-corfu-mode 1)) +#+end_src + +* Name shortcuts + +The name workflow uses one repository file for both abbrev triggers and CAPF +candidates. Abbrev handles deterministic one-shot expansions, while Corfu-backed +CAPF completion offers explicit choice among name variants. The two mechanisms +stay separate: abbrev mutates the buffer immediately, CAPF only proposes +candidates. + +#+begin_src emacs-lisp + (defconst ss/name-dictionary-file + (expand-file-name "name-dictionary.el" user-emacs-directory) + "Persistent source of truth for name abbrevs and CAPF candidates.") + + (defvar ss/name-dictionary-entries nil + "Persistent name entries used by abbrev and CAPF.") + + (when (file-exists-p ss/name-dictionary-file) + (load ss/name-dictionary-file nil t)) + + (require 'seq) + (require 'subr-x) + + (defun ss/name-dictionary--entry-name (entry) + "Return the canonical name in ENTRY." + (plist-get entry :name)) + + (defun ss/name-dictionary--entry-abbrev (entry) + "Return the abbrev trigger in ENTRY." + (plist-get entry :abbrev)) + + (defun ss/name-dictionary--entry-aliases (entry) + "Return alias candidates in ENTRY." + (plist-get entry :aliases)) + + (defun ss/name-dictionary-default-abbrev (name) + "Suggest a short trigger for NAME." + (let* ((parts (split-string (string-trim name) "[[:space:]]+" t)) + (first (downcase (substring (car parts) 0 (min 2 (length (car parts)))))) + (last (downcase (substring (car (last parts)) 0 1)))) + (if (> (length parts) 1) + (concat first last) + first))) + + (defun ss/name-dictionary-read-aliases (prompt) + "Read PROMPT and return a cleaned alias list." + (let ((aliases (mapcar #'string-trim (split-string (read-string prompt) "," t)))) + (seq-filter (lambda (string) (not (string-empty-p string))) aliases))) + + (defun ss/name-dictionary-canonical-names () + "Return the canonical names from the dictionary." + (mapcar #'ss/name-dictionary--entry-name ss/name-dictionary-entries)) + + (defun ss/name-dictionary-candidates () + "Return all CAPF candidates from the dictionary." + (delete-dups + (apply #'append + (mapcar (lambda (entry) + (cons (ss/name-dictionary--entry-name entry) + (ss/name-dictionary--entry-aliases entry))) + ss/name-dictionary-entries)))) + + (defun ss/name-dictionary-install-abbrevs () + "Install name abbrevs into the current buffer." + (setq-local local-abbrev-table (copy-abbrev-table local-abbrev-table)) + (dolist (entry ss/name-dictionary-entries) + (when-let ((name (ss/name-dictionary--entry-name entry)) + (abbrev (ss/name-dictionary--entry-abbrev entry))) + (define-abbrev local-abbrev-table abbrev name)))) + + (defun ss/name-dictionary-refresh-buffers () + "Refresh name abbrevs in every prose buffer." + (dolist (buffer (buffer-list)) + (with-current-buffer buffer + (when (and (bound-and-true-p abbrev-mode) + (derived-mode-p 'text-mode 'org-mode)) + (ss/name-dictionary-install-abbrevs))))) + + (defun ss/name-dictionary-save () + "Write the name dictionary file." + (let ((print-length nil) + (print-level nil)) + (with-temp-file ss/name-dictionary-file + (insert ";; -*- lexical-binding: t; -*-\n") + (insert ";; Persistent name entries used by abbrev and CAPF.\n\n") + (insert "(setq ss/name-dictionary-entries\n '") + (insert (pp-to-string ss/name-dictionary-entries)) + (insert ")\n")))) + + (defun ss/name-dictionary-reload () + "Reload the name dictionary file and refresh prose buffers." + (interactive) + (when (file-exists-p ss/name-dictionary-file) + (load ss/name-dictionary-file nil t)) + (ss/name-dictionary-refresh-buffers) + (message "Reloaded name dictionary")) + + (defun ss/name-dictionary--upsert (entry) + "Insert or replace ENTRY in `ss/name-dictionary-entries'." + (setq ss/name-dictionary-entries + (sort + (cons entry + (seq-remove (lambda (existing) + (or (string= (ss/name-dictionary--entry-name existing) + (ss/name-dictionary--entry-name entry)) + (string= (ss/name-dictionary--entry-abbrev existing) + (ss/name-dictionary--entry-abbrev entry)))) + ss/name-dictionary-entries)) + (lambda (left right) + (string< (ss/name-dictionary--entry-name left) + (ss/name-dictionary--entry-name right)))))) + + (defun ss/name-dictionary--remove (name) + "Remove NAME from `ss/name-dictionary-entries'." + (setq ss/name-dictionary-entries + (seq-remove (lambda (entry) + (string= (ss/name-dictionary--entry-name entry) name)) + ss/name-dictionary-entries))) + + (defun ss/name-dictionary--save-entry (name abbrev aliases) + "Persist a name entry and refresh prose buffers." + (let ((entry (list :name name :abbrev abbrev))) + (when aliases + (setq entry (append entry (list :aliases aliases)))) + (ss/name-dictionary--upsert entry) + (ss/name-dictionary-save) + (ss/name-dictionary-refresh-buffers) + (message "Added name: %s" name))) + + (defun ss/name-dictionary-add-name (name abbrev aliases) + "Add a canonical NAME, ABBREV trigger, and optional ALIASES." + (interactive + (let* ((name (read-string "Full name: ")) + (abbrev (string-trim + (read-string "Abbrev trigger: " + (ss/name-dictionary-default-abbrev name)))) + (aliases (ss/name-dictionary-read-aliases + "Aliases (comma-separated, optional): "))) + (list name abbrev aliases))) + (when (string-empty-p abbrev) + (setq abbrev (ss/name-dictionary-default-abbrev name))) + (ss/name-dictionary--save-entry name abbrev aliases)) + + (defun ss/name-dictionary-add-name-from-region (beg end abbrev aliases) + "Add the active region as a name entry." + (interactive + (if (use-region-p) + (let* ((name (string-trim + (buffer-substring-no-properties + (region-beginning) (region-end)))) + (abbrev (string-trim + (read-string "Abbrev trigger: " + (ss/name-dictionary-default-abbrev name)))) + (aliases (ss/name-dictionary-read-aliases + "Aliases (comma-separated, optional): "))) + (list (region-beginning) (region-end) abbrev aliases)) + (user-error "Select a name first"))) + (let ((name (string-trim + (buffer-substring-no-properties beg end)))) + (when (string-empty-p abbrev) + (setq abbrev (ss/name-dictionary-default-abbrev name))) + (ss/name-dictionary--save-entry name abbrev aliases))) + + (defun ss/name-dictionary-remove-name (name) + "Remove NAME from the persistent dictionary." + (interactive + (list (completing-read "Remove name: " + (ss/name-dictionary-canonical-names) + nil t))) + (ss/name-dictionary--remove name) + (ss/name-dictionary-save) + (ss/name-dictionary-refresh-buffers) + (message "Removed name: %s" name)) + + (defun ss/name-dictionary-open () + "Open the persistent name dictionary." + (interactive) + (find-file ss/name-dictionary-file)) + + (defun ss/name-capf () + "Return a name completion candidate set at a word boundary." + (let ((end (point))) + (save-excursion + (skip-syntax-backward "w_") + (let ((beg (point)) + (candidates (ss/name-dictionary-candidates))) + (when (and (< beg end) candidates) + (list beg end candidates :exclusive 'no)))))) + + (defun ss/enable-name-capf () + "Add `ss/name-capf' once in prose buffers." + (unless (memq #'ss/name-capf completion-at-point-functions) + (add-hook 'completion-at-point-functions #'ss/name-capf nil t))) + + (dolist (hook '(text-mode-hook org-mode-hook)) + (add-hook hook #'ss/enable-name-capf)) #+end_src * Notes workflow -This keeps the note-taking system deliberately small. Daily notes stay as plain +The note-taking system remains deliberately small. Daily notes stay as plain Org files in =~/org/daily/=, while longer-lived notes use Denote inside the same root directory and rely on links for relationships. @@ -554,9 +774,9 @@ repo-local behavior lives with the notes tree. * Gptel workflow -This keeps LLM chat available as a small workflow tool inside Emacs. GitHub -Copilot authentication is handled on demand by gptel, so there is no token -plumbing in this file. +LLM chat remains a small workflow tool inside Emacs. GitHub Copilot +authentication is handled on demand by gptel, so there is no token plumbing in +this file. #+begin_src emacs-lisp (use-package gptel |
