[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] master 6a874a4 11/67: Add `ivy' back end
From: |
Oleh Krehel |
Subject: |
[elpa] master 6a874a4 11/67: Add `ivy' back end |
Date: |
Sun, 22 Mar 2015 17:33:51 +0000 |
branch: master
commit 6a874a4ed341920d2bcfcd78ebda885b66d89908
Author: Oleh Krehel <address@hidden>
Commit: Oleh Krehel <address@hidden>
Add `ivy' back end
* ivy.el: New completion back end.
* swiper.el: Package doesn't depend on `helm'.
(ivy): Depend on `ivy'.
(swiper-completion-method): New defcustom.
(swiper--window): New var.
(swiper--helm-keymap): Rename from `swiper--keymap'.
(swiper): Change to a dispatch.
(swiper--init): New defun.
(swiper--ivy): New command.
(swiper--helm): New command.
(swiper--cleanup): New defun.
(swiper--update-input-helm): Rename from `swiper--update-input'.
(swiper--update-input-ivy): New defun.
(swiper--add-overlays): New defun.
(swiper--update-sel): Update.
(swiper--subexps):
(swiper--regex-hash):
(swiper--regex): Move to ivy.
(swiper--action): Update.
---
ivy.el | 258 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
swiper.el | 220 ++++++++++++++++++++++++++++++++-------------------
2 files changed, 396 insertions(+), 82 deletions(-)
diff --git a/ivy.el b/ivy.el
new file mode 100644
index 0000000..63ccbc1
--- /dev/null
+++ b/ivy.el
@@ -0,0 +1,258 @@
+;;; ivy.el --- Incremental Vertical completYon -*- lexical-binding: t -*-
+
+;; Copyright (C) 2015 Oleh Krehel
+
+;; Author: Oleh Krehel <address@hidden>
+;; URL: https://github.com/abo-abo/ivy
+;; Version: 0.1.0
+
+;; This file is not part of GNU Emacs
+
+;; This file 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, 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.
+
+;; For a full copy of the GNU General Public License
+;; see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; This package provides `ivy-read' as an alternative to
+;; `completing-read' and similar functions.
+;;
+;; There's no intricate code to determine the best candidate.
+;; Instead, the user can navigate to it with `ivy-next-line' and
+;; `ivy-previous-line'.
+;;
+;; The matching is done by splitting the input text by spaces and
+;; re-building it into a regex.
+;; So "for example" is transformed into "\\(for\\).*\\(example\\)".
+
+;;; Code:
+;;* Customization
+(defgroup ivy nil
+ "Incremental vertical completion."
+ :group 'convenience)
+
+(defface ivy-current-match
+ '((t (:background "#e5b7c0")))
+ "Face used by Ivy for highlighting first match.")
+
+(defcustom ivy-height 10
+ "Number of lines for the minibuffer window."
+ :type 'integer)
+
+;;* User Visible
+;;** Keymap
+(defvar ivy-minibuffer-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "C-m") 'ivy-done)
+ (define-key map (kbd "C-n") 'ivy-next-line)
+ (define-key map (kbd "C-p") 'ivy-previous-line)
+ (define-key map (kbd "C-s") 'ivy-next-line)
+ (define-key map (kbd "C-r") 'ivy-previous-line)
+ (define-key map (kbd "SPC") 'self-insert-command)
+ (define-key map (kbd "DEL") 'ivy-backward-delete-char)
+ (define-key map (kbd "M-<") 'ivy-beginning-of-buffer)
+ (define-key map (kbd "M->") 'ivy-end-of-buffer)
+ map)
+ "Keymap used in the minibuffer.")
+
+;;** Commands
+(defun ivy-done ()
+ "Exit the minibuffer with the selected candidate."
+ (interactive)
+ (delete-minibuffer-contents)
+ (insert ivy--current)
+ (exit-minibuffer))
+
+(defun ivy-next-line ()
+ "Select the next completion candidate."
+ (interactive)
+ (unless (>= ivy--index (1- ivy--length))
+ (incf ivy--index)))
+
+(defun ivy-beginning-of-buffer ()
+ "Select the first completion candidate."
+ (interactive)
+ (setq ivy--index 0))
+
+(defun ivy-end-of-buffer ()
+ "Select the last completion candidate."
+ (interactive)
+ (setq ivy--index (1- ivy--length)))
+
+(defun ivy-previous-line ()
+ "Select the previous completion candidate."
+ (interactive)
+ (unless (zerop ivy--index)
+ (decf ivy--index)))
+
+(defun ivy-backward-delete-char ()
+ "Forward to `backward-delete-char'.
+On error (read-only), quit without selecting."
+ (interactive)
+ (condition-case nil
+ (backward-delete-char 1)
+ (error
+ (minibuffer-keyboard-quit))))
+
+;;** Entry Point
+(defun ivy-read (prompt collection &optional update-fn)
+ "Read a string in the minibuffer, with completion.
+PROMPT is a string to prompt with; normally it ends in a colon and a space.
+COLLECTION is a list of strings.
+UPDATE-FN is called each time the current candidate(s) is changed."
+ (setq ivy--index 0)
+ (setq ivy--old-re nil)
+ (setq ivy-text "")
+ (setq ivy--all-candidates collection)
+ (setq ivy--update-fn update-fn)
+ (unwind-protect
+ (minibuffer-with-setup-hook
+ #'ivy--minibuffer-setup
+ (read-from-minibuffer prompt))
+ (remove-hook 'post-command-hook #'ivy--exhibit)))
+
+(defvar ivy-text ""
+ "Stores the user's string as it is typed in.")
+
+;;* Implementation
+;;** Regex
+(defvar ivy--subexps 0
+ "Number of groups in the current `ivy--regex'.")
+
+(defvar ivy--regex-hash
+ (make-hash-table :test 'equal)
+ "Store pre-computed regex.")
+
+(defun ivy--regex (str)
+ "Re-build regex from STR in case it has a space."
+ (let ((hashed (gethash str ivy--regex-hash)))
+ (if hashed
+ (prog1 (cdr hashed)
+ (setq ivy--subexps (car hashed)))
+ (cdr (puthash str
+ (let ((subs (split-string str " +" t)))
+ (if (= (length subs) 1)
+ (cons
+ (setq ivy--subexps 0)
+ (car subs))
+ (cons
+ (setq ivy--subexps (length subs))
+ (mapconcat
+ (lambda (x) (format "\\(%s\\)" x))
+ subs
+ ".*"))))
+ ivy--regex-hash)))))
+
+;;** Rest
+(defun ivy--minibuffer-setup ()
+ "Setup ivy completion in the minibuffer."
+ (set (make-local-variable 'completion-show-inline-help) nil)
+ (use-local-map (make-composed-keymap ivy-minibuffer-map
+ (current-local-map)))
+ (setq-local max-mini-window-height ivy-height)
+ (add-hook 'post-command-hook #'ivy--exhibit nil t)
+ ;; show completions with empty input
+ (ivy--exhibit))
+
+(defvar ivy--all-candidates nil
+ "Store the candidates passed to `ivy-read'.")
+
+(defvar ivy--index 0
+ "Store the index of the current candidate.")
+
+(defvar ivy--length 0
+ "Store the amount of viable candidates.")
+
+(defvar ivy--current ""
+ "Current candidate.")
+
+(defvar ivy--update-fn nil
+ "Current function to call when current candidate(s) update.")
+
+(defun ivy--input ()
+ "Return the current minibuffer input."
+ ;; assume one-line minibuffer input
+ (buffer-substring-no-properties
+ (minibuffer-prompt-end)
+ (line-end-position)))
+
+(defun ivy--cleanup ()
+ "Delete the displayed completion candidates."
+ (save-excursion
+ (goto-char (minibuffer-prompt-end))
+ (delete-region (line-end-position) (point-max))))
+
+(defun ivy--exhibit ()
+ "Insert Ivy completions display.
+Should be run via minibuffer `post-command-hook'."
+ (setq ivy-text (ivy--input))
+ (ivy--cleanup)
+ (let ((text (while-no-input
+ (ivy-completions
+ ivy-text
+ ivy--all-candidates)))
+ (buffer-undo-list t)
+ deactivate-mark)
+ (when ivy--update-fn
+ (funcall ivy--update-fn))
+ ;; Do nothing if while-no-input was aborted.
+ (when (stringp text)
+ (save-excursion
+ (forward-line 1)
+ (insert text)))))
+
+(defvar ivy--old-re nil
+ "Store the old regexp.")
+
+(defvar ivy--old-cands nil
+ "Store the candidates matched by `ivy--old-re'.")
+
+(defun ivy-completions (name candidates)
+ "Return as text the current completions.
+NAME is a string of words separated by spaces that is used to
+build a regex.
+CANDIDATES is a list of strings."
+ (let* ((re (ivy--regex name))
+ (cands (if (equal re ivy--old-re)
+ ivy--old-cands
+ (setq ivy--old-re re)
+ (setq ivy--old-cands
+ (ignore-errors
+ (cl-remove-if-not
+ (lambda (x) (string-match re x))
+ candidates))))))
+ (setq ivy--length (length cands))
+ ;; should do a re-anchor here
+ (when (>= ivy--index ivy--length)
+ (setq ivy--index (1- ivy--length)))
+ (if (null cands)
+ ""
+ (let ((index ivy--index))
+ (if (< index (/ ivy-height 2))
+ (setq cands
+ (cl-subseq cands 0 (min (1- ivy-height) ivy--length)))
+ (setq cands
+ (cl-subseq cands
+ (- index (/ ivy-height 2))
+ (min (+ index (/ ivy-height 2))
+ ivy--length)))
+ (setq index (min (/ ivy-height 2)
+ (1- (length cands)))))
+ (setq ivy--current (copy-sequence
+ (nth index cands)))
+ (setf (nth index cands)
+ (propertize ivy--current 'face 'ivy-current-match))
+ (concat "\n" (mapconcat #'identity cands "\n"))))))
+
+(provide 'ivy)
+
+;;; ivy.el ends here
diff --git a/swiper.el b/swiper.el
index 3243069..6bc53d0 100644
--- a/swiper.el
+++ b/swiper.el
@@ -1,11 +1,11 @@
-;;; swiper.el --- Isearch with a helm overview. Oh, man! -*- lexical-binding:
t -*-
+;;; swiper.el --- Isearch with an overview. Oh, man! -*- lexical-binding: t -*-
;; Copyright (C) 2015 Oleh Krehel
;; Author: Oleh Krehel <address@hidden>
;; URL: https://github.com/abo-abo/swiper
;; Version: 0.1.0
-;; Package-Requires: ((helm "1.6.7") (emacs "24.1"))
+;; Package-Requires: ((emacs "24.1"))
;; Keywords: matching
;; This file is not part of GNU Emacs
@@ -26,23 +26,30 @@
;;; Commentary:
;;
;; This package gives an overview of the current regex search
-;; candidates in a `helm' buffer. The search regex can be split into
-;; groups with a space. Each group is highlighted with a different
-;; face.
+;; candidates. The search regex can be split into groups with a
+;; space. Each group is highlighted with a different face.
+;;
+;; The overview back end can be either `helm' or `ivy'.
;;
;; It can double as a quick `regex-builder', although only single
;; lines will be matched.
;;; Code:
-(require 'helm)
+(require 'ivy)
(defgroup swiper nil
- "Interactive `occur' using `helm'."
+ "`isearch' with an overview."
:group 'matching
:prefix "swiper-")
+(defcustom swiper-completion-method 'helm
+ "Method to select a candidate from a list of strings."
+ :type '(choice
+ (const :tag "Helm" helm)
+ (const :tag "Ivy" ivy)))
+
(defface swiper-match-face-1
- '((t (:background "#FEEA89")))
+ '((t (:background "#FEEA89")))
"Face for `swiper' matches.")
(defface swiper-match-face-2
@@ -70,6 +77,10 @@
(defvar swiper--buffer nil
"Store current buffer.")
+(defvar swiper--window nil
+ "Store current window.
+This is necessary for `window-start' while in minibuffer.")
+
(defalias 'swiper-font-lock-ensure
(if (fboundp 'font-lock-ensure)
'font-lock-ensure
@@ -96,20 +107,53 @@
(zerop (forward-line 1)))
(nreverse candidates))))
-(defvar swiper--keymap
- (let ((map (copy-keymap helm-map)))
+(defvar swiper-helm-keymap
+ (let ((map (make-sparse-keymap)))
(define-key map (kbd "C-s") 'helm-next-line)
(define-key map (kbd "C-r") 'helm-previous-line)
map)
- "Allows you to go to next and previous hit isearch-style")
+ "Allows you to go to next and previous hit isearch-style.")
;;;###autoload
(defun swiper ()
- "Interactive `occur' using `helm'."
+ "`isearch' with an overview."
(interactive)
+ (if (and (eq 'swiper-completion-method 'helm)
+ (featurep 'helm))
+ (swiper--helm)
+ (swiper--ivy)))
+
+(defun swiper--init ()
+ "Perform initialization common to both completion methods."
(deactivate-mark)
(setq swiper--len 0)
(setq swiper--anchor (line-number-at-pos))
+ (setq swiper--buffer (current-buffer))
+ (setq swiper--window (selected-window)))
+
+(defun swiper--ivy ()
+ "`isearch' with an overview using `ivy'."
+ (interactive)
+ (ido-mode -1)
+ (swiper--init)
+ (unwind-protect
+ (let ((res (ivy-read "pattern: "
+ (swiper--candidates)
+ #'swiper--update-input-ivy)))
+ (goto-char (point-min))
+ (forward-line (1- (read res)))
+ (re-search-forward
+ (ivy--regex ivy-text)
+ (line-end-position)
+ t))
+ (ido-mode 1)
+ (swiper--cleanup)))
+
+(defun swiper--helm ()
+ "`isearch' with an overview using `helm'."
+ (interactive)
+ (require 'helm)
+ (swiper--init)
(unwind-protect
(let ((helm-display-function
(lambda (buf)
@@ -121,32 +165,35 @@
(helm :sources
`((name . ,(buffer-name))
(init . (lambda ()
- (setq swiper--buffer (current-buffer))
(add-hook 'helm-move-selection-after-hook
#'swiper--update-sel)
(add-hook 'helm-update-hook
- #'swiper--update-input)
+ #'swiper--update-input-helm)
(add-hook 'helm-after-update-hook
#'swiper--reanchor)))
- (match-strict . (lambda (x) (ignore-errors
- (string-match (swiper--regex
helm-input) x))))
+ (match-strict . (lambda (x)
+ (ignore-errors
+ (string-match (ivy--regex helm-input)
x))))
(candidates . ,(swiper--candidates))
(filtered-candidate-transformer
helm-fuzzy-highlight-matches)
(action . swiper--action))
- :keymap swiper--keymap
+ :keymap (make-composed-keymap
+ swiper-helm-keymap
+ helm-map)
:preselect
(format "^%d " swiper--anchor)
:buffer "*swiper*"))
;; cleanup
- (remove-hook 'helm-move-selection-after-hook
- #'swiper--update-sel)
- (remove-hook 'helm-update-hook
- #'swiper--update-input)
- (remove-hook 'helm-after-update-hook
- #'swiper--reanchor)
- (while swiper--overlays
- (delete-overlay (pop swiper--overlays)))))
+ (remove-hook 'helm-move-selection-after-hook #'swiper--update-sel)
+ (remove-hook 'helm-update-hook #'swiper--update-input-helm)
+ (remove-hook 'helm-after-update-hook #'swiper--reanchor)
+ (swiper--cleanup)))
+
+(defun swiper--cleanup ()
+ "Clean up the overlays."
+ (while swiper--overlays
+ (delete-overlay (pop swiper--overlays))))
(defvar swiper--overlays nil
"Store overlays.")
@@ -155,40 +202,76 @@
"A line number to which the search should be anchored.")
(defvar swiper--len 0
- "The last length of `helm-input' for which an anchoring was made.")
+ "The last length of input for which an anchoring was made.")
-(defun swiper--update-input ()
+(defun swiper--update-input-helm ()
"Update selection."
+ (swiper--cleanup)
(with-current-buffer swiper--buffer
- (let ((re (swiper--regex helm-input))
- (we (window-end nil t)))
- (while swiper--overlays
- (delete-overlay (pop swiper--overlays)))
- (when (> (length helm-input) 1)
- (save-excursion
- (goto-char (window-start))
- (while (ignore-errors (re-search-forward re we t))
- (let ((i 0))
- (while (<= i swiper--subexps)
- (when (match-beginning i)
- (let ((overlay (make-overlay (match-beginning i)
- (match-end i)))
- (face
- (cond ((zerop swiper--subexps)
- (cl-caddr swiper-faces))
- ((zerop i)
- (car swiper-faces))
- (t
- (nth (1+ (mod (1- i) (1- (length
swiper-faces))))
- swiper-faces)))))
- (push overlay swiper--overlays)
- (overlay-put overlay 'face face)
- (overlay-put overlay 'priority i)
- (cl-incf i))))))))))
+ (swiper--add-overlays
+ (ivy--regex helm-input)
+ (window-start swiper--window)
+ (window-end swiper--window t)))
(when (/= (length helm-input) swiper--len)
(setq swiper--len (length helm-input))
(swiper--reanchor)))
+(defun swiper--update-input-ivy ()
+ "Called when `ivy' input is updated."
+ (swiper--cleanup)
+ (let* ((re (ivy--regex ivy-text))
+ (str ivy--current)
+ (num (if (string-match "^[0-9]+" str)
+ (string-to-number (match-string 0 str))
+ 0)))
+ (with-current-buffer swiper--buffer
+ (goto-char (point-min))
+ (when (plusp num)
+ (goto-char (point-min))
+ (forward-line (1- num))
+ (setf (window-point swiper--window)
+ (point)))
+ (let ((ov (make-overlay
+ (line-beginning-position)
+ (1+ (line-end-position)))))
+ (overlay-put ov 'face 'swiper-line-face)
+ (overlay-put ov 'window swiper--window)
+ (push ov swiper--overlays))
+ (swiper--add-overlays
+ re
+ (save-excursion
+ (forward-line (- (window-height swiper--window)))
+ (point))
+ (save-excursion
+ (forward-line (window-height swiper--window))
+ (point))))))
+
+(defun swiper--add-overlays (re beg end)
+ "Add overlays for RE regexp in current buffer between BEG and END."
+ (when (> (length re) 1)
+ (save-excursion
+ (goto-char beg)
+ ;; RE can become an invalid regexp
+ (while (ignore-errors (re-search-forward re end t))
+ (let ((i 0))
+ (while (<= i ivy--subexps)
+ (when (match-beginning i)
+ (let ((overlay (make-overlay (match-beginning i)
+ (match-end i)))
+ (face
+ (cond ((zerop ivy--subexps)
+ (cl-caddr swiper-faces))
+ ((zerop i)
+ (car swiper-faces))
+ (t
+ (nth (1+ (mod (1- i) (1- (length swiper-faces))))
+ swiper-faces)))))
+ (push overlay swiper--overlays)
+ (overlay-put overlay 'face face)
+ (overlay-put overlay 'window swiper--window)
+ (overlay-put overlay 'priority i)))
+ (cl-incf i)))))))
+
(defun swiper--binary (beg end)
"Find anchor between BEG and END."
(if (<= (- end beg) 10)
@@ -220,7 +303,7 @@
(defun swiper--update-sel ()
"Update selection."
- (let* ((re (swiper--regex helm-input))
+ (let* ((re (ivy--regex helm-input))
(str (buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))
@@ -239,7 +322,7 @@
(helm-persistent-action-display-window)
(goto-char pt)
(recenter)
- (swiper--update-input))))
+ (swiper--update-input-helm))))
(with-current-buffer swiper--buffer
(let ((ov (make-overlay
(line-beginning-position)
@@ -259,39 +342,12 @@
(forward-line -1)
(helm-next-line 1))))
-(defvar swiper--subexps 1
- "Number of groups in `swiper--regex'.")
-
-(defvar swiper--regex-hash
- (make-hash-table :test 'equal)
- "Store pre-computed regex.")
-
-(defun swiper--regex (str)
- "Re-build regex from STR in case it has a space."
- (let ((hashed (gethash str swiper--regex-hash)))
- (if hashed
- (prog1 (cdr hashed)
- (setq swiper--subexps (car hashed)))
- (cdr (puthash str
- (let ((subs (split-string str " +" t)))
- (if (= (length subs) 1)
- (cons
- (setq swiper--subexps 0)
- (car subs))
- (cons
- (setq swiper--subexps (length subs))
- (mapconcat
- (lambda (x) (format "\\(%s\\)" x))
- subs
- ".*"))))
- swiper--regex-hash)))))
-
(defun swiper--action (x)
"Goto line X."
(goto-char (point-min))
(forward-line (1- (read x)))
(re-search-forward
- (swiper--regex helm-input) (line-end-position) t))
+ (ivy--regex helm-input) (line-end-position) t))
(provide 'swiper)
- [elpa] master 7b86747 09/67: swiper.el (swiper--regex): Update signature, (continued)
- [elpa] master 7b86747 09/67: swiper.el (swiper--regex): Update signature, Oleh Krehel, 2015/03/22
- [elpa] master a267b34 10/67: familiar isearch key bindings while helm is active, Oleh Krehel, 2015/03/22
- [elpa] master a817342 14/67: ivy.el: Improve the highlighting in the minibuffer, Oleh Krehel, 2015/03/22
- [elpa] master 97ab66a 13/67: README.md: Update, Oleh Krehel, 2015/03/22
- [elpa] master eb829a9 16/67: Account for zero-length regex matches, Oleh Krehel, 2015/03/22
- [elpa] master 9bcf1dc 12/67: Update dependencies., Oleh Krehel, 2015/03/22
- [elpa] master 7cea819 18/67: ivy.el: Add `ivy-exit', Oleh Krehel, 2015/03/22
- [elpa] master eb1def0 17/67: Add initial-input optional argument, Oleh Krehel, 2015/03/22
- [elpa] master 2f5cc11 15/67: swiper.el: Use `with-selected-window' instead of `with-current-buffer', Oleh Krehel, 2015/03/22
- [elpa] master 02065be 19/67: swiper.el: Restore original point on canceling, Oleh Krehel, 2015/03/22
- [elpa] master 6a874a4 11/67: Add `ivy' back end,
Oleh Krehel <=
- [elpa] master f268cc8 21/67: Reveal invisible overlays, Oleh Krehel, 2015/03/22
- [elpa] master 835208a 20/67: Inherit standard faces by default, Oleh Krehel, 2015/03/22
- [elpa] master f148a94 24/67: Fix use of cl-incf, Oleh Krehel, 2015/03/22
- [elpa] master 2ed9ee2 23/67: Require delsel for `minibuffer-keyboard-quit', Oleh Krehel, 2015/03/22
- [elpa] master 1ca1660 22/67: swiper.el: Save position before last search, Oleh Krehel, 2015/03/22
- [elpa] master 02ca7a1 25/67: Add anchoring, Oleh Krehel, 2015/03/22
- [elpa] master b1ac649 26/67: Don't recenter unless necessary, Oleh Krehel, 2015/03/22
- [elpa] master a07c2e4 27/67: swiper.el: Make ivy the default back end, Oleh Krehel, 2015/03/22
- [elpa] master baa9df7 30/67: Truncate candidates to window width in the minibuffer, Oleh Krehel, 2015/03/22
- [elpa] master 7c6d00d 32/67: ivy.el (ivy-read): Return immediately for less than 2 candidates, Oleh Krehel, 2015/03/22