Skip to main content
Function v2, which is much simpler and does not (require) 'cl-* packages. I'm forgetting how to ELisp...
Source Link
(require 'cl-lib) (require 'cl-seq) (require 'ido) ;; This can be generalized by attaching a property to command symbol. (defun x-popup-menu--advice-around (f &rest args) (if (or (not (eq 'flyspell-correct-word-before-point this-command)) (ignore-errors (mouse-event-p (car args)))) (apply f args) ; Called via a mouse event, run normally. ;; Called via keyboard, do ido-completing-read. (let* ((mdesc (cadadr args)) (prompt (concat (car mdesc) ": ")) (pairwise (cdr mdesc)) ; (("foo" "foo") ... "" ... ) - may contain non-lists. (plist (nreverse (cl-reduce (lambda (acc x) (if (nlistp x) acc (cl-list* (cdr x) (car x) acc))) pairwise :initial-value nil))) ; ("foo" ("foo") ... ) (choices (nreverse (cl-reduce (lambda (acc x) (if (nlistp x) acc (cons (car x) acc))) pairwise :initial-value nil)))) ;; N.B.: every value in the plist is a 1-element list. (plist-get plist (ido-completing-read prompt choices nil t))))) (add-hook 'flyspell-mode-hook (lambda () (if (not flyspell-mode) (advice-remove 'x-popup-menu #'x-popup-menu--advice-around) (advice-add 'x-popup-menu :around #'x-popup-menu--advice-around) (define-key flyspell-mode-map [(control ?\.)] 'flyspell-correct-word-before-point)))) 
(require 'ido) ;; This can be generalized by attaching a property to command symbol. (defun x-popup-menu--advice-around (f &rest args) (if (or (not (eq 'flyspell-correct-word-before-point this-command)) (ignore-errors (mouse-event-p (car args)))) (apply f args) ; Not our guy, or called via a mouse event, run normally. ;; Called via keyboard, do ido-completing-read. (let* ((mdesc (cadadr args)) (prompt (concat (car mdesc) ": ")) ; Menu title. (mbody (cdr mdesc)) ; Menu body: (("foo" "foo") ... "" ... ) ; Menu body. (plist (mapcan (lambda (p) (and p (listp p) (list (car p) (cdr p)))) mbody)) ; (display_name (value) ... ) (choices (mapcan (lambda (p) (and p (listp p) (list (car p)))) mbody))) ; (display_name ...) ;; Every value in the plist is a 1-element list. (plist-get plist (ido-completing-read prompt choices nil t))))) (add-hook 'flyspell-mode-hook (lambda () (if (not flyspell-mode) (advice-remove 'x-popup-menu #'x-popup-menu--advice-around) (advice-add 'x-popup-menu :around #'x-popup-menu--advice-around) (define-key flyspell-mode-map [(control ?\.)] #'flyspell-correct-word-before-point)))) 
(require 'cl-lib) (require 'cl-seq) (require 'ido) ;; This can be generalized by attaching a property to command symbol. (defun x-popup-menu--advice-around (f &rest args) (if (or (not (eq 'flyspell-correct-word-before-point this-command)) (ignore-errors (mouse-event-p (car args)))) (apply f args) ; Called via a mouse event, run normally. ;; Called via keyboard, do ido-completing-read. (let* ((mdesc (cadadr args)) (prompt (concat (car mdesc) ": ")) (pairwise (cdr mdesc)) ; (("foo" "foo") ... "" ... ) - may contain non-lists. (plist (nreverse (cl-reduce (lambda (acc x) (if (nlistp x) acc (cl-list* (cdr x) (car x) acc))) pairwise :initial-value nil))) ; ("foo" ("foo") ... ) (choices (nreverse (cl-reduce (lambda (acc x) (if (nlistp x) acc (cons (car x) acc))) pairwise :initial-value nil)))) ;; N.B.: every value in the plist is a 1-element list. (plist-get plist (ido-completing-read prompt choices nil t))))) (add-hook 'flyspell-mode-hook (lambda () (if (not flyspell-mode) (advice-remove 'x-popup-menu #'x-popup-menu--advice-around) (advice-add 'x-popup-menu :around #'x-popup-menu--advice-around) (define-key flyspell-mode-map [(control ?\.)] 'flyspell-correct-word-before-point)))) 
(require 'ido) ;; This can be generalized by attaching a property to command symbol. (defun x-popup-menu--advice-around (f &rest args) (if (or (not (eq 'flyspell-correct-word-before-point this-command)) (ignore-errors (mouse-event-p (car args)))) (apply f args) ; Not our guy, or called via a mouse event, run normally. ;; Called via keyboard, do ido-completing-read. (let* ((mdesc (cadadr args)) (prompt (concat (car mdesc) ": ")) ; Menu title. (mbody (cdr mdesc)) ; Menu body: (("foo" "foo") ... "" ... ) ; Menu body. (plist (mapcan (lambda (p) (and p (listp p) (list (car p) (cdr p)))) mbody)) ; (display_name (value) ... ) (choices (mapcan (lambda (p) (and p (listp p) (list (car p)))) mbody))) ; (display_name ...) ;; Every value in the plist is a 1-element list. (plist-get plist (ido-completing-read prompt choices nil t))))) (add-hook 'flyspell-mode-hook (lambda () (if (not flyspell-mode) (advice-remove 'x-popup-menu #'x-popup-menu--advice-around) (advice-add 'x-popup-menu :around #'x-popup-menu--advice-around) (define-key flyspell-mode-map [(control ?\.)] #'flyspell-correct-word-before-point)))) 
Source Link

I forgot that I asked this question before, got frustrated by the slow link again, started googling, and this was the first page that came up in search. Not a good sign... So I did it. Hope someone would use it one day.

This relies on string keys in plist be eq to strings in choices: this is how plist-get works; the strings must not be copied. Not that I can think of a scenario where that would make sense; a reminder just in case.

If someone would want to make that into a package, please do. This can be generalized by attaching a concrete function (like this one which parses the menu and returns something that flyspell-correct-word-before-point expects to get from its x-popup-menu call) as a property of a command symbol ('flyspell-correct-word-before-point in this case).

(require 'cl-lib) (require 'cl-seq) (require 'ido) ;; This can be generalized by attaching a property to command symbol. (defun x-popup-menu--advice-around (f &rest args) (if (or (not (eq 'flyspell-correct-word-before-point this-command)) (ignore-errors (mouse-event-p (car args)))) (apply f args) ; Called via a mouse event, run normally. ;; Called via keyboard, do ido-completing-read. (let* ((mdesc (cadadr args)) (prompt (concat (car mdesc) ": ")) (pairwise (cdr mdesc)) ; (("foo" "foo") ... "" ... ) - may contain non-lists. (plist (nreverse (cl-reduce (lambda (acc x) (if (nlistp x) acc (cl-list* (cdr x) (car x) acc))) pairwise :initial-value nil))) ; ("foo" ("foo") ... ) (choices (nreverse (cl-reduce (lambda (acc x) (if (nlistp x) acc (cons (car x) acc))) pairwise :initial-value nil)))) ;; N.B.: every value in the plist is a 1-element list. (plist-get plist (ido-completing-read prompt choices nil t))))) (add-hook 'flyspell-mode-hook (lambda () (if (not flyspell-mode) (advice-remove 'x-popup-menu #'x-popup-menu--advice-around) (advice-add 'x-popup-menu :around #'x-popup-menu--advice-around) (define-key flyspell-mode-map [(control ?\.)] 'flyspell-correct-word-before-point))))