Somewhat inspired by Hyberbole's `hui-search-web` Dictionary lookup, I wanted an interactive dictionary and thesaurus lookup capability that I could integrate with Embark to get definitions and synonyms for the word under the cursor. It turns out that [dictionary.com](https://dictionary.com) has an API that's free to use for personal, non-commercial use under 1,000 requests per day.
These functions are built with `completing-read` so they play very nicely with ido, vertico, selectrum, consult etc. I haven't tried them with Helm or Ivy.
Because they're interactive functions, it's trivial to add these as Embark actions. Specifically, I added them to the `embark-identifier-map` and `embark-region-map`. The later allows me to select a word (with something like `expand-region`, or any other way to mark that word as the active region) call embark-act, browse through synonyms, and replace the selected word with the chosen synonym.
(define-key embark-identifier-map "DD" #'metropolis/get-definition)
(define-key embark-identifier-map "DT" #'metropolis/get-synonyms)
(define-key embark-region-map "DD" #'metropolis/get-definition)
(define-key embark-region-map "DT" #'metropolis/get-synonyms)
The attached gif shows this workflow with Embark, Vertico, and Consult.
All that's required to configure is to create an account on [dictionary.com](https://dictionary.com), generate API keys, and supply those with the environment variables `DICTIONARY_API_KEY` and `THESAURUS_API_KEY`.
Here's the code to do this. It depends on dash.el. If there's interest, I can publish this in some capacity or another.
(defun metropolis/thesaurus-response-buffer-to-formatted-string (rbuffer)
"""Take the results of json-parse-buffer, RBUFFER, that was applied to the thesaurus api response,
and format the parts we want into a nice string for browsing
"""
;; The thesaurus API gives a specific match of a particular word per item, and each item's id distinguishes
;; it from other possible meanings or uses of that word. syns is a list of synonyms associated to that id.
;; Flatten the results to remove the nested lists that sometimes are retunred.
;; Then format it as id: (list of synonyms)
(-map
(lambda (n) (format "%s: %s"
(alist-get 'id (alist-get 'meta n))
(flatten-tree (alist-get 'syns (alist-get 'meta n)))))
rbuffer))
(defun metropolis/request-thesaurus (word api-key)
(with-temp-buffer
(url-insert-file-contents (format "https://www.dictionaryapi.com/api/v3/references/thesaurus/json/%s?key=%s" word api-key))
(metropolis/thesaurus-response-buffer-to-formatted-string (json-parse-buffer :object-type 'alist :array-type 'list))))
(defun metropolis/defs-and-sysnonyms-to-clean-syns (selected-defs-and-synonyms)
;; The regex filters out all non-alphanumeric characters, but leaves accented Latin characters.
;; We take the cdr or the split-string so we only complete against the synonyms, not including the id.
(cdr (split-string (replace-regexp-in-string "[^a-zA-Z\d\s\u00C0-\u00FF]" "" selected-defs-and-synonyms))))
(defun metropolis/get-synonyms (word)
"Send a request to dictionary.com to fetch synonyms of WORD. Insert the selected word into the buffer."
(interactive "sWord: ")
(let* ((api-key (getenv "THESAURUS_API_KEY"))
(defs-and-synonyms (metropolis/request-thesaurus word api-key))
(selected-defs-and-synonyms (completing-read "Select synonyms: " defs-and-synonyms))
(selected-synonym (completing-read "Synonyms: " (metropolis/defs-and-sysnonyms-to-clean-syns selected-defs-and-synonyms))))
(progn
(if mark-active (delete-region (region-beginning) (region-end)) nil)
(insert selected-synonym))))
(defun metropolis/get-definition (word)
"Send a request to dictionary.com to fetch the definition of WORD"
(interactive "sWord: ")
(let ((api-key (getenv "DICTIONARY_API_KEY")))
(completing-read "Definitions: "
(with-temp-buffer
(url-insert-file-contents (format "https://www.dictionaryapi.com/api/v3/references/collegiate/json/%s?key=%s" word api-key))
(-map (apply-partially 'alist-get 'shortdef) (json-parse-buffer :object-type 'alist :array-type 'list))))))
`get-definition` and `get-synonyms` are the only functions to care about. The rest are internal. Any and all feedback welcome!
https://i.redd.it/r1clpt8zh5e91.gif