aboutsummaryrefslogtreecommitdiff

Features | Installation | Quick Start | Keybindings | Configuration | Extending Sources | org-drill | Troubleshooting | Development

A personal Emacs glossary. C-h g looks up terms in a single git-tracked org file. On a local miss, gloss fetches candidate definitions from Wiktionary and prompts you to pick which one to save — with provenance recorded. The same org file feeds org-drill for spaced-repetition study.

Status

In active development. v1 not yet released. Core features land; first-week shakedown pending. See docs/design/gloss.org for the full design and docs/decisions/ for the recorded ADRs.

Features

  • Single-file storage. One git-tracked org file, one * term heading per entry, alphabetical order maintained on insert. Diff-clean, hand-editable.
  • Online fallback on cache miss. Looks up missing terms in Wiktionary, presents candidate definitions in a side-buffer picker, saves the one you chose with provenance.
  • Auto-save when there's only one definition. No picker shown; the single definition lands in the glossary and the entry is displayed.
  • Manual add via a side-window editor with a read-only header (term + underline) and an editable body region. C-c C-c saves; C-c C-k cancels.
  • Edit-in-place. C-h g e jumps to the source org file at the entry's heading. A buffer-local after-save-hook refreshes the cache when you save.
  • org-drill export. Tag every entry as :drill: with :DRILL_CARD_TYPE: twosided via C-h g D. M-x org-drill runs the session unmodified.
  • Layered architecture. gloss-core (data) + gloss-fetch (network) + gloss-display (UI) + gloss-drill (drill export) + gloss (orchestration). Each layer mocks at its own boundary.
  • Pluggable sources for v2+. The gloss-fetch--sources alist registry walks fetchers in gloss-fetch-sources order; v1 ships only Wiktionary. Adding DictionaryAPI.dev or Wordnik later is one alist entry plus one fetcher function.
  • Diagnostic *gloss-debug* opt-in log buffer for layer-prefixed event tracing without polluting *Messages*.

Installation

Not on MELPA. gloss is on GitHub at https://github.com/cjennings/gloss and on cjennings.net.

Requirements: Emacs 27.1+, org-mode 9.3+. Online fetching also requires an Emacs built with libxml2 (most distribution builds ship it).

package-vc-install (Emacs 29+)

(unless (package-installed-p 'gloss)
  (package-vc-install "https://github.com/cjennings/gloss"))
(require 'gloss)
(gloss-install-prefix)            ; binds the C-h g sub-map

use-package with :vc (Emacs 29+)

(use-package gloss
  :vc (:url "https://github.com/cjennings/gloss" :rev :newest)
  :commands (gloss-lookup gloss-add gloss-edit
             gloss-fetch-online gloss-list-terms gloss-stats
             gloss-reload gloss-drill-export gloss-toggle-debug)
  :init
  (gloss-install-prefix))

straight.el

(straight-use-package
 '(gloss :type git :host github :repo "cjennings/gloss"))
(require 'gloss)
(gloss-install-prefix)

Manual installation

git clone https://github.com/cjennings/gloss.git ~/path/to/gloss

Then in your init:

(add-to-list 'load-path "~/path/to/gloss")
(require 'gloss)
(gloss-install-prefix)

Quick Start

After installing and running (gloss-install-prefix), C-h g becomes the gloss prefix. The single most common command is C-h g g (lookup):

  1. Type the term you want to look up. gloss-lookup reads from the minibuffer with word-at-point as the default, so just RET grabs the word under point.
  2. Cache hit: a side window opens on the right showing the term and its body.
  3. Cache miss with one definition online: the package fetches Wiktionary, saves the definition silently with :source: wiktionary, and shows it.
  4. Cache miss with multiple definitions: a picker appears in the minibuffer with each option formatted as [wiktionary] .... Pick one — that's what gets saved.
  5. Cache miss with no definitions: the echo area shows gloss: no definition found for X. No save.
  6. Network failure: the echo area shows gloss: couldn't reach any source for X. No save. Try again later.

To add a term manually (no online fetch), C-h g a:

  1. Minibuffer prompts Add term:. Type the term, RET.
  2. A side-window buffer opens with the term and a =====~ underline as a read-only header. Point lands in the editable body region underneath.
  3. Type the definition.
  4. C-c C-c saves the entry with :source: manual and closes the side window.
  5. C-c C-k cancels — no save, side window closes.

Keybindings

After (gloss-install-prefix), all commands live under C-h g:

Key Command What it does
C-h g g gloss-lookup Look up a term; fetch online on miss.
C-h g a gloss-add Add a term manually (read-only header + editable body).
C-h g e gloss-edit Jump to the source org file at the term's heading.
C-h g o gloss-fetch-online Force online fetch, bypassing cache.
C-h g D gloss-drill-export Tag every entry for org-drill.
C-h g l gloss-list-terms Browse glossary terms via completing-read.
C-h g s gloss-stats Show total / by-source / drill-tagged / size / mtime.
C-h g r gloss-reload Force reload of the cache from disk.
C-h g d gloss-toggle-debug Toggle the *gloss-debug* log buffer.

You can change the prefix by passing an argument to gloss-install-prefix:

(gloss-install-prefix (kbd "C-c g"))

Or skip the helper entirely and bind gloss-prefix-map where you want.

In gloss-add-mode (the *gloss-add: TERM* buffer):

Key Command What it does
C-c C-c gloss-add-finish Save the body and close the buffer.
C-c C-k gloss-add-abort Cancel; close without saving.

In gloss-mode (the *gloss: TERM* side buffer): inherits special-mode, so q dismisses the window.

Configuration

Four defcustoms. All of them have sensible defaults; configure only if you want different behaviour.

gloss-file

The org file that holds the glossary. Default:

(expand-file-name "gloss.org"
                  (or org-directory user-emacs-directory))

If your org-directory is set, the glossary lives next to your other org files. Otherwise it lands under user-emacs-directory. To override:

(setq gloss-file "~/notes/glossary.org")

The file is created on first save with a #+TITLE: Glossary header. Parent directory is created if missing. See ADR-1 for the rationale.

gloss-fetch-sources

The list of online sources to try, in order. Default:

(setq gloss-fetch-sources '(wiktionary))

v1 ships with only wiktionary. Set to nil to disable all online fetching:

(setq gloss-fetch-sources nil)

When more sources land in v2+, you'll be able to reorder them or pick a subset. See Extending Sources below.

gloss-fetch-timeout

Maximum time in seconds to wait for any single source to respond. Default 5. Bump higher on slow connections, lower if you'd rather fail fast.

(setq gloss-fetch-timeout 10)

gloss-debug

When non-nil, gloss writes layer-prefixed diagnostic events to a *gloss-debug* buffer. Off by default. Toggle interactively with C-h g d or set:

(setq gloss-debug t)

The debug buffer is opt-in for everything beyond user-facing events; *Messages* still shows the things you actually did or asked for.

Extending Sources

For v2+, register an additional fetcher in the source registry. The shape is an alist mapping a source symbol to a fetcher function. Each fetcher takes a TERM string and returns a per-source result plist:

;; Per-source result shape:
;; (:source SYM :status STATUS [:defs (DEF ...)] [:reason STRING])
;;
;; STATUS values:
;;   :ok :defs (def1 def2 ...)   — success, defs is a non-empty list
;;   :no-defs                    — server reached, term not there
;;   :unreachable                — DNS, refused, timeout
;;   :server-error               — HTTP 5xx, malformed JSON, schema mismatch
;;   :rate-limited               — HTTP 429
;;
;; A definition shape:
;; (:source SYM :text "definition text...")

Register your fetcher:

(defun my-dictionaryapi-fetcher (term)
  "Fetch TERM from DictionaryAPI.dev. Return per-source result plist."
  ;; ... call the API, build the plist ...
  )

(add-to-list 'gloss-fetch--sources
             '(dictionary-api . my-dictionaryapi-fetcher))
(add-to-list 'gloss-fetch-sources 'dictionary-api t)

The orchestrator walks gloss-fetch-sources in order and aggregates each source's result into the user-facing rollup. See gloss-fetch.el for the Wiktionary fetcher as a worked example.

org-drill Integration

Once you have a few entries saved, C-h g D tags every top-level heading with :drill: and adds :DRILL_CARD_TYPE: twosided as a property. M-x org-drill then runs the spaced-repetition session against gloss-file unmodified.

The export is idempotent — running it twice in a row touches nothing on the second pass. To remove the tags and properties, M-x gloss-drill-untag-all reverses every entry.

gloss-drill-export signals a user-error if org-drill isn't installed. Install it with:

M-x package-install RET org-drill RET

Or via :vc for the maintained fork:

(use-package org-drill
  :vc (:url "https://github.com/cjennings/org-drill" :rev :newest))

See ADR-3 for why every export uses twosided.

Troubleshooting

Online fetch requires Emacs built with libxml2

The Wiktionary fetcher uses libxml-parse-html-region to strip HTML from the raw API response. If your Emacs build doesn't include libxml2, online fetching is disabled package-wide for the session.

Manual gloss-add still works without libxml. To get online fetching, install an Emacs build with libxml support — most distribution packages include it.

gloss: couldn't reach any source for TERM

Network failure. Either the host is unreachable (DNS, no connection, firewall), the request timed out (gloss-fetch-timeout), or the server returned an error (5xx, rate limited).

For technical detail, enable gloss-debug and re-run the lookup. The *gloss-debug* buffer records the per-source :reason string (timeout (5s), HTTP 503, etc.). *Messages* keeps the user-facing rollup only.

gloss: glossary file appears corrupt

The org parser failed on gloss-file. The cache is preserved (so existing lookups still work), but recent changes haven't loaded. Open gloss-file, fix the syntax error, then M-x gloss-reload.

The most common cause is a hand edit that broke an entry's :PROPERTIES: drawer. Find the offending heading, fix the drawer, save.

Term not in glossary for a term you saved earlier

Cache and disk are out of sync. Try M-x gloss-reload first — that re-reads from disk. If it still misses, the term may have been hand-edited under a different heading; check gloss-file directly with M-x gloss-edit.

The cache auto-refreshes on every lookup if gloss-file's mtime advances, so out-of-band edits via git pull or another Emacs session are picked up automatically. gloss-reload is the manual escape hatch.

Side window won't dismiss

In a *gloss: TERM* buffer, q calls quit-window (inherited from special-mode). If your config rebinds q, the window won't dismiss the same way. Use C-x 1 as a fallback, or rebind q back in gloss-mode-map.

Development

Test infrastructure

gloss uses Cask for dependency management and ert-runner for tests. Install once:

cask install

Common targets:

make help                              # List all targets
make test                              # Run the full suite
make test-file FILE=tests/test-foo.el  # One file
make test-name TEST=pattern            # By test name pattern
make validate-parens                   # check-parens on every .el
make compile                           # Byte-compile package files
make lint                              # elisp-lint pass
make clean                             # Remove .elc

The current suite is 129 tests across the four layers and the orchestration layer. The pure helpers (gloss--orchestrate-fetch-result, gloss-display--format-candidate, gloss-display--render-entry, gloss--add-finish-internal, gloss--stats-text) get full Normal/Boundary/Error coverage. Mode-glue gets smoke tests only — Emacs already tests its own prompts and major-mode mechanics.

Contributing

Pull requests welcome. Match the existing test style: per-function file, three category coverage, real production code via require (never inlined). Mock at boundaries (url-retrieve-synchronously, completing-read, display-buffer); never mock internal helpers.

For non-trivial changes, open an issue first to discuss the design.

License

GPL-3.0-or-later. See LICENSE.