emacs-elpa-diffs
[Top][All Lists]
Advanced

[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)
 



reply via email to

[Prev in Thread] Current Thread [Next in Thread]