summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--README.md15
-rw-r--r--abbrev_defs1
-rw-r--r--config.org230
-rw-r--r--name-dictionary.el25
5 files changed, 268 insertions, 7 deletions
diff --git a/.gitignore b/.gitignore
index 6106eb5..bdf817b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,6 @@
!/.gitignore
!/AGENTS.md
!/README.md
-!/config.org \ No newline at end of file
+!/config.org
+!/abbrev_defs
+!/name-dictionary.el
diff --git a/README.md b/README.md
index 922bcbe..4225d93 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,19 @@ The minibuffer stack is intentionally small:
- `vertico` provides the completion UI.
- `orderless` handles matching.
- `marginalia` adds annotations.
+- `corfu` handles in-buffer completion popups for text and Org buffers.
+
+Name entry uses two separate paths:
+
+- `abbrev` provides deterministic one-shot shortcuts for fixed name expansions.
+- a small CAPF feeds Corfu a fixed list of name variants from `name-dictionary.el`.
+- `M-x ss/name-dictionary-add-name` and `M-x ss/name-dictionary-remove-name` update that file and refresh the current prose buffers.
+- `M-x ss/name-dictionary-add-name-from-region` uses the active region as the name being added.
+
+### Persistent abbrevs
+
+Persistent abbrevs live in `abbrev_defs` at the repository root. The config loads that file on startup, enables abbrev mode only in text-like buffers, and saves learned abbrevs back to the same file silently when buffers are saved.
+The name shortcut list lives in `name-dictionary.el` so it can be managed from Emacs with a single source of truth.
### Babel tangle process
@@ -89,7 +102,7 @@ Daily notes are plain Org files in `~/org/daily/`, named by date. When a daily n
- `Notes`
- `Open Loops`
-This keeps daily capture fast without routing everything through Denote.
+Daily capture stays fast without routing everything through Denote.
### Agenda usage
diff --git a/abbrev_defs b/abbrev_defs
new file mode 100644
index 0000000..055c8c8
--- /dev/null
+++ b/abbrev_defs
@@ -0,0 +1 @@
+;; Persistent abbrev definitions written by Emacs.
diff --git a/config.org b/config.org
index f996776..1c18407 100644
--- a/config.org
+++ b/config.org
@@ -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
diff --git a/name-dictionary.el b/name-dictionary.el
new file mode 100644
index 0000000..ccad42a
--- /dev/null
+++ b/name-dictionary.el
@@ -0,0 +1,25 @@
+;; -*- lexical-binding: t; -*-
+;; Persistent name entries used by abbrev and CAPF.
+
+(setq ss/name-dictionary-entries
+ '((:name "Ajay Shirke" :abbrev "ajs")
+ (:name "Akash Ali" :abbrev "aka")
+ (:name "Anant Sharma" :abbrev "ans")
+ (:name "Anish Kapoor" :abbrev "ank")
+ (:name "Ashish Pawar" :abbrev "asp")
+ (:name "Atilla Gul" :abbrev "atg")
+ (:name "Harjeet Singh" :abbrev "has")
+ (:name "Ilayaraja Selvaraju" :abbrev "ils")
+ (:name "Jaganmohanrao Peddada" :abbrev "jap")
+ (:name "Karthik Seelam" :abbrev "kas")
+ (:name "Kashif Hussain" :abbrev "kah")
+ (:name "Kenny Xu" :abbrev "kex")
+ (:name "Krishnaraj Muralidharan" :abbrev "krm")
+ (:name "Manmohan Verma" :abbrev "mav")
+ (:name "Mudit Sharma" :abbrev "mus")
+ (:name "Munesh Wali" :abbrev "muw")
+ (:name "Naresh Kumar Patro" :abbrev "nap")
+ (:name "Ramesh Sugandh Mallela" :abbrev "ram")
+ (:name "Shailesh Borse" :abbrev "shb")
+ (:name "Vinay Deo" :abbrev "vid")
+ (:name "Vishnu Kaarthi Thangadurai" :abbrev "vit")))