summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLev Lamberov <dogsleg@debian.org>2016-11-04 23:28:36 +0500
committerLev Lamberov <dogsleg@debian.org>2016-11-04 23:28:36 +0500
commit86a9bf1366ecae1d3d196b9502864b80780c70bb (patch)
tree084f85ca0a37441c00f4e4a224ecbd5121a2d92c
New upstream version 0.1.6
-rw-r--r--.ert-runner1
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml12
-rw-r--r--Cask9
-rw-r--r--README.md105
-rw-r--r--ido-vertical-mode.el316
-rw-r--r--screenshot.gifbin0 -> 43811 bytes
-rw-r--r--test/ido-vertical-mode-test.el124
8 files changed, 568 insertions, 0 deletions
diff --git a/.ert-runner b/.ert-runner
new file mode 100644
index 0000000..e1f54e5
--- /dev/null
+++ b/.ert-runner
@@ -0,0 +1 @@
+-l ido-vertical-mode.el \ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d4691b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.cask
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..c879fad
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,12 @@
+language: emacs-lisp
+before_install:
+ - curl -fsSkL https://gist.github.com/rejeep/7736123/raw > travis.sh && source ./travis.sh
+ - export PATH="/home/travis/.cask/bin:$PATH"
+ - export PATH="/home/travis/.evm/bin:$PATH"
+ - evm install $EVM_EMACS --use
+ - cask
+env:
+ - EVM_EMACS=emacs-24.4-bin
+script:
+ - emacs --version
+ - cask exec ert-runner
diff --git a/Cask b/Cask
new file mode 100644
index 0000000..46330e9
--- /dev/null
+++ b/Cask
@@ -0,0 +1,9 @@
+(source melpa)
+(source marmalade)
+
+(package-file "ido-vertical-mode.el")
+
+(depends-on "ido")
+
+(development
+ (depends-on "ert-runner"))
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6aa7344
--- /dev/null
+++ b/README.md
@@ -0,0 +1,105 @@
+# ido-vertical-mode.el
+
+[![Build Status](https://travis-ci.org/gempesaw/ido-vertical-mode.el.svg?branch=master)](https://travis-ci.org/gempesaw/ido-vertical-mode.el) Makes ido-mode display vertically.
+
+![screenshot.gif](screenshot.gif)
+
+This mode takes care of some caveats that are otherwise ugly to store
+in your init file.
+
+You may also be interested in
+[`ido-ubiquitous`](https://github.com/DarwinAwardWinner/ido-ubiquitous)
+and [`smex`](https://github.com/nonsequitur/smex).
+
+## Install via [MELPA Stable](http://stable.melpa.org/#/) or [marmalade](http://marmalade-repo.org)
+
+`M-x` `package-install` `ido-vertical-mode`
+
+If you use MELPA instead of MELPA Stable, there's no guarantee that
+you'll get something that works; I've accidentally broken master
+before and will unfortunately probably do it again :(
+
+## Turn it on
+
+ (require 'ido-vertical-mode)
+ (ido-mode 1)
+ (ido-vertical-mode 1)
+
+Or you can use `M-x ido-vertical-mode` to toggle it.
+
+## Customize
+
+#### Count
+
+Show the count of candidates:
+
+```elisp
+(setq ido-vertical-show-count t)
+```
+
+#### Colors
+
+Make it look like @abo-abo's [blog post](http://oremacs.com/2015/02/09/ido-vertical/):
+
+```elisp
+(setq ido-use-faces t)
+(set-face-attribute 'ido-vertical-first-match-face nil
+ :background "#e5b7c0")
+(set-face-attribute 'ido-vertical-only-match-face nil
+ :background "#e52b50"
+ :foreground "white")
+(set-face-attribute 'ido-vertical-match-face nil
+ :foreground "#b00000")
+(ido-vertical-mode 1)
+```
+
+Make it look like the screenshot above:
+
+```elisp
+(setq ido-use-faces t)
+(set-face-attribute 'ido-vertical-first-match-face nil
+ :background nil
+ :foreground "orange")
+(set-face-attribute 'ido-vertical-only-match-face nil
+ :background nil
+ :foreground nil)
+(set-face-attribute 'ido-vertical-match-face nil
+ :foreground nil)
+(ido-vertical-mode 1)
+```
+
+Reset the faces to their defaults:
+
+```elisp
+(set-face-attribute 'ido-vertical-first-match-face nil
+ :background nil
+ :foreground nil)
+(set-face-attribute 'ido-vertical-only-match-face nil
+ :background nil
+ :foreground nil)
+(set-face-attribute 'ido-vertical-match-face nil
+ :background nil
+ :foreground nil)
+(ido-vertical-mode 1)
+
+;; optionally
+(setq ido-use-faces nil)
+```
+
+#### Keybindings
+
+Since the prospects are listed vertically, it makes sense to use
+`C-n/C-p` to navigate through the options. These are added to the
+`ido-completion-map` by default (and `ido-toggle-prefix`, previously
+on `C-p`, is moved to `M-p`).
+
+You also have the option to rebind some or all of the arrow keys with
+`ido-vertical-define-keys`:
+
+ (setq ido-vertical-define-keys 'C-n-C-p-up-and-down)
+
+to use up and down to navigate the options, or
+
+ (setq ido-vertical-define-keys 'C-n-C-p-up-down-left-right)
+
+to use left and right to move through the history/directories.
diff --git a/ido-vertical-mode.el b/ido-vertical-mode.el
new file mode 100644
index 0000000..d8ffd88
--- /dev/null
+++ b/ido-vertical-mode.el
@@ -0,0 +1,316 @@
+;;; ido-vertical-mode.el --- Makes ido-mode display vertically.
+
+;; Copyright (C) 2013, 2014 Steven Degutis
+
+;; Author: Steven Degutis
+;; Maintainer: Daniel Gempesaw <gempesaw@gmail.com>
+;; Version: 0.1.5
+;; Keywords: convenience
+;; URL: https://github.com/gempesaw/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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Makes ido-mode display prospects vertically
+
+;;; Code:
+
+(require 'ido)
+
+;;; 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
+ '("\n-> " ; left bracket around prospect list
+ "" ; right bracket around prospect list
+ "\n " ; separator between prospects, depends on `ido-separator`
+ "\n ..." ; 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]"
+ "\n-> " ; left bracket around the sole remaining completion
+ "" ; right bracket around the sole remaining completion
+ )
+
+ "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'.")
+
+(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-mode 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-mode)
+
+(defvar ido-vertical-count-active nil
+ "Used internally to track whether we're already showing the count")
+
+(defcustom ido-vertical-define-keys 'C-n-and-C-p-only
+ "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-mode)
+
+(defface ido-vertical-first-match-face
+ '((t (:inherit ido-first-match)))
+ "Face used by Ido Vertical for highlighting first match."
+ :group 'ido-vertical-mode)
+
+(defface ido-vertical-only-match-face
+ '((t (:inherit ido-only-match)))
+ "Face used by Ido Vertical for highlighting only match."
+ :group 'ido-vertical-mode)
+
+(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-mode)
+
+;; 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))
+ 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.
+ (if (< lencomps (1+ ido-max-prospects))
+ (progn
+ (setq additional-items-indicator "\n")
+ (setq comps (append comps (make-list (- (1+ ido-max-prospects) lencomps) "")))))
+
+ (when ido-use-faces
+ (dotimes (i ido-max-prospects)
+ (setf (nth i comps) (substring (if (listp (nth i comps))
+ (car (nth i comps))
+ (nth i comps))
+ 0))
+ (when (string-match name (nth i comps))
+ (ignore-errors
+ (add-face-text-property (match-beginning 0)
+ (match-end 0)
+ 'ido-vertical-match-face
+ nil (nth i comps))))))
+
+ (if (and ind ido-use-faces)
+ (put-text-property 0 1 'face 'ido-indicator ind))
+
+ (when ido-vertical-show-count
+ (setcar ido-vertical-decorations (format " [%d]\n-> " lencomps))
+ (setq ido-vertical-count-active t))
+ (when (and (not ido-vertical-show-count)
+ ido-vertical-count-active)
+ (setcar ido-vertical-decorations "\n-> ")
+ (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 turn-on-ido-vertical ()
+ (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-decorations ido-vertical-decorations)
+ (fset 'ido-completions 'ido-vertical-completions)
+
+ (add-hook 'ido-minibuffer-setup-hook 'ido-vertical-disable-line-truncation)
+ (add-hook 'ido-setup-hook 'ido-vertical-define-keys))
+
+(defun turn-off-ido-vertical ()
+ (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))
+
+(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 "M-p") '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 "<up>") 'ido-prev-match)
+ (define-key ido-completion-map (kbd "<down>") 'ido-next-match))
+ (when (eq ido-vertical-define-keys 'C-n-C-p-up-down-left-right)
+ (define-key ido-completion-map (kbd "<left>") 'ido-vertical-prev-match)
+ (define-key ido-completion-map (kbd "<right>") 'ido-vertical-next-match)))
+
+;;;###autoload
+(define-minor-mode ido-vertical-mode
+ "Makes ido-mode display vertically."
+ :global t
+ (if ido-vertical-mode
+ (turn-on-ido-vertical)
+ (turn-off-ido-vertical)))
+
+(provide 'ido-vertical-mode)
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
+;;; ido-vertical-mode.el ends here
diff --git a/screenshot.gif b/screenshot.gif
new file mode 100644
index 0000000..0e31513
--- /dev/null
+++ b/screenshot.gif
Binary files differ
diff --git a/test/ido-vertical-mode-test.el b/test/ido-vertical-mode-test.el
new file mode 100644
index 0000000..bb214f6
--- /dev/null
+++ b/test/ido-vertical-mode-test.el
@@ -0,0 +1,124 @@
+(require 'ert)
+(require 'ido)
+(require 'ido-vertical-mode)
+
+(ido-mode 1)
+(ido-vertical-mode 1)
+
+;;; invoke ido-switch-buffer to initialize ido variables that would
+;;; otherwise throw void error
+(execute-kbd-macro [24 98 return] 1)
+
+(ert-deftest ivm-should-install-decorations ()
+ (ido-vertical-mode 1)
+ (let ((prospects (ido-completions "")))
+ (should (string-match "->" prospects))
+ (should (string-match "\n" prospects))))
+
+(ert-deftest ivm-should-indicate-more-results ()
+ (ido-vertical-mode 1)
+ (let ((buffers (mapcar (lambda (num)
+ (get-buffer-create
+ (format "ivm-test-buffer-%s" num)))
+ (number-sequence 1 11)))
+ prospects)
+ (save-window-excursion
+ (execute-kbd-macro [24 98 ?i ?v ?m ?- ?t ?e ?s ?t])
+ (setq prospects (ido-completions "ivm-test"))
+ (should (string-match "\.\.\.$" prospects)))
+ (mapc 'kill-buffer buffers)))
+
+(ert-deftest ivm-should-properly-disable-itself ()
+ (ido-vertical-mode 1)
+ (ido-vertical-mode -1)
+ (should (not (string-match "\n" (ido-completions "")))))
+
+(ert-deftest ivm-should-show-confirm-dialog ()
+ (ido-vertical-mode 1)
+ (let* ((no-results [24 98 ?t ?h ?i ?s ?s ?h ?o ?u ?l ?d ?n ?o ?t ?m ?a ?t ?c ?h])
+ (confirm (vconcat no-results [return])))
+ (execute-kbd-macro no-results 1)
+ (should (string-match "No Match" (buffer-name (current-buffer))))
+ (kill-buffer (current-buffer))
+ (execute-kbd-macro confirm 1)
+ (should (string-match "Confirm" (buffer-name (current-buffer))))
+ (kill-buffer (current-buffer))))
+
+(ert-deftest ivm-should-handle-fontified-candidates ()
+ (let ((ido-matches '((#(".ido.last" 1 4 (face ido-vertical-match-face)) "/Users/JS/")
+ ""
+ (#("200-ido.el" 4 7 (face ido-vertical-match-face)) "/Users/JS/.emacs.d/configs/" "~/.emacs.d/configs/"))))
+ (should (ido-vertical-completions "ido"))))
+
+;;; The following tests are pretty fragile. ido-vertical-completions
+;;; depends on the global value of ido-matches, which we set. It
+;;; returns the completions as a string, and we can check the text
+;;; properties of particular characters in the return to see that they
+;;; have the expected faces.
+
+(ert-deftest ivm-should-highlight-matched-candidates ()
+ (let* ((ido-use-faces t)
+ (ido-matches '("ido" "ido-vertical"))
+ (ido-query (ido-vertical-completions "ido"))
+ (first-comp-pos (string-match "ido" ido-query))
+ (ido-query-first-comp-face (get-text-property first-comp-pos 'face ido-query))
+ (ido-query-second-comp-face (get-text-property (+ first-comp-pos 7) 'face ido-query))
+ (debug nil))
+ (when debug (prin1 ido-query))
+ (should (and (memq 'ido-vertical-match-face ido-query-first-comp-face)
+ (memq 'ido-vertical-first-match-face ido-query-first-comp-face)))
+ (should (and (memq 'ido-vertical-match-face `(,ido-query-second-comp-face))
+ (eq nil (get-text-property 19 'face ido-query))))))
+
+(ert-deftest ivm-should-not-highlight-without-ido-use-faces ()
+ (let* ((ido-use-faces nil)
+ (ido-matches '("ido"))
+ (ido-query (ido-vertical-completions "ido"))
+ (first-comp-pos (string-match "ido" ido-query))
+ (ido-query-first-comp-face (get-text-property first-comp-pos 'face ido-query))
+ (debug nil))
+ (when debug (prin1 ido-query))
+ (should (eq nil ido-query-first-comp-face))))
+
+(ert-deftest ivm-should-not-highlight-missed-candidates ()
+ (let* ((ido-use-faces t)
+ (ido-matches '("ido" "ido-vertical"))
+ (ido-query (ido-vertical-completions "no results"))
+ (first-comp-pos (string-match "ido" ido-query))
+ (second-comp-pos (+ 7 first-comp-pos))
+ (ido-query-first-comp-face (get-text-property first-comp-pos 'face ido-query))
+ (ido-query-second-comp-face (get-text-property second-comp-pos 'face ido-query))
+ (debug nil))
+ (when debug (prin1 ido-query))
+ (should (memq 'ido-vertical-first-match-face `(,ido-query-first-comp-face)))
+ (should (and (eq nil ido-query-second-comp-face)))))
+
+(ert-deftest ivm-should-highlight-only-candidate ()
+ (let* ((ido-use-faces t)
+ (ido-matches '("ido"))
+ (ido-query (ido-vertical-completions "no results"))
+ (first-comp-pos (string-match "ido" ido-query))
+ (ido-query-first-comp-face (get-text-property first-comp-pos 'face ido-query))
+ (debug nil))
+ (when debug (prin1 ido-query))
+ (should (memq 'ido-vertical-only-match-face `(,ido-query-first-comp-face)))))
+
+(ert-deftest ivm-should-show-count ()
+ (let* ((ido-matches '("1" "2" "3" "4" "5"))
+ (ido-vertical-show-count t)
+ (ido-use-faces nil)
+ (query (ido-vertical-completions "")))
+ ;; Exposes a bug where we were toggling the count on and off
+ ;; instead of keeping it on
+ (setq query (ido-vertical-completions ""))
+ (should (string= " [5]\n" (substring query 0 5)))
+ ;; Count should update when filtering completions
+ (setq ido-matches '("1"))
+ (setq query (ido-vertical-completions "1"))
+ (should (string= " [1]" (substring query 0 4)))))
+
+(ert-deftest ivm-should-turn-off-count ()
+ (let* ((ido-matches '("1"))
+ (ido-vertical-show-count nil)
+ (query (ido-vertical-completions "")))
+ (should (string= "\n-> " (substring-no-properties query 0 4)))))