1 ;;; dropdown-list.el --- Drop-down menu interface
3 ;; Filename: dropdown-list.el
4 ;; Description: Drop-down menu interface
5 ;; Copyright (C) 2008-2012 Free Software Foundation, Inc.
6 ;; Author: Jaeyoun Chung [jay.chung@gmail.com]
8 ;; Authors: pluskid <pluskid@gmail.com>, João Távora <joaotavora@gmail.com>
9 ;; Created: Sun Mar 16 11:20:45 2008 (Pacific Daylight Time)
11 ;; Last-Updated: Sun Mar 16 12:19:49 2008 (Pacific Daylight Time)
14 ;; URL: http://www.emacswiki.org/cgi-bin/wiki/dropdown-list.el
15 ;; Keywords: convenience menu
16 ;; Compatibility: GNU Emacs 21.x, GNU Emacs 22.x
18 ;; Features that might be required by this library:
22 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
26 ;; According to Jaeyoun Chung, "overlay code stolen from company-mode.el."
28 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
33 ;; Clean-up - e.g. use char-to-string for control chars removed by email posting.
34 ;; Moved example usage code (define-key*, command-selector) inside the library.
35 ;; Require cl.el at byte-compile time.
36 ;; Added GPL statement.
37 ;; 2008/01/06 Jaeyoun Chung
38 ;; Posted to gnu-emacs-sources@gnu.org at 9:10 p.m.
40 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
42 ;; This program is free software: you can redistribute it and/or modify
43 ;; it under the terms of the GNU General Public License as published by
44 ;; the Free Software Foundation, either version 3 of the License, or
45 ;; (at your option) any later version.
47 ;; This program is distributed in the hope that it will be useful,
48 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
49 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
50 ;; GNU General Public License for more details.
52 ;; You should have received a copy of the GNU General Public License
53 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
55 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
59 (eval-when-compile (require 'cl)) ;; decf, fourth, incf, loop, mapcar*
61 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
63 (defface dropdown-list-face
64 '((t :inherit default :background "lightyellow" :foreground "black"))
65 "*Bla." :group 'dropdown-list)
67 (defface dropdown-list-selection-face
68 '((t :inherit dropdown-list-face :background "purple"))
69 "*Bla." :group 'dropdown-list)
71 (defvar dropdown-list-overlays nil)
73 (defun dropdown-list-hide ()
74 (while dropdown-list-overlays
75 (delete-overlay (pop dropdown-list-overlays))))
77 (defun dropdown-list-put-overlay (beg end &optional prop value prop2 value2)
78 (let ((ov (make-overlay beg end)))
79 (overlay-put ov 'window t)
81 (overlay-put ov prop value)
82 (when prop2 (overlay-put ov prop2 value2)))
85 (defun dropdown-list-line (start replacement &optional no-insert)
86 ;; start might be in the middle of a tab, which means we need to hide the
88 (let ((end (+ start (length replacement)))
90 before-string after-string)
91 (goto-char (point-at-eol))
92 (if (< (current-column) start)
93 (progn (setq before-string (make-string (- start (current-column)) ? ))
94 (setq beg-point (point)))
95 (goto-char (point-at-bol)) ;; Emacs bug, move-to-column is wrong otherwise
96 (move-to-column start)
97 (setq beg-point (point))
98 (when (> (current-column) start)
99 (goto-char (1- (point)))
100 (setq beg-point (point))
101 (setq before-string (make-string (- start (current-column)) ? ))))
103 (setq end-point (point))
104 (let ((end-offset (- (current-column) end)))
105 (when (> end-offset 0) (setq after-string (make-string end-offset ?b))))
107 ;; prevent inheriting of faces
108 (setq before-string (when before-string (propertize before-string 'face 'default)))
109 (setq after-string (when after-string (propertize after-string 'face 'default))))
110 (let ((string (concat before-string replacement after-string)))
113 (push (dropdown-list-put-overlay beg-point end-point 'invisible t
114 'after-string string)
115 dropdown-list-overlays)))))
117 (defun dropdown-list-start-column (display-width)
118 (let ((column (mod (current-column) (window-width)))
119 (width (window-width)))
120 (cond ((<= (+ column display-width) width) column)
121 ((> column display-width) (- column display-width))
122 ((>= width display-width) (- width display-width))
125 (defun dropdown-list-move-to-start-line (candidate-count)
126 (decf candidate-count)
127 (let ((above-line-count (save-excursion (- (vertical-motion (- candidate-count)))))
128 (below-line-count (save-excursion (vertical-motion candidate-count))))
129 (cond ((= below-line-count candidate-count)
131 ((= above-line-count candidate-count)
132 (vertical-motion (- candidate-count))
134 ((>= (+ below-line-count above-line-count) candidate-count)
135 (vertical-motion (- (- candidate-count below-line-count)))
139 (defun dropdown-list-at-point (candidates &optional selidx)
141 (let* ((lengths (mapcar #'length candidates))
142 (max-length (apply #'max lengths))
143 (start (dropdown-list-start-column (+ max-length 3)))
145 (candidates (mapcar* (lambda (candidate length)
146 (let ((diff (- max-length length)))
148 (concat (if (> diff 0)
149 (concat candidate (make-string diff ? ))
150 (substring candidate 0 max-length))
151 (format "%3d" (+ 2 i)))
152 'face (if (eql (incf i) selidx)
153 'dropdown-list-selection-face
154 'dropdown-list-face))))
159 (dropdown-list-move-to-start-line (length candidates))
160 (loop initially (vertical-motion 0)
161 for candidate in candidates
162 do (dropdown-list-line (+ (current-column) start) candidate)
163 while (/= (vertical-motion 1) 0)
164 finally return t)))))
166 (defun dropdown-list (candidates)
169 (save-window-excursion
171 (let ((candidate-count (length candidates))
174 (unless (dropdown-list-at-point candidates selidx)
175 (switch-to-buffer (setq temp-buffer (get-buffer-create "*selection*"))
177 (delete-other-windows)
178 (delete-region (point-min) (point-max))
179 (insert (make-string (length candidates) ?\n))
180 (goto-char (point-min))
181 (dropdown-list-at-point candidates selidx))
182 (setq key (read-key-sequence ""))
183 (cond ((and (stringp key)
185 (<= (aref key 0) (+ ?0 (min 9 candidate-count))))
186 (setq selection (- (aref key 0) ?1)
188 ((member key `(,(char-to-string ?\C-p) [up] "p"))
189 (setq selidx (mod (+ candidate-count (1- (or selidx 0)))
191 ((member key `(,(char-to-string ?\C-n) [down] "n"))
192 (setq selidx (mod (1+ (or selidx -1)) candidate-count)))
193 ((member key `(,(char-to-string ?\f))))
194 ((member key `(,(char-to-string ?\r) [return]))
195 (setq selection selidx
199 (and temp-buffer (kill-buffer temp-buffer)))
201 ;; (message "your selection => %d: %s" selection (nth selection candidates))
205 (defun define-key* (keymap key command)
206 "Add COMMAND to the multiple-command binding of KEY in KEYMAP.
207 Use multiple times to bind different COMMANDs to the same KEY."
208 (define-key keymap key (combine-command command (lookup-key keymap key))))
210 (defun combine-command (command defs)
211 "$$$$$ FIXME - no doc string"
212 (cond ((null defs) command)
214 (eq 'lambda (car defs))
216 (listp (fourth defs))
217 (eq 'command-selector (car (fourth defs))))
218 (unless (member `',command (cdr (fourth defs)))
219 (setcdr (fourth defs) (nconc (cdr (fourth defs)) `(',command))))
222 `(lambda () (interactive) (command-selector ',defs ',command)))))
224 (defvar command-selector-last-command nil "$$$$$ FIXME - no doc string")
226 (defun command-selector (&rest candidates)
227 "$$$$$ FIXME - no doc string"
228 (if (and (eq last-command this-command) command-selector-last-command)
229 (call-interactively command-selector-last-command)
230 (let* ((candidate-strings
231 (mapcar (lambda (candidate)
232 (format "%s" (if (symbolp candidate)
234 (let ((s (format "%s" candidate)))
235 (if (>= (length s) 7)
236 (concat (substring s 0 7) "...")
239 (selection (dropdown-list candidate-strings)))
241 (let ((cmd (nth selection candidates)))
242 (call-interactively cmd)
243 (setq command-selector-last-command cmd))))))
247 (provide 'dropdown-list)
249 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
250 ;;; dropdown-list.el ends here