completion-ui-menu.el 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. ;;; completion-ui-menu.el --- menu user-interface for Completion-UI
  2. ;; Copyright (C) 2009, 2012 Toby Cubitt
  3. ;; Author: Toby Cubitt <toby-predictive@dr-qubit.org>
  4. ;; Version: 0.1.2
  5. ;; Keywords: completion, user interface, menu
  6. ;; URL: http://www.dr-qubit.org/emacs.php
  7. ;; This file is NOT part of Emacs.
  8. ;;
  9. ;; This file is free software: you can redistribute it and/or modify it under
  10. ;; the terms of the GNU General Public License as published by the Free
  11. ;; Software Foundation, either version 3 of the License, or (at your option)
  12. ;; any later version.
  13. ;;
  14. ;; This program is distributed in the hope that it will be useful, but WITHOUT
  15. ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  16. ;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  17. ;; more details.
  18. ;;
  19. ;; You should have received a copy of the GNU General Public License along
  20. ;; with this program. If not, see <http://www.gnu.org/licenses/>.
  21. ;;; Code:
  22. (eval-when-compile (require 'cl))
  23. (require 'completion-ui)
  24. ;;; ============================================================
  25. ;;; Customization variables
  26. (defgroup completion-ui-menu nil
  27. "Completion-UI menu user interface."
  28. :group 'completion-ui)
  29. (completion-ui-defcustom-per-source completion-ui-use-menu t
  30. "When non-nil, enable the completion menu and browser."
  31. :group 'completion-ui-menu
  32. :type 'boolean)
  33. (defcustom completion-menu-offset '(0 . 0)
  34. "Pixel offset for completion menus.
  35. This sometimes needs to be tweaked manually to get completion
  36. menus in the correct position under different window systems."
  37. :group 'completion-ui-menu
  38. :type '(cons (integer :tag "x") (integer :tag "y")))
  39. (completion-ui-defcustom-per-source completion-browser-max-items 25
  40. "Maximum number of completions to display
  41. in any one completion browser submenu."
  42. :group 'completion-ui-menu
  43. :type 'integer)
  44. (completion-ui-defcustom-per-source
  45. completion-browser-recurse-on-completions t
  46. "If non-nil, the completion browser will recursively list
  47. completions of completions (of completions of completions...).
  48. If nil, it will only display the original list of completions,
  49. organised hierarchically.
  50. Note that setting `non-prefix-completion' makes the browser
  51. act as though this variable is set to nil, regardless of its
  52. actual value, since recursing only makes sense for prefix
  53. completion."
  54. :group 'completion-ui-menu
  55. :type 'boolean)
  56. (defcustom completion-browser-buckets 'balance
  57. "Policy for choosing number of \"buckets\" in completion browser
  58. when there are more than `completion-browser-max-items' to
  59. display:
  60. balance: balance number of buckets and size of content
  61. maximize: maximize number of buckets, minimize size of contents
  62. mininize: minimize number of buckets, maximize size of contents"
  63. :group 'completion-ui-menu
  64. :type '(choice (const :tag "balance" balance)
  65. (const :tag "maximize" max)
  66. (const :tag "minimize" min)))
  67. ;;; ============================================================
  68. ;;; Other configuration variables
  69. (defvar completion-menu-map nil
  70. "Keymap used when the completion menu is enabled.
  71. These key bindings get added to the completion overlay keymap.")
  72. (unless completion-menu-map
  73. (setq completion-menu-map (make-sparse-keymap))
  74. ;; M-<down> displayes the completion menu
  75. (define-key completion-menu-map [M-down] 'completion-activate-menu)
  76. ;; clicking on a completion displays the completion menu
  77. (define-key completion-menu-map [mouse-2] 'completion-activate-menu))
  78. ;;; ============================================================
  79. ;;; Interface functions
  80. (defun completion-activate-menu (&optional overlay browser)
  81. "Show the completion menu.
  82. With a prefix argument, show the completion browser."
  83. (interactive (list nil current-prefix-arg))
  84. ;; look for completion overlay at point, unless one was supplied
  85. (unless overlay (setq overlay (completion-ui-overlay-at-point)))
  86. ;; deactivate other auto-show interfaces
  87. (completion-ui-deactivate-auto-show-interface overlay)
  88. ;; show the completion menu
  89. (if browser
  90. (completion-show-browser-menu overlay)
  91. (completion-show-menu overlay)))
  92. (defun completion-activate-menu-keys (overlay)
  93. "Enable menu key bindings for OVERLAY."
  94. (map-keymap
  95. (lambda (key binding)
  96. (define-key (overlay-get overlay 'keymap) (vector key) binding))
  97. completion-menu-map))
  98. (defun completion-deactivate-menu-keys (overlay)
  99. "Disable menu key bindings for OVERLAY."
  100. (map-keymap
  101. (lambda (key binding)
  102. (define-key (overlay-get overlay 'keymap) (vector key) nil))
  103. completion-menu-map))
  104. (defun completion-show-menu (&optional overlay menu)
  105. "Show completion menu for completion OVERLAY.
  106. The point had better be within OVERLAY or you'll have a sneezing
  107. fit.
  108. If no OVERLAY is supplied, one is found at point (this only
  109. happens when this function is called interactively).
  110. If MENU is supplied, use that to construct the menu, unless an
  111. overlay overrides it. It is called with one argument, the
  112. completion OVERLAY. MENU defaults to the \"overlay local\"
  113. binding of 'completion-menu, or `completion-menu' if there is
  114. none."
  115. (interactive)
  116. (unless overlay (setq overlay (completion-ui-overlay-at-point)))
  117. (when overlay
  118. (unless menu
  119. (setq menu (completion-ui-source-menu overlay)))
  120. (let ((prefix (overlay-get overlay 'prefix))
  121. keymap result)
  122. (cond
  123. ;; if `menu' is a function, evaluate it to get menu
  124. ((functionp menu)
  125. (setq keymap (funcall menu overlay))
  126. ;; throw error if return value has wrong type
  127. (unless (or (null keymap) (keymapp keymap))
  128. (error "`completion-menu' returned wrong type:null or keymapp, %s"
  129. (prin1-to-string keymap))))
  130. ;; if `menu' is a keymap, use that
  131. ((keymapp menu) (setq keymap menu))
  132. ;; otherwise, throw an error
  133. (t (error "Wrong type in `completion-menu': functionp or keymapp, %s"
  134. (prin1-to-string menu))))
  135. ;; if we've constructed a menu, display it
  136. (when keymap
  137. (setq result
  138. (x-popup-menu
  139. (save-excursion
  140. (goto-char (overlay-start overlay))
  141. (list
  142. (let ((pos (completion-window-posn-at-point
  143. nil nil
  144. (car completion-menu-offset)
  145. (+ (frame-char-height) 3
  146. (cdr completion-menu-offset)))))
  147. (list (car pos) (cdr pos)))
  148. (selected-window))
  149. ;; (completion-posn-at-point-as-event
  150. ;; nil nil
  151. ;; (car completion-menu-offset)
  152. ;; (+ (frame-char-height) 3
  153. ;; (cdr completion-menu-offset)))
  154. )
  155. keymap))
  156. ;; if they ain't selected nuffin', don't do nuffin'!
  157. (when result
  158. ;; convert result to a vector for key lookup
  159. (setq result (apply 'vector result))
  160. (cond
  161. ;; if they selected a completion from the menu...
  162. ((string-match "^completion-insert"
  163. (symbol-name (aref result (1- (length result)))))
  164. ;; insert selected completion
  165. (destructuring-bind (cmpl len)
  166. (funcall (lookup-key keymap result))
  167. ;; run accept hooks
  168. (run-hook-with-args 'completion-accept-functions prefix cmpl)
  169. ;; deactivate interfaces, delete original prefix, and insert
  170. ;; selected completion
  171. (completion-ui-deactivate-interfaces overlay)
  172. (delete-region (- (point) (length prefix)) (point))
  173. (let ((overwrite-mode nil)) (insert cmpl)))
  174. (completion-ui-delete-overlay overlay))
  175. ;; otherwise, run whatever they did select
  176. (t (funcall (lookup-key keymap result))))
  177. )))))
  178. (defun completion-show-browser-menu (&optional overlay menu)
  179. "Show completion browser menu for completion OVERLAY.
  180. The point had better be within OVERLAY or you'll get hives.
  181. If no OVERLAY is supplied, one is found at point.
  182. If MENU is supplied, use that to construct the menu, unless an
  183. overlay overrides it. Defaults to the appropriate completion
  184. source setting, or `completion-construct-browser-menu' if there
  185. is none.
  186. Note: can be overridden by \"overlay local\" binding of
  187. 'completion-browser-menu-function."
  188. (interactive)
  189. ;; this function is really just a call to `completion-show-menu' but passing
  190. ;; the browser menu function as the menu argument
  191. (completion-show-menu
  192. overlay (or menu (completion-ui-source-browser overlay))))
  193. (defun completion-construct-menu (overlay)
  194. "Construct and return menu keymap defining the completion menu
  195. for a completion OVERLAY."
  196. (let* ((menu (make-sparse-keymap))
  197. (prefix (overlay-get overlay 'prefix))
  198. (completions (overlay-get overlay 'completions))
  199. (num (length completions))
  200. n)
  201. ;; construct menu keymap from available completions
  202. (dotimes (i num)
  203. (setq n (- num i 1))
  204. (define-key menu
  205. (vector (intern (concat "completion-insert-" (number-to-string n))))
  206. (list 'menu-item
  207. (if (stringp (nth n completions))
  208. (nth n completions)
  209. (car (nth n completions)))
  210. `(lambda ()
  211. (list ,(if (stringp (nth n completions))
  212. (nth n completions) (car (nth n completions)))
  213. ,(if (stringp (nth n completions))
  214. (length prefix) (cdr (nth n completions)))))
  215. ;; if a hotkey is associated with completion, show it in menu
  216. :keys (when (and (completion-ui-get-value-for-source
  217. overlay completion-ui-use-hotkeys)
  218. (< n (length completion-hotkey-list)))
  219. (key-description
  220. (vector (nth n completion-hotkey-list)))))))
  221. ;; add entry to switch to completion browser
  222. (define-key-after menu [separator-browser] '(menu-item "--"))
  223. (define-key-after menu [completion-browser-menu-function]
  224. (list 'menu-item "Browser..." 'completion-show-browser-menu))
  225. ;; return the menu keymap
  226. menu))
  227. (defun completion-construct-browser-menu
  228. (overlay &optional menu-item-func sub-menu-func)
  229. "Construct the completion browser menu keymap
  230. for a completion OVERLAY.
  231. MENU-ITEM-FUNC and SUB-MENU-FUNC override the default functions
  232. for creating the sub-menus and menu items. Both functions are
  233. passed 4 arguments: a list of completions, or a single completion
  234. in the case of MENU-ITEM-FUNC, MENU-ITEM-FUNC, SUB-MENU-FUNCT,
  235. and OVERLAY. They should return menu keymaps."
  236. ;; FIXME: could we speed this up by using :filter menu entry functions to
  237. ;; construct do just-in-time construction of submenus? This didn't
  238. ;; use to work, but maybe in new Emacs versions it does...
  239. ;; inform user it's in progress, as it can take a while
  240. (message "Creating predictive completion browser\
  241. (C-g to cancel if taking too long)...")
  242. ;; default menu creation functions
  243. (unless menu-item-func
  244. (setq menu-item-func 'completion-browser-menu-item))
  245. (unless sub-menu-func
  246. (setq sub-menu-func 'completion-browser-sub-menu))
  247. ;; main browser menu is just a browser submenu...
  248. (let* ((completions
  249. (funcall (completion-ui-source-completion-function overlay)
  250. (overlay-get overlay 'prefix)))
  251. (menu (funcall sub-menu-func completions
  252. menu-item-func sub-menu-func overlay)))
  253. ;; ... with an item added for switching to the basic completion
  254. ;; menu
  255. (define-key-after menu [separator-basic] '(menu-item "--"))
  256. (define-key-after menu [completion-menu]
  257. (list 'menu-item "Basic..." 'completion-show-menu))
  258. ;; return keymap
  259. menu))
  260. ;; Note:
  261. ;;
  262. ;; I should probably use some `imenu' function to create the menu,
  263. ;; since `imenu' already deals with "bucketising" menus (an ugly
  264. ;; necessity which should anyway be replaced with menu scrollbars,
  265. ;; preferably with just-in-time calculation of menu entries --
  266. ;; heads-up Emacs devs!).
  267. ;;
  268. ;; My excuses are that `imenu--mouse-menu' etc. are undocumented,
  269. ;; rolling my own was easier, and anyway I think my buckets are better
  270. ;; (they're optimal in the information-theoretic sense that you need
  271. ;; to make the least number of choices to get to the entry you want).
  272. ;;
  273. ;; One day I might patch the `imenu' "bucketising" code, and use
  274. ;; `imenu' here instead. Don't hold your breath.
  275. (defun completion-browser-sub-menu
  276. (completions menu-item-func sub-menu-func overlay)
  277. "Construct a predictive completion browser sub-menu keymap."
  278. (let ((prefix (overlay-get overlay 'prefix))
  279. (menu (make-sparse-keymap))
  280. (num-completions (length completions))
  281. (max-items (completion-ui-get-value-for-source
  282. overlay completion-browser-max-items)))
  283. (cond
  284. ;; if there's only 1 entry, don't bother with sub-menu, just set keymap
  285. ;; to be the item itself
  286. ((= num-completions 1)
  287. (let* ((cmpl (car completions))
  288. (entry (funcall menu-item-func
  289. cmpl menu-item-func sub-menu-func overlay)))
  290. (cond
  291. ;; if entry is a menu keymap, use it as the menu, adding completion
  292. ;; itself to the top
  293. ((keymapp entry)
  294. (define-key entry [separator-item-sub-menu] '(menu-item "--"))
  295. (define-key entry [completion-insert-root]
  296. (list
  297. 'menu-item cmpl
  298. `(lambda ()
  299. (list ,(if (stringp cmpl) cmpl (car cmpl))
  300. ,(if (stringp cmpl) (length prefix) (cdr cmpl))))))
  301. (setq menu entry))
  302. (t ;; if entry is a single item, add it to the menu
  303. (define-key menu [completion-insert-0]
  304. (list
  305. 'menu-item cmpl
  306. `(lambda ()
  307. (list ,(if (stringp cmpl) cmpl (car cmpl))
  308. ,(if (stringp cmpl) (length prefix) (cdr cmpl))))))))
  309. ))
  310. ;; if menu does not need to be divided into buckets, just add the
  311. ;; completions themselves to the keymap
  312. ((<= num-completions max-items)
  313. (dotimes (i num-completions)
  314. (define-key-after menu
  315. (vector (intern (concat "completion-insert-"
  316. (number-to-string i))))
  317. (list 'menu-item
  318. (if (stringp (nth i completions))
  319. (nth i completions)
  320. (car (nth i completions)))
  321. (funcall menu-item-func
  322. (nth i completions) menu-item-func sub-menu-func
  323. overlay))
  324. )))
  325. ;; if menu needs to be divided into buckets, construct a menu keymap
  326. ;; containing the bucket menus
  327. (t
  328. (let* ((num-buckets
  329. (cond
  330. ;; maximize number of buckets, minimize size of
  331. ;; contents
  332. ((eq completion-browser-buckets 'max) max-items)
  333. ;; minimize number of buckets, maximize size of
  334. ;; contents
  335. ((eq completion-browser-buckets 'min)
  336. (min max-items (1+ (/ (1- num-completions) max-items))))
  337. ;; balance number of buckets and size of contents
  338. (t (min max-items (round (sqrt num-completions))))))
  339. (num-per-bucket (/ num-completions num-buckets))
  340. (num-large-buckets (% num-completions num-buckets))
  341. (num-small-buckets (- num-buckets num-large-buckets))
  342. i j)
  343. (dotimes (b num-buckets)
  344. ;; if bucket has only 1 entry, don't bother with bucket
  345. ;; menu, just add completion itself to keymap
  346. (if (and (= 1 num-per-bucket) (< b num-small-buckets))
  347. (define-key-after menu
  348. (vector (intern (concat "completion-insert-"
  349. (number-to-string i))))
  350. (list 'menu-item
  351. (if (stringp (nth i completions))
  352. (nth i completions)
  353. (car (nth i completions)))
  354. (funcall menu-item-func
  355. (nth i completions)
  356. menu-item-func sub-menu-func overlay)))
  357. ;; if bucket has more than 1 entry...
  358. ;; get index of first completion in bucket
  359. (setq i (+ (* (min b num-small-buckets) num-per-bucket)
  360. (* (max 0 (- b num-small-buckets))
  361. (1+ num-per-bucket))))
  362. ;; get index of last completion in bucket
  363. (setq j (1- (+ i num-per-bucket
  364. (if (< b num-small-buckets) 0 1))))
  365. ;; add bucket menu to keymap
  366. (define-key-after menu
  367. (vector (intern (concat "bucket-" (number-to-string b))))
  368. (list 'menu-item
  369. (concat "From \""
  370. (nth i completions)
  371. "\" to \""
  372. (nth j completions) "\"")
  373. ;; call function to generate sub-menu
  374. (funcall sub-menu-func
  375. (completion--sublist completions i (1+ j))
  376. menu-item-func sub-menu-func overlay))))
  377. ))))
  378. ;; return constructed menu
  379. menu))
  380. (defun completion-browser-menu-item
  381. (cmpl menu-item-func sub-menu-func overlay)
  382. "Construct predictive completion browser menu item."
  383. (let* ((prefix (overlay-get overlay 'prefix))
  384. (cmpl-function (or (completion-ui-source-completion-function
  385. (overlay-get overlay 'completion-source))
  386. (overlay-get overlay 'completion-source)))
  387. (cmpl-prefix-function
  388. (overlay-get overlay 'completion-prefix-function))
  389. (non-prefix-completion (overlay-get overlay 'non-prefix-completion))
  390. ;; If `non-prefix-completion' is null, get completions for entry,
  391. ;; dropping the empty string which corresponds to the same entry
  392. ;; again (which would lead to infinite recursion). It makes no sense
  393. ;; to get completions of completions (of completions of
  394. ;; completions...) when doing something other than prefix-completion,
  395. ;; so the entry is just the original completion itself if
  396. ;; `non-prefix-completion' is non-nil.
  397. (completions
  398. (and (completion-ui-get-value-for-source
  399. overlay completion-browser-recurse-on-completions)
  400. (not non-prefix-completion)
  401. (not (string= (if (stringp cmpl) cmpl (car cmpl)) ""))
  402. ;; note :have to replace any prefix length data in completions
  403. ;; list with prefix length data from original prefix
  404. (mapcar
  405. (if (stringp cmpl)
  406. (lambda (c) (if (stringp c) c (car c)))
  407. (lambda (c) (cons (if (stringp c) c (car c)) (cdr cmpl))))
  408. (cdr (funcall cmpl-function cmpl))))))
  409. ;; if there are no completions (other than the entry itself), create a
  410. ;; selectable completion item
  411. (if (null completions)
  412. `(lambda ()
  413. (list ,(if (stringp cmpl) cmpl (car cmpl))
  414. ,(if (stringp cmpl) (length prefix) (cdr cmpl))))
  415. ;; otherwise, create a sub-menu containing them
  416. (let ((menu (funcall sub-menu-func
  417. completions menu-item-func sub-menu-func overlay)))
  418. ;; add completion itself to the menu
  419. (define-key menu [separator-item-sub-menu] '(menu-item "--"))
  420. (define-key menu [completion-insert-root]
  421. (list 'menu-item
  422. cmpl
  423. `(lambda ()
  424. (list ,(if (stringp cmpl) cmpl (car cmpl))
  425. ,(if (stringp cmpl) (length prefix) (cdr cmpl))))))
  426. ;; return the menu keymap
  427. menu))))
  428. ;;; =================================================================
  429. ;;; Register user-interface
  430. (completion-ui-register-interface menu
  431. :variable completion-ui-use-menu
  432. :activate completion-activate-menu-keys
  433. :deactivate completion-deactivate-menu-keys
  434. :auto-show completion-show-menu)
  435. (provide 'completion-ui-menu)
  436. ;; Local Variables:
  437. ;; eval: (font-lock-add-keywords nil '(("(\\(completion-ui-defcustom-per-source\\)\\>[ \t]*\\(\\sw+\\)?" (1 font-lock-keyword-face) (2 font-lock-variable-name-face))))
  438. ;; End:
  439. ;;; completion-ui-menu.el ends here