;;; dropdown-list.el --- dropdown menu interface ;; Copyright (C) 2008 Jaeyoun Chung ;; Author: jay AT kldp DOT org ;; Keywords: convenience ;; ;; overlay code stolen from company-mode.el ;; ;;; Code: (defface dropdown-list-face '((t :inherit default :background "lightyellow" :foreground "black")) "*Bla." :group 'dropdown-list) (defface dropdown-list-selection-face '((t :inherit dropdown-list :background "purple")) "*Bla." :group 'dropdown-list) (defvar dropdown-list-overlays nil) (defun dropdown-list-hide () (while dropdown-list-overlays (delete-overlay (pop dropdown-list-overlays)))) (defun dropdown-list-put-overlay (beg end &optional prop value prop2 value2) (let ((ov (make-overlay beg end))) (overlay-put ov 'window t) (when prop (overlay-put ov prop value) (when prop2 (overlay-put ov prop2 value2))) ov)) (defun dropdown-list-line (start replacement &optional no-insert) ;; start might be in the middle of a tab, which means we need to hide the ;; tab and add spaces (let ((end (+ start (length replacement))) beg-point end-point before-string after-string) (goto-char (point-at-eol)) (if (< (current-column) start) (progn (setq before-string (make-string (- start (current-column)) ? )) (setq beg-point (point))) (goto-char (point-at-bol)) ;; Emacs bug, move-to-column is wrong otherwise (move-to-column start) (setq beg-point (point)) (when (> (current-column) start) (goto-char (1- (point))) (setq beg-point (point)) (setq before-string (make-string (- start (current-column)) ? )))) (move-to-column end) (setq end-point (point)) (let ((end-offset (- (current-column) end))) (when (> end-offset 0) (setq after-string (make-string end-offset ?b)))) (when no-insert ;; prevent inheriting of faces (setq before-string (when before-string (propertize before-string 'face 'default))) (setq after-string (when after-string (propertize after-string 'face 'default)))) (let ((string (concat before-string replacement after-string))) (if no-insert string (push (dropdown-list-put-overlay beg-point end-point 'invisible t 'after-string string) dropdown-list-overlays))))) (defun dropdown-list-start-column (display-width) (let ((column (mod (current-column) (window-width))) (width (window-width))) (cond ((<= (+ column display-width) width) column) ((> column display-width) (- column display-width)) ((>= width display-width) (- width display-width)) (t nil)))) (defun dropdown-list-move-to-start-line (candidate-count) (decf candidate-count) (let ((above-line-count (save-excursion (- (vertical-motion (- candidate-count))))) (below-line-count (save-excursion (vertical-motion candidate-count)))) (cond ((= below-line-count candidate-count) t) ((= above-line-count candidate-count) (vertical-motion (- candidate-count)) t) ((>= (+ below-line-count above-line-count) candidate-count) (vertical-motion (- (- candidate-count below-line-count))) t) (t nil)))) (defun dropdown-list-at-point (candidates &optional selidx) (dropdown-list-hide) (let* ((lengths (mapcar #'length candidates)) (max-length (apply #'max lengths)) (start (dropdown-list-start-column (+ max-length 3))) (i -1) (candidates (mapcar* (lambda (candidate length) (let ((diff (- max-length length))) (propertize (concat (if (> diff 0) (concat candidate (make-string diff ? )) (substring candidate 0 max-length)) (format "%3d" (+ 2 i))) 'face (if (eql (incf i) selidx) 'dropdown-list-selection-face 'dropdown-list-face)))) candidates lengths))) (save-excursion (and start (dropdown-list-move-to-start-line (length candidates)) (loop initially (vertical-motion 0) for candidate in candidates do (dropdown-list-line (+ (current-column) start) candidate) while (/= (vertical-motion 1) 0) finally return t))))) (defun dropdown-list (candidates) (let ((selection) (temp-buffer)) (save-window-excursion (unwind-protect (let ((candidate-count (length candidates)) done key selidx) (while (not done) (unless (dropdown-list-at-point candidates selidx) (switch-to-buffer (setq temp-buffer (get-buffer-create "*selection*")) 'norecord) (delete-other-windows) (delete-region (point-min) (point-max)) (insert (make-string (length candidates) ?\n)) (goto-char (point-min)) (dropdown-list-at-point candidates selidx)) (setq key (read-key-sequence "")) (cond ((and (stringp key) (>= (aref key 0) ?1) (<= (aref key 0) (+ ?0 (min 9 candidate-count)))) (setq selection (- (aref key 0) ?1) done t)) ((member key '("" [up])) (setq selidx (mod (+ candidate-count (1- (or selidx 0))) candidate-count))) ((member key '("" [down])) (setq selidx (mod (1+ (or selidx -1)) candidate-count))) ((member key '(" "))) ((member key '(" " [return] " ")) (setq selection selidx done t)) (t (setq done t))))) (dropdown-list-hide) (and temp-buffer (kill-buffer temp-buffer))) selection))) (provide 'dropdown-list) ;;; dropdown-list.el ends here