From 86a9bf1366ecae1d3d196b9502864b80780c70bb Mon Sep 17 00:00:00 2001 From: Lev Lamberov Date: Fri, 4 Nov 2016 23:28:36 +0500 Subject: New upstream version 0.1.6 --- .ert-runner | 1 + .gitignore | 1 + .travis.yml | 12 ++ Cask | 9 ++ README.md | 105 ++++++++++++++ ido-vertical-mode.el | 316 +++++++++++++++++++++++++++++++++++++++++ screenshot.gif | Bin 0 -> 43811 bytes test/ido-vertical-mode-test.el | 124 ++++++++++++++++ 8 files changed, 568 insertions(+) create mode 100644 .ert-runner create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Cask create mode 100644 README.md create mode 100644 ido-vertical-mode.el create mode 100644 screenshot.gif create mode 100644 test/ido-vertical-mode-test.el 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 +;; 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 . + +;;; 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 "") '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 + (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 Binary files /dev/null and b/screenshot.gif 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))))) -- cgit v1.2.3