File: avy-menu.el

package info (click to toggle)
avy-menu 0.1.1-2
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 88 kB
  • sloc: lisp: 116; makefile: 2
file content (174 lines) | stat: -rw-r--r-- 6,477 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
;;; avy-menu.el --- Library providing avy-powered popup menu -*- lexical-binding: t; -*-
;;
;; Copyright © 2016–2017 Mark Karpov <markkarpov92@gmail.com>
;;
;; Author: Mark Karpov <markkarpov92@gmail.com>
;; URL: https://github.com/mrkkrp/avy-menu
;; Version: 0.1.1
;; Package-Requires: ((emacs "24.3") (avy "0.3.0"))
;; Keywords: popup, menu
;;
;; This file is not part of GNU Emacs.
;;
;; This program is free software: you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by the
;; Free Software Foundation, either version 3 of the License, or (at your
;; option) any later version.
;;
;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
;; Public License for more details.
;;
;; You should have received a copy of the GNU General Public License along
;; with this program. If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; The library provides Avy-powered popup menu that allows to quickly choose
;; from available options.  This is used in (at least) the following
;; packages:
;;
;; * `ace-popup-menu'
;; * `char-menu'
;; * `hasky-extensions'
;;
;; You can use it directly for your custom needs as well.

;;; Code:

(require 'avy)
(require 'cl-lib)

(defgroup avy-menu nil
  "Avy-powered popup menu."
  :group  'convenience
  :tag    "Avy Menu"
  :prefix "avy-menu-"
  :link   '(url-link :tag "GitHub" "https://github.com/mrkkrp/avy-menu"))

(defface avy-menu-title
  '((t (:inherit font-lock-function-name-face)))
  "Face used to print title of entire menu.")

(defface avy-menu-pane-header
  '((t (:inherit underline)))
  "Face used to print pane headers.")

(defface avy-menu-inactive
  '((t (:inherit shadow)))
  "Face used to print inactive menu items.")

;;;###autoload
(defun avy-menu (buffer-or-name menu &optional show-pane-header)
  "Show a popup menu in a temporary window and return user's selection.

BUFFER-OR-NAME specifies name of the buffer (or buffer itself)
that hosts menu options.  MENU itself should be a list of the
form (TITLE PANE1 PANE2 …), where each pane is a list of
form (TITLE ITEM1 ITEM2 …).  Each item is normally a cons
cell (STRING . VALUE), but a string can appear as an item—that
makes a non-selectable item in the menu.  Also, empty strings
start a new sub-section.

If SHOW-PANE-HEADER is not NIL, show pane headers (titles),
otherwise hide them.

Returned value is VALUE if user has selected something and NIL if
he has cancelled the whole menu or pressed key that does not
correspond to any available option."
  (let ((buffer (get-buffer-create buffer-or-name))
        menu-item-alist
        (first-pane t))
    (with-current-buffer buffer
      (with-current-buffer-window
       ;; buffer or name
       buffer
       ;; action (for `display-buffer')
       (cons 'display-buffer-below-selected
             '((window-height . fit-window-to-buffer)
               (preserve-size . (nil . t))))
       ;; quit-function
       (lambda (window _value)
         (with-selected-window window
           (unwind-protect
               (cdr
                (assq
                 (avy-with avy-menu
                   (avy--process (mapcar #'car menu-item-alist)
                                 #'avy--overlay-pre))
                 menu-item-alist))
             (when (window-live-p window)
               (quit-restore-window window 'kill)))))
       ;; menu generation
       (setq cursor-type nil)
       (cl-destructuring-bind (title . panes) menu
         (insert (propertize title 'face 'avy-menu-title)
                 "\n\n")
         (dolist (pane panes)
           (cl-destructuring-bind (title . items) pane
             (if first-pane
                 (setq first-pane nil)
               (insert "\n\n"))
             (when show-pane-header
               (insert (propertize title 'face 'avy-menu-pane-header)
                       "\n\n"))
             (let ((pane-alist (avy-menu--insert-strings items)))
               (if menu-item-alist
                   (nconc menu-item-alist pane-alist)
                 (setq menu-item-alist pane-alist))))))))))

(defun avy-menu--insert-strings (items)
  "Insert ITEMS much like `completion--insert-strings' in current buffer.

ITEMS should be a list, where every element is a cons of
form (STRING . VALUE), where STRING is the string to be printed
in current buffer and VALUE is used to construct result value of
this function.  ITEMS can contain plain strings, in this case
they are printed with inactive face.  Empty strings are not
printed, instead they begin new sub-section.

Return alist of values (POS . VALUE), where POS indicates
position of STRING in the buffer and VALUE is its associated
value according to ITEMS."
  (when (consp items)
    (let* ((strings (mapcar (lambda (x) (if (consp x) (car x) x))
                            items))
           (length (apply 'max
                          (mapcar #'string-width strings)))
           (window (get-buffer-window (current-buffer) 0))
           (wwidth (if window (1- (window-width window)) 79))
           (columns (min (max 2 (/ wwidth (+ 2 length)))
                         (max 1 (/ (length strings) 2))))
           (colwidth (/ wwidth columns))
           (column 0)
           (first t)
           laststring
           result)
      (dolist (str strings)
        (unless (equal laststring str)
          (setq laststring str)
          (let ((length (string-width str))
                (value  (cdr (assq str items))))
            (unless first
              (if (or (< wwidth (+ (max colwidth length) column))
                      (zerop length))
                  (progn
                    (insert "\n" (if (zerop length) "\n" ""))
                    (setq column 0))
                (insert " \t")
                (set-text-properties (1- (point)) (point)
                                     `(display (space :align-to ,column)))))
            (setq first (zerop length))
            (when value
              (push (cons (point) value) result))
            (insert (if value
                        str
                      (propertize str 'face 'avy-menu-inactive)))
            (setq column (+ column
                            (* colwidth (ceiling length colwidth)))))))
      (reverse result))))

(provide 'avy-menu)

;;; avy-menu.el ends here