From: Eshel Yaron <me@eshelyaron.com>
To: Eli Zaretskii <eliz@gnu.org>
Cc: philipk@posteo.net, emacs-devel@gnu.org
Subject: Re: [ELPA] New package: dict
Date: Mon, 15 May 2023 21:50:57 +0300 [thread overview]
Message-ID: <m11qjh8m3y.fsf@eshelyaron.com> (raw)
In-Reply-To: <83ednj9sw2.fsf@gnu.org> (Eli Zaretskii's message of "Sun, 14 May 2023 12:14:37 +0300")
[-- Attachment #1: Type: text/plain, Size: 1889 bytes --]
Eli Zaretskii <eliz@gnu.org> writes:
>> From: Eshel Yaron <me@eshelyaron.com>
>> Cc: philipk@posteo.net, emacs-devel@gnu.org
>> Date: Sun, 14 May 2023 09:41:52 +0300
>>
>> Eli Zaretskii <eliz@gnu.org> writes:
>>
>> > How about adding to dictionary.el a customizable function that
>> > dictionary-search would call instead of its default operation?
>>
>> Thanks, this would be a good way to expose the different behavior that I
>> want to add. However, my difficulty lies elsewhere. The issue I
>> brought up in my previous message is with implementing this behavior
>> without introducing unnecessary code duplication to dictionary.el.
>>
>> In short, we need two things: a way to obtain a word's definition and a
>> way to obtain dictionary matches given some input (for completion).
>> dictionary.el does these things already, but in way that's too coupled
>> with its user interface to admit reuse for my purposes. So the question
>> is whether to add the needed stuff from Dict to dictionary.el and accept
>> some code duplication, or try to refactor the parts of dictionary.el
>> that communicate with the dictionary server to provide a cleaner API.
>
> I'd say try the latter if it's reasonably easy; otherwise try the
> former.
Alright, I'm attaching a patch that extends dictionary.el with new user
options that modify the behavior of `dictionary-search`. With this
patch, `dictionary-search` behaves like my `dict-describe-word` after
applying the following customizations:
--8<---------------cut here---------------start------------->8---
(setq dictionary-read-dictionary-function
#'dictionary-completing-read-dictionary)
(setq dictionary-read-word-function
#'dictionary-completing-read-word)
(setq dictionary-display-definition-function
#'dictionary-display-definition-in-help-buffer)
--8<---------------cut here---------------end--------------->8---
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-customization-options-for-dictionary-search.patch --]
[-- Type: text/x-patch, Size: 11408 bytes --]
From 1b5eecf46a40888c8c9ba900b17c1701fb3bcd70 Mon Sep 17 00:00:00 2001
From: Eshel Yaron <me@eshelyaron.com>
Date: Mon, 15 May 2023 21:04:21 +0300
Subject: [PATCH] Add customization options for dictionary-search
Allow users to customize 'dictionary-search' via several new
customization options.
* lisp/net/dictionary.el (dictionary-define-word)
(dictionary-match-word)
(dictionary-completing-read-word)
(dictionary-dictionaries)
(dictionary-completing-read-dictionary)
(dictionary-display-definition-in-help-buffer): New functions.
(dictionary-read-word-prompt)
(dictionary-display-definition-function)
(dictionary-read-word-function)
(dictionary-read-dictionary-function): New user options.
(dictionary-search): Use them.
* etc/NEWS: Announce.
---
etc/NEWS | 36 +++++++++
lisp/net/dictionary.el | 166 +++++++++++++++++++++++++++++++++++++----
2 files changed, 187 insertions(+), 15 deletions(-)
diff --git a/etc/NEWS b/etc/NEWS
index b4846eb11b0..8a9afa53cdc 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -322,6 +322,42 @@ instead of:
and another_expression):
do_something()
+** Dictionary
+
+---
+*** New user option 'dictionary-read-word-prompt'.
+This allows the user to customize the prompt that is used by
+'dictionary-search' when asking for a word to search in the
+dictionary.
+
+---
+*** New user option 'dictionary-display-definition-function'.
+This allows the user to customize the way in which 'dictionary-search'
+displays word definitions. If non-nil, this user option should be set
+to a function that displays a word definition obtained from a
+dictionary server. The new function
+'dictionary-display-definition-in-help-buffer' can be used to display
+the definition in a *Help* buffer, instead of the default *Dictionary*
+buffer.
+
+---
+*** New user option 'dictionary-read-word-function'.
+This allows the user to customize the way in which 'dictionary-search'
+prompts for a word to search in the dictionary. If non-nil, this user
+option should be set to a function that lets the user select a word
+and returns it as a string. The new function
+'dictionary-completing-read-word' can be used to prompt with
+completion based on dictionary matches.
+
+---
+*** New user option 'dictionary-read-dictionary-function'.
+This allows the user to customize the way in which 'dictionary-search'
+prompts for a dictionary to search in. If non-nil, this user option
+should be set to a function that lets the user select a dictionary and
+returns its name as a string. The new function
+'dictionary-completing-read-dictionary' can be used to prompt with
+completion based on dictionaries that the server supports.
+
\f
* New Modes and Packages in Emacs 30.1
diff --git a/lisp/net/dictionary.el b/lisp/net/dictionary.el
index ba65225692a..adf1f409f26 100644
--- a/lisp/net/dictionary.el
+++ b/lisp/net/dictionary.el
@@ -38,6 +38,7 @@
(require 'custom)
(require 'dictionary-connection)
(require 'button)
+(require 'help-mode)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Stuff for customizing.
@@ -247,6 +248,39 @@ dictionary-coding-systems-for-dictionaries
)))
:version "28.1")
+(defcustom dictionary-read-word-prompt "Search word"
+ "Prompt string to use when prompting for a word."
+ :type 'string
+ :version "30.1")
+
+(defcustom dictionary-display-definition-function nil
+ "Function to use for displaying dictionary definitions.
+It is called with three string arguments: the word being defined,
+the dictionary name, and the full definition."
+ :type '(choice (const :tag "Dictionary buffer" nil)
+ (const :tag "Help buffer"
+ dictionary-display-definition-in-help-buffer)
+ (function :tag "Custom function"))
+ :version "30.1")
+
+(defcustom dictionary-read-word-function nil
+ "Function to use for prompting for a word.
+It is called with no arguments and must return a string."
+ :type '(choice (const :tag "Default" nil)
+ (const :tag "Dictionary-based completion"
+ dictionary-completing-read-word)
+ (function :tag "Custom function"))
+ :version "30.1")
+
+(defcustom dictionary-read-dictionary-function nil
+ "Function to use for prompting for a dictionary.
+It is called with no arguments and must return a string."
+ :type '(choice (const :tag "Default" nil)
+ (const :tag "Choose among server-provided dictionaries"
+ dictionary-completing-read-dictionary)
+ (function :tag "Custom function"))
+ :version "30.1")
+
(defface dictionary-word-definition-face
'((((supports (:family "DejaVu Serif")))
(:family "DejaVu Serif"))
@@ -366,6 +400,8 @@ dictionary-word-history
'()
"History list of searched word.")
+(defvar dictionary--last-match nil)
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Basic function providing startup actions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1149,23 +1185,33 @@ dictionary-search
It presents the selection or word at point as default input and
allows editing it."
(interactive
- (list (let ((default (dictionary-search-default)))
- (read-string (format-prompt "Search word" default)
- nil 'dictionary-word-history default))
- (if current-prefix-arg
- (read-string (if dictionary-default-dictionary
- (format "Dictionary (%s): " dictionary-default-dictionary)
- "Dictionary: ")
- nil nil dictionary-default-dictionary)
- dictionary-default-dictionary)))
-
- ;; if called by pressing the button
- (unless word
- (setq word (read-string "Search word: " nil 'dictionary-word-history)))
- ;; just in case non-interactively called
+ (let ((dict
+ (if current-prefix-arg
+ (if dictionary-read-dictionary-function
+ (funcall dictionary-read-dictionary-function)
+ (read-string (if dictionary-default-dictionary
+ (format "Dictionary (%s): "
+ dictionary-default-dictionary)
+ "Dictionary: ")
+ nil nil dictionary-default-dictionary))
+ dictionary-default-dictionary)))
+ (list (if dictionary-read-word-function
+ (funcall dictionary-read-word-function)
+ (let ((default (dictionary-search-default)))
+ (read-string (format-prompt dictionary-read-word-prompt default)
+ nil 'dictionary-word-history default)))
+ dict)))
(unless dictionary
(setq dictionary dictionary-default-dictionary))
- (dictionary-new-search (cons word dictionary)))
+ (if dictionary-display-definition-function
+ (if-let ((definition (dictionary-define-word word dictionary)))
+ (funcall dictionary-display-definition-function word dictionary definition)
+ (user-error "No definition found for \"%s\"" word))
+ ;; if called by pressing the button
+ (unless word
+ (setq word (read-string "Search word: " nil 'dictionary-word-history)))
+ ;; just in case non-interactively called
+ (dictionary-new-search (cons word dictionary))))
;;;###autoload
(defun dictionary-lookup-definition ()
@@ -1386,5 +1432,95 @@ dictionary-context-menu
'dictionary-separator))
menu)
+(defun dictionary-define-word (word dictionary)
+ "Return the definition of WORD in DICTIONARY, or nil if not found."
+ (dictionary-send-command
+ (format "define %s \"%s\"" dictionary word))
+ (when (and (= (read (dictionary-read-reply)) 150)
+ (= (read (dictionary-read-reply)) 151))
+ (dictionary-read-answer)))
+
+(defun dictionary-match-word (word)
+ "Return dictionary matches for WORD as a list of strings."
+ (unless (string-empty-p word)
+ (if (string= (car dictionary--last-match) word)
+ (cdr dictionary--last-match)
+ (dictionary-send-command
+ (format "match %s %s \"%s\""
+ dictionary-default-dictionary
+ dictionary-default-strategy
+ word))
+ (when (and (= (read (dictionary-read-reply)) 152))
+ (with-temp-buffer
+ (insert (dictionary-read-answer))
+ (goto-char (point-min))
+ (let ((result nil))
+ (while (not (eobp))
+ (search-forward " " nil t)
+ (push (read (current-buffer)) result)
+ (search-forward "\n" nil t))
+ (setq result (reverse result))
+ (setq dictionary--last-match (cons word result))
+ result))))))
+
+(defun dictionary-completing-read-word ()
+ "Prompt for a word with completion based on dictionary matches."
+ (let* ((completion-ignore-case t)
+ (word-at-point (thing-at-point 'word t))
+ (default (dictionary-match-word word-at-point)))
+ (completing-read (format-prompt dictionary-read-word-prompt default)
+ (completion-table-dynamic #'dictionary-match-word)
+ nil t nil 'dictionary-word-history default t)))
+
+(defun dictionary-dictionaries ()
+ "Return the list of dictionaries the server supports."
+ (dictionary-send-command "show db")
+ (when (and (= (read (dictionary-read-reply)) 110))
+ (with-temp-buffer
+ (insert (dictionary-read-answer))
+ (goto-char (point-min))
+ (let ((result '(("!" . "First matching dictionary")
+ ("*" . "All dictionaries"))))
+ (while (not (eobp))
+ (push (cons (buffer-substring
+ (search-forward "\n" nil t)
+ (1- (search-forward " " nil t)))
+ (read (current-buffer)))
+ result))
+ (reverse result)))))
+
+(defun dictionary-completing-read-dictionary ()
+ "Prompt for a dictionary the server supports."
+ (let* ((dicts (dictionary-dictionaries))
+ (len (apply #'max (mapcar #'length (mapcar #'car dicts))))
+ (completion-extra-properties
+ (list :annotation-function
+ (lambda (key)
+ (concat (make-string (1+ (- len (length key))) ?\s)
+ (alist-get key dicts nil nil #'string=))))))
+ (completing-read (format-prompt "Select dictionary"
+ dictionary-default-dictionary)
+ dicts nil t nil nil dictionary-default-dictionary)))
+
+(define-button-type 'help-word
+ :supertype 'help-xref
+ 'help-function 'dictionary-search
+ 'help-echo (purecopy "mouse-2, RET: describe this word"))
+
+(defun dictionary-display-definition-in-help-buffer (word dictionary definition)
+ "Display DEFINITION, the definition of WORD in DICTIONARY."
+ (let ((help-buffer-under-preparation t))
+ (help-setup-xref (list #'dictionary-search word dictionary)
+ (called-interactively-p 'interactive))
+ (with-help-window (help-buffer)
+ (with-current-buffer (help-buffer)
+ (insert definition)
+ (goto-char (point-min))
+ (while (re-search-forward (rx "{"
+ (group-n 1 (* (not (any ?}))))
+ "}")
+ nil t)
+ (help-xref-button 1 'help-word (match-string 1)))))))
+
(provide 'dictionary)
;;; dictionary.el ends here
--
2.40.1
next prev parent reply other threads:[~2023-05-15 18:50 UTC|newest]
Thread overview: 42+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-05-11 13:22 [ELPA] New package: dict Eshel Yaron
2023-05-11 13:59 ` Eli Zaretskii
2023-05-11 14:14 ` Philip Kaludercic
2023-05-11 17:56 ` Eshel Yaron
2023-05-11 18:16 ` Eli Zaretskii
2023-05-11 18:29 ` Philip Kaludercic
2023-05-12 13:17 ` Eshel Yaron
2023-05-12 13:44 ` Eli Zaretskii
2023-05-14 6:41 ` Eshel Yaron
2023-05-14 9:14 ` Eli Zaretskii
2023-05-15 18:50 ` Eshel Yaron [this message]
2023-05-18 7:57 ` Eshel Yaron
2023-05-18 8:32 ` Eli Zaretskii
2023-05-18 10:59 ` Eli Zaretskii
2023-05-18 12:21 ` Eshel Yaron
2023-05-18 14:09 ` Eli Zaretskii
2023-05-18 15:51 ` Eshel Yaron
2023-05-18 15:58 ` Eli Zaretskii
2023-05-19 8:34 ` Eshel Yaron
2023-05-20 14:19 ` Eli Zaretskii
2023-05-20 16:49 ` Philip Kaludercic
2023-05-20 18:27 ` Eshel Yaron
2023-05-20 19:11 ` Philip Kaludercic
2023-05-21 6:52 ` Eshel Yaron
2023-05-25 9:52 ` Eshel Yaron
2023-05-25 19:10 ` Philip Kaludercic
2023-05-26 9:16 ` Eli Zaretskii
2023-05-26 11:36 ` Rudolf Adamkovič
2023-05-26 12:26 ` Eshel Yaron
2023-05-18 12:59 ` Philip Kaludercic
2023-05-18 15:37 ` Eshel Yaron
2023-05-18 15:58 ` Philip Kaludercic
2023-05-14 16:06 ` Stephen Leake
2023-05-15 18:58 ` Eshel Yaron
2023-05-11 14:18 ` Philip Kaludercic
2023-05-11 18:00 ` Eshel Yaron
2023-05-11 18:31 ` Philip Kaludercic
2023-05-12 13:32 ` Eshel Yaron
2023-05-16 19:38 ` Philip Kaludercic
2023-05-17 2:25 ` Eli Zaretskii
2023-05-13 22:30 ` Richard Stallman
2023-05-14 6:48 ` Eshel Yaron
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=m11qjh8m3y.fsf@eshelyaron.com \
--to=me@eshelyaron.com \
--cc=eliz@gnu.org \
--cc=emacs-devel@gnu.org \
--cc=philipk@posteo.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.