;;; ido-vertical-mode.el --- Makes ido-mode display vertically ;; Copyright (C) 2013, 2014 Steven Degutis ;; Author: Steven Degutis ;; Maintainer: Christopher Reichert ;; Version: 1.0.1 ;; Package-Requires: ((emacs "24.4")) ;; Keywords: convenience ;; URL: https://github.com/creichert/ido-vertical-mode.el ;; 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 . ;;; Commentary: ;; Makes ido-mode display prospects vertically ;;; Code: (require 'ido) (require 'cl-lib) ;;; The following three variables and their comments are lifted ;;; directly from `ido.el'; they are defined here to avoid compile-log ;;; warnings. See `ido.el' for more information. ;; Non-nil if we should add [confirm] to prompt (defvar ido-show-confirm-message) ;; Remember if current directory is non-readable (so we cannot do completion). (defvar ido-directory-nonreadable) ;; Remember if current directory is 'huge' (so we don't want to do completion). (defvar ido-directory-too-big) (defvar ido-vertical-decorations nil "Changing the decorations does most of the work for ido-vertical This sets up newlines and arrows before, between, and after the prospects. For additional information, see `ido-decorations'.") (defcustom ido-vertical-padding " " "How many spaces to pad the completion candidates. When setting this variable in ELISP, you must also make sure `ido-vertical-decorations' is updated. In addition, if `ido-vertical-mode' is on, it must be set to the new value of `ido-vertical-decorations' for this variable to take effect in the next ido completion event." :type 'string :group 'ido-vertical :initialize 'custom-initialize-default :set (lambda (symbol value) (set-default symbol value) (setq ido-vertical-decorations (ido-vertical-make-decorations :padding value)) (when (bound-and-true-p ido-vertical-mode) (setq ido-decorations ido-vertical-decorations)))) (defcustom ido-vertical-indicator "->" "Indicator displayed next to the candidate that will be selected. When setting this variable in ELISP, you must also make sure `ido-vertical-decorations' is updated. In addition, if `ido-vertical-mode' is on, it must be set to the new value of `ido-vertical-decorations' for this variable to take effect in the next ido completion event." :type 'string :group 'ido-vertical :initialize 'custom-initialize-default :set (lambda (symbol value) (set-default symbol value) (setq ido-vertical-decorations (ido-vertical-make-decorations :indicator value)) (when (bound-and-true-p ido-vertical-mode) (setq ido-decorations ido-vertical-decorations)))) (defvar ido-vertical-old-decorations nil "The original `ido-decorations' variable We need to keep track of the original value so we can restore it when turning `ido-vertical-mode' off") (defvar ido-vertical-old-completions nil "The original `ido-completions' function We need to keep track of the original value of `ido-completions' so we can restore it when turning `ido-vertical-mode' off") (defgroup ido-vertical nil "Make ido behave vertically." :group 'ido) (defcustom ido-vertical-show-count nil "Non nil means show the count of candidates while completing." :type 'boolean :group 'ido-vertical) (defvar ido-vertical-count-active nil "Used internally to track whether we're already showing the count") (defcustom ido-vertical-define-keys nil "Defines which keys that `ido-vertical-mode' redefines." :type '(choice (const :tag "Keep default ido keys." nil) (const :tag "C-p and C-n are up & down in match" C-n-and-C-p-only) (const :tag "C-p/up and C-n/down are up and down in match." C-n-C-p-up-and-down) (const :tag "C-p/up, C-n/down are up/down in match. left or right cycle history or directory." C-n-C-p-up-down-left-right)) :group 'ido-vertical) (defcustom ido-vertical-pad-list t "Non nil means to pad the list of candidates to ensure the minibuffer area is always tall" :type 'boolean :group 'ido-vertical) (defcustom ido-vertical-disable-if-short nil "Non nil means that ido will go back to horizontal mode if the candidates all fit in the minibuffer area" :type 'boolean :group 'ido-vertical) (defface ido-vertical-first-match-face '((t (:inherit ido-first-match))) "Face used by Ido Vertical for highlighting first match." :group 'ido-vertical) (defface ido-vertical-only-match-face '((t (:inherit ido-only-match))) "Face used by Ido Vertical for highlighting only match." :group 'ido-vertical) (defface ido-vertical-match-face '((t (:inherit font-lock-variable-name-face :bold t :underline t))) "Face used by Ido Vertical for the matched part." :group 'ido-vertical) (cl-defun ido-vertical-make-decorations (&key (padding ido-vertical-padding) (indicator ido-vertical-indicator)) "Construct a new `ido-decorations' format." (list (concat "\n" indicator padding) ; left bracket around prospect list "" ; right bracket around prospect list (concat (format (format "\n%%-%ds" (length indicator)) "") padding) ; separator between prospects, depends on `ido-separator` (concat (format (format "\n%%-%ds" (length indicator)) "") padding "...") ; inserted at the end of a truncated list of prospects "[" ; left bracket around common match string "]" ; right bracket around common match string " [No match]" " [Matched]" " [Not readable]" " [Too big]" " [Confirm]" (concat "\n" indicator padding) ; left bracket around the sole remaining completion "" ; right bracket around the sole remaining completion )) (defun ido-vertical-or-horizontal-completions (name) (if (and ido-vertical-disable-if-short (<= (length ido-matches) ido-max-prospects)) (let ((short-result (let ((ido-decorations ido-vertical-old-decorations)) (funcall ido-vertical-old-completions name)))) (if (>= (window-body-width (minibuffer-window)) (+ (minibuffer-prompt-width) (length short-result))) short-result (ido-vertical-completions name))) (ido-vertical-completions name))) ;; borrowed from ido.el and modified to work better when vertical (defun ido-vertical-completions (name) ;; Return the string that is displayed after the user's text. ;; Modified from `icomplete-completions'. (let* ((comps ido-matches) (ind (and (consp (car comps)) (> (length (cdr (car comps))) 1) ido-merged-indicator)) (lencomps (length comps)) (additional-items-indicator (nth 3 ido-decorations)) (comps-empty (null comps)) (ncomps lencomps) first) ;; Keep the height of the suggestions list constant by padding ;; when lencomps is too small. Also, if lencomps is too short, we ;; should not indicate that there are additional prospects. (when (< lencomps (1+ ido-max-prospects)) (setq additional-items-indicator "\n") (when ido-vertical-pad-list (setq comps (append comps (make-list (- (1+ ido-max-prospects) lencomps) ""))) (setq ncomps (length comps)))) (if (not ido-incomplete-regexp) (when ido-use-faces ;; Make a copy of [ido-matches], otherwise the selected string ;; could contain text properties which could lead to weird ;; artifacts, e.g. buffer-file-name having text properties. (setq comps (cl-loop for comps-i being the elements of (if (eq comps ido-matches) ido-matches comps) do (setf comps-i (substring-no-properties (if (listp comps-i) (car comps-i) comps-i) 0)) (when (string-match (if ido-enable-regexp name (regexp-quote name)) comps-i) (ignore-errors (add-face-text-property (match-beginning 0) (match-end 0) 'ido-vertical-match-face nil comps-i))) collect comps-i)))) (if (and ind ido-use-faces) (put-text-property 0 1 'face 'ido-indicator ind)) (when ido-vertical-show-count (setcar ido-vertical-decorations (concat (format " [%d]\n%s" lencomps ido-vertical-indicator) ido-vertical-padding)) (setq ido-vertical-count-active t)) (when (and (not ido-vertical-show-count) ido-vertical-count-active) (setcar ido-vertical-decorations (concat "\n" ido-vertical-indicator ido-vertical-padding)) (setq ido-vertical-count-active nil)) (if (and ido-use-faces comps) (let* ((fn (ido-name (car comps))) (ln (length fn))) (setq first (format "%s" fn)) (if (fboundp 'add-face-text-property) (add-face-text-property 0 (length first) (cond ((> lencomps 1) 'ido-vertical-first-match-face) (ido-incomplete-regexp 'ido-incomplete-regexp) (t 'ido-vertical-only-match-face)) nil first) (put-text-property 0 ln 'face (if (= lencomps 1) (if ido-incomplete-regexp 'ido-incomplete-regexp 'ido-vertical-only-match-face) 'ido-vertical-first-match-face) first)) (if ind (setq first (concat first ind))) (setq comps (cons first (cdr comps))))) ;; Previously we'd check null comps to see if the list was ;; empty. We pad the list with empty items to keep the list at a ;; constant height, so we have to check if the entire list is ;; empty, instead of (null comps) (cond (comps-empty (cond (ido-show-confirm-message (or (nth 10 ido-decorations) " [Confirm]")) (ido-directory-nonreadable (or (nth 8 ido-decorations) " [Not readable]")) (ido-directory-too-big (or (nth 9 ido-decorations) " [Too big]")) (ido-report-no-match (nth 6 ido-decorations)) ;; [No match] (t ""))) (ido-incomplete-regexp (concat " " (car comps))) ((null (cdr comps)) ;one match (concat (concat (nth 11 ido-decorations) ;; [ ... ] (ido-name (car comps)) (nth 12 ido-decorations)) (if (not ido-use-faces) (nth 7 ido-decorations)))) ;; [Matched] (t ;multiple matches (let* ((items (if (> ido-max-prospects 0) (1+ ido-max-prospects) 999)) (alternatives (apply #'concat (cdr (apply #'nconc (mapcar (lambda (com) (setq com (ido-name com)) (setq items (1- items)) (cond ((< items 0) ()) ((= items 0) (list additional-items-indicator)) ; " | ..." (t (list (nth 2 ido-decorations) ; " | " (let ((str (substring com 0))) (if (and ido-use-faces (not (string= str first)) (ido-final-slash str)) (put-text-property 0 (length str) 'face 'ido-subdir str)) str))))) comps)))))) (concat ;; put in common completion item -- what you get by pressing tab (if (and (stringp ido-common-match-string) (> (length ido-common-match-string) (length name))) (concat (nth 4 ido-decorations) ;; [ ... ] (substring ido-common-match-string (length name)) (nth 5 ido-decorations))) ;; list all alternatives (nth 0 ido-decorations) ;; { ... } alternatives (nth 1 ido-decorations))))))) (defun ido-vertical-disable-line-truncation () "Prevent the newlines in the minibuffer from being truncated" (set (make-local-variable 'truncate-lines) nil)) (defun ido-vertical-turn-on () (if (and (eq nil ido-vertical-old-decorations) (eq nil ido-vertical-old-completions)) (progn (setq ido-vertical-old-decorations ido-decorations) (setq ido-vertical-old-completions (symbol-function 'ido-completions)))) (setq ido-vertical-decorations (ido-vertical-make-decorations) ido-decorations ido-vertical-decorations) (fset 'ido-completions 'ido-vertical-or-horizontal-completions) (add-hook 'ido-minibuffer-setup-hook 'ido-vertical-disable-line-truncation) (add-hook 'ido-setup-hook 'ido-vertical-define-keys)) (make-obsolete 'turn-on-ido-vertical 'ido-vertical-turn-on "1.0.1") (defun ido-vertical-turn-off () (setq ido-decorations ido-vertical-old-decorations) (fset 'ido-completions ido-vertical-old-completions) (remove-hook 'ido-minibuffer-setup-hook 'ido-vertical-disable-line-truncation) (remove-hook 'ido-setup-hook 'ido-vertical-define-keys)) (make-obsolete 'turn-off-ido-vertical 'ido-vertical-turn-off "1.0.1") (defun ido-vertical-next-match () "Call the correct next-match function for right key. This is based on: - Different functions for completing directories and prior history. " (interactive) (cond ((and (boundp 'item) item (eq item 'file)) (ido-next-match-dir)) (t (next-history-element 1)))) (defun ido-vertical-prev-match () "Call the correct prev-match function for left key. This is based on: - Different functions for completing directories and prior history. " (interactive) (cond ((and (boundp 'item) item (eq item 'file)) (ido-prev-match-dir)) (t (previous-history-element 1)))) (defun ido-vertical-define-keys () ;; C-n/p is more intuitive in vertical layout (when ido-vertical-define-keys (define-key ido-completion-map (kbd "C-n") 'ido-next-match) (define-key ido-completion-map (kbd "C-p") 'ido-prev-match) (define-key ido-completion-map (kbd "C-c C-t") 'ido-toggle-prefix)) (when (memq ido-vertical-define-keys '(C-n-C-p-up-and-down C-n-C-p-up-down-left-right)) (define-key ido-completion-map (kbd "") 'ido-prev-match) (define-key ido-completion-map (kbd "") 'ido-next-match)) (when (eq ido-vertical-define-keys 'C-n-C-p-up-down-left-right) (define-key ido-completion-map (kbd "") 'ido-vertical-prev-match) (define-key ido-completion-map (kbd "") 'ido-vertical-next-match))) ;;;###autoload (define-minor-mode ido-vertical-mode "Makes ido-mode display vertically." :global t (if ido-vertical-mode (ido-vertical-turn-on) (ido-vertical-turn-off))) (provide 'ido-vertical-mode) ;; Local Variables: ;; indent-tabs-mode: nil ;; End: ;;; ido-vertical-mode.el ends here