branch: master commit 302a16a15bbaf3842246293a27c77ba2fd9a56e1 Merge: 5aa7896 55414c3 Author: Oleh Krehel <ohwoeo...@gmail.com> Commit: Oleh Krehel <ohwoeo...@gmail.com>
Add 'packages/swiper/' from commit '55414c321ca07bd86f0f1efaf8f6130617e6fad6' git-subtree-dir: packages/swiper git-subtree-mainline: 5aa78963734dc6975cad8df1c8853b65a4f1b826 git-subtree-split: 55414c321ca07bd86f0f1efaf8f6130617e6fad6 --- packages/swiper/.travis.yml | 12 ++ packages/swiper/Makefile | 16 ++ packages/swiper/README.md | 16 ++ packages/swiper/counsel.el | 77 ++++++++ packages/swiper/ivy-test.el | 67 +++++++ packages/swiper/ivy.el | 413 +++++++++++++++++++++++++++++++++++++++++++ packages/swiper/swiper.el | 255 ++++++++++++++++++++++++++ 7 files changed, 856 insertions(+), 0 deletions(-) diff --git a/packages/swiper/.travis.yml b/packages/swiper/.travis.yml new file mode 100644 index 0000000..1f5dbc7 --- /dev/null +++ b/packages/swiper/.travis.yml @@ -0,0 +1,12 @@ +language: emacs-lisp +env: + matrix: + - EMACS=emacs24 + +before_install: + - sudo add-apt-repository -y ppa:cassou/emacs + - sudo apt-get update -qq + - sudo apt-get install -qq $EMACS + +script: + - make test diff --git a/packages/swiper/Makefile b/packages/swiper/Makefile new file mode 100644 index 0000000..453f709 --- /dev/null +++ b/packages/swiper/Makefile @@ -0,0 +1,16 @@ +emacs ?= emacs + +LOAD = -l ivy.el -l swiper.el + +.PHONY: all compile clean + +all: test + +test: + $(emacs) -batch $(LOAD) -l ivy-test.el -f ert-run-tests-batch-and-exit + +compile: + $(emacs) -batch $(LOAD) --eval "(mapc #'byte-compile-file '(\"ivy.el\" \"swiper.el\"))" + +clean: + rm -f *.elc diff --git a/packages/swiper/README.md b/packages/swiper/README.md new file mode 100644 index 0000000..6482463 --- /dev/null +++ b/packages/swiper/README.md @@ -0,0 +1,16 @@ +[](https://travis-ci.org/abo-abo/swiper) + +## Swiper + +Package for GNU Emacs that gives you an overview as you search for a regex + + + +The package uses the `ivy` back end for the overview, see also +[swiper-helm](https://github.com/abo-abo/swiper-helm). + +## Screenshots + + + +There's also a one minute [video demo](https://www.youtube.com/watch?v=s3qwiAtKjuA). diff --git a/packages/swiper/counsel.el b/packages/swiper/counsel.el new file mode 100644 index 0000000..07d9a66 --- /dev/null +++ b/packages/swiper/counsel.el @@ -0,0 +1,77 @@ +;;; consel.el --- Elisp completion at point -*- lexical-binding: t -*- + +;; Copyright (C) 2015 Free Software Foundation, Inc. + +;; Author: Oleh Krehel <ohwoeo...@gmail.com> + +;; This file is 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: +;; +;; Just call `counsel' to start completing the `obarray'. +;; The initial (optional) input is thing-at-point. + +;;; Code: + +(require 'ivy) + +(defun counsel () + "Elisp completion at point." + (interactive) + (counsel--generic + (lambda (str) (all-completions str obarray)))) + +(defun couns-clj () + "Clojure completion at point." + (interactive) + (counsel--generic + (lambda (str) + (mapcar + #'cl-caddr + (cider-sync-request:complete str ":same"))))) + +(defun couns-git () + "Find file in the current Git repository." + (interactive) + (let* ((default-directory (locate-dominating-file + default-directory ".git")) + (cands (split-string + (shell-command-to-string + "git ls-files --full-name --") + "\n")) + (file (ivy-read "Find file: " cands))) + (when file + (find-file file)))) + +(defun counsel--generic (completion-fn) + "Complete thing at point with COMPLETION-FN." + (let* ((bnd (bounds-of-thing-at-point 'symbol)) + (str (if bnd + (buffer-substring-no-properties + (car bnd) (cdr bnd)) + "")) + (candidates (funcall completion-fn str)) + (ivy-height 7) + (res (ivy-read (format "pattern (%s): " str) + candidates))) + (when (stringp res) + (when bnd + (delete-region (car bnd) (cdr bnd))) + (insert res)))) + +(provide 'counsel) + +;;; counsel.el ends here diff --git a/packages/swiper/ivy-test.el b/packages/swiper/ivy-test.el new file mode 100644 index 0000000..a33f886 --- /dev/null +++ b/packages/swiper/ivy-test.el @@ -0,0 +1,67 @@ +;;; ivy-test.el --- tests for ivy + +;; Copyright (C) 2015 Free Software Foundation, Inc. + +;; Author: Oleh Krehel + +;; This file is 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/>. + +(require 'ert) + +(defvar ivy-expr nil + "Holds a test expression to evaluate with `ivy-eval'.") + +(defvar ivy-result nil + "Holds the eval result of `ivy-expr' by `ivy-eval'.") + +(defun ivy-eval () + "Evaluate `ivy-expr'." + (interactive) + (setq ivy-result (eval ivy-expr))) + +(global-set-key (kbd "C-c e") 'ivy-eval) + +(defun ivy-with (expr keys) + "Evaluate EXPR followed by KEYS." + (let ((ivy-expr expr)) + (execute-kbd-macro + (vconcat (kbd "C-c e") + (kbd keys))) + ivy-result)) + +(ert-deftest ivy-read () + (should (equal + (ivy-read "pattern: " nil) + nil)) + (should (equal + (ivy-read "pattern: " '("42")) + "42")) + (should (equal + (ivy-with '(ivy-read "pattern: " '("blue" "yellow")) + "C-m") + "blue")) + (should (equal + (ivy-with '(ivy-read "pattern: " '("blue" "yellow")) + "y C-m") + "yellow")) + (should (equal + (ivy-with '(ivy-read "pattern: " '("blue" "yellow")) + "y DEL b C-m") + "blue")) + (should (equal + (ivy-with '(ivy-read "pattern: " '("blue" "yellow")) + "z C-m") + nil))) diff --git a/packages/swiper/ivy.el b/packages/swiper/ivy.el new file mode 100644 index 0000000..4d307f3 --- /dev/null +++ b/packages/swiper/ivy.el @@ -0,0 +1,413 @@ +;;; ivy.el --- Incremental Vertical completYon -*- lexical-binding: t -*- + +;; Copyright (C) 2015 Free Software Foundation, Inc. + +;; Author: Oleh Krehel <ohwoeo...@gmail.com> +;; URL: https://github.com/abo-abo/swiper +;; Version: 0.1.0 +;; Package-Requires: ((emacs "24.1")) +;; Keywords: matching + +;; This file is 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 (:inherit highlight))) + "Face used by Ivy for highlighting first match.") + +(defcustom ivy-height 10 + "Number of lines for the minibuffer window." + :type 'integer) + +(defcustom ivy-count-format "%-4d " + "The style of showing the current candidate count for `ivy-read'. +Set this to nil if you don't want the count." + :type 'string) + +(defcustom ivy-wrap nil + "Whether to wrap around after the first and last candidate." + :type 'boolean) + +;;* User Visible +;;** Keymap +(require 'delsel) +(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-or-history) + (define-key map (kbd "C-r") 'ivy-previous-line-or-history) + (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) + (define-key map (kbd "M-n") 'ivy-next-history-element) + (define-key map (kbd "M-p") 'ivy-previous-history-element) + (define-key map (kbd "C-g") 'minibuffer-keyboard-quit) + map) + "Keymap used in the minibuffer.") + +(defvar ivy-history nil + "History list of candidates entered in the minibuffer. + +Maximum length of the history list is determined by the value +of `history-length', which see.") + +;;** Commands +(defun ivy-done () + "Exit the minibuffer with the selected candidate." + (interactive) + (delete-minibuffer-contents) + (unless (zerop ivy--length) + (insert ivy--current) + (setq ivy-exit 'done)) + (exit-minibuffer)) + +(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-next-line () + "Select the next completion candidate." + (interactive) + (if (>= ivy--index (1- ivy--length)) + (when ivy-wrap + (ivy-beginning-of-buffer)) + (cl-incf ivy--index))) + +(defun ivy-next-line-or-history () + "Select the next completion candidate. +If the input is empty, select the previous history element instead." + (interactive) + (when (string= ivy-text "") + (ivy-previous-history-element 1)) + (if (>= ivy--index (1- ivy--length)) + (when ivy-wrap + (ivy-beginning-of-buffer)) + (cl-incf ivy--index))) + +(defun ivy-previous-line () + "Select the previous completion candidate." + (interactive) + (if (zerop ivy--index) + (when ivy-wrap + (ivy-end-of-buffer)) + (cl-decf ivy--index))) + +(defun ivy-previous-line-or-history () + "Select the previous completion candidate. +If the input is empty, select the previous history element instead." + (interactive) + (when (string= ivy-text "") + (ivy-previous-history-element 1)) + (if (zerop ivy--index) + (when ivy-wrap + (ivy-end-of-buffer)) + (cl-decf ivy--index))) + +(defun ivy-previous-history-element (arg) + "Forward to `previous-history-element' with ARG." + (interactive "p") + (previous-history-element arg) + (move-end-of-line 1)) + +(defun ivy-next-history-element (arg) + "Forward to `next-history-element' with ARG." + (interactive "p") + (next-history-element arg) + (move-end-of-line 1)) + +(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 initial-input update-fn preselect) + "Read a string in the minibuffer, with completion. + +PROMPT is a string to prompt with; normally it ends in a colon +and a space. When PROMPT contains %d, it will be updated with +the current number of matching candidates. + +COLLECTION is a list of strings. + +If INITIAL-INPUT is non-nil, insert it in the minibuffer initially. + +UPDATE-FN is called each time the current candidate(s) is changed. + +If PRESELECT is non-nil select the corresponding candidate out of +the ones that match INITIAL-INPUT." + (cl-case (length collection) + (0 nil) + (1 (car collection)) + (t + (setq ivy--index (or + (and preselect + (ivy--preselect-index + collection initial-input preselect)) + 0)) + (setq ivy--old-re nil) + (setq ivy--old-cands nil) + (setq ivy-text "") + (setq ivy--all-candidates collection) + (setq ivy--update-fn update-fn) + (setq ivy-exit nil) + (setq ivy--default (or (thing-at-point 'symbol) "")) + (setq ivy--prompt + (cond ((string-match "%.*d" prompt) + prompt) + ((string-match "%.*d" ivy-count-format) + (concat ivy-count-format prompt)) + (t + nil))) + (unwind-protect + (minibuffer-with-setup-hook + #'ivy--minibuffer-setup + (let ((res (read-from-minibuffer + prompt + initial-input + ivy-minibuffer-map + nil + 'ivy-history))) + (when (eq ivy-exit 'done) + (pop ivy-history) + (setq ivy-history + (cons ivy-text (delete ivy-text ivy-history))) + res))) + (remove-hook 'post-command-hook #'ivy--exhibit))))) + +(defun ivy--preselect-index (candidates initial-input preselect) + "Return the index in CANDIDATES filtered by INITIAL-INPUT for PRESELECT." + (when initial-input + (setq candidates + (cl-remove-if-not + (lambda (x) + (string-match initial-input x)) + candidates))) + (cl-position-if + (lambda (x) + (string-match preselect x)) + candidates)) + +(defvar ivy-text "" + "Stores the user's string as it is typed in.") + +(defvar ivy-exit nil + "Store 'done if the completion was successfully selected. +Otherwise, store nil.") + +;;* 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) + (set (make-local-variable 'minibuffer-default-add-function) + (lambda () + (list ivy--default))) + (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--default nil + "Default initial input.") + +(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)))) + +(defvar ivy--prompt nil + "Store the format-style prompt. +When non-nil, it should contain one %d.") + +(defun ivy--insert-prompt () + "Update the prompt according to `ivy--prompt'." + (when ivy--prompt + (let ((inhibit-read-only t) + (n-str (format ivy--prompt ivy--length))) + (save-excursion + (goto-char (point-min)) + (delete-region (point-min) (minibuffer-prompt-end)) + (set-text-properties + 0 (length n-str) + '(front-sticky t rear-nonsticky t field t read-only t face minibuffer-prompt) + n-str) + (insert n-str)) + ;; get out of the prompt area + (constrain-to-field nil (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 (ivy-completions + ivy-text + ivy--all-candidates)) + (buffer-undo-list t) + deactivate-mark) + (when ivy--update-fn + (funcall ivy--update-fn)) + (ivy--insert-prompt) + ;; 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--add-face (str face) + "Propertize STR with FACE. +`font-lock-append-text-property' is used, since it's better than +`propertize' or `add-face-text-property' in this case." + (font-lock-append-text-property 0 (length str) 'face face str) + str) + +(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 (and (equal re ivy--old-re) + ivy--old-cands) + ivy--old-cands + (setq ivy--old-re re) + (ignore-errors + (cl-remove-if-not + (lambda (x) (string-match re x)) + candidates)))) + (tail (nthcdr ivy--index ivy--old-cands)) + (ww (window-width)) + idx) + (setq ivy--length (length cands)) + (when (and tail ivy--old-cands) + (while (and tail + (null (setq idx (cl-position (pop tail) cands + :test #'equal))))) + (setq ivy--index (or idx 0))) + (setq ivy--old-cands cands) + (when (>= ivy--index ivy--length) + (setq ivy--index (max (1- ivy--length) 0))) + (if (null cands) + "" + (let* ((half-height (/ ivy-height 2)) + (start (max 0 (- ivy--index half-height))) + (end (min (+ start (1- ivy-height)) ivy--length)) + (cands (cl-subseq cands start end)) + (index (min ivy--index half-height (1- (length cands))))) + (setq ivy--current (copy-sequence (nth index cands))) + (setf (nth index cands) + (ivy--add-face ivy--current 'ivy-current-match)) + (concat "\n" (mapconcat + (lambda (s) + (if (> (length s) ww) + (concat (substring s 0 (- ww 3)) "...") + s)) + cands "\n")))))) + +(provide 'ivy) + +;;; ivy.el ends here diff --git a/packages/swiper/swiper.el b/packages/swiper/swiper.el new file mode 100644 index 0000000..1be8676 --- /dev/null +++ b/packages/swiper/swiper.el @@ -0,0 +1,255 @@ +;;; swiper.el --- Isearch with an overview. Oh, man! -*- lexical-binding: t -*- + +;; Copyright (C) 2015 Free Software Foundation, Inc. + +;; Author: Oleh Krehel <ohwoeo...@gmail.com> +;; URL: https://github.com/abo-abo/swiper +;; Version: 0.1.0 +;; Package-Requires: ((emacs "24.1") (ivy "0.1.0")) +;; Keywords: matching + +;; This file is 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 gives an overview of the current regex search +;; candidates. The search regex can be split into groups with a +;; space. Each group is highlighted with a different face. +;; +;; The overview back end is `ivy'. +;; +;; It can double as a quick `regex-builder', although only single +;; lines will be matched. + +;;; Code: +(require 'ivy) + +(defgroup swiper nil + "`isearch' with an overview." + :group 'matching + :prefix "swiper-") + +(defface swiper-match-face-1 + '((t (:inherit isearch-lazy-highlight-face))) + "Face for `swiper' matches.") + +(defface swiper-match-face-2 + '((t (:inherit isearch))) + "Face for `swiper' matches.") + +(defface swiper-match-face-3 + '((t (:inherit match))) + "Face for `swiper' matches.") + +(defface swiper-match-face-4 + '((t (:inherit isearch))) + "Face for `swiper' matches.") + +(defface swiper-line-face + '((t (:inherit highlight))) + "Face for current `swiper' line.") + +(defcustom swiper-faces '(swiper-match-face-1 + swiper-match-face-2 + swiper-match-face-3 + swiper-match-face-4) + "List of `swiper' faces for group matches.") + +(defcustom swiper-min-highlight 2 + "Only highlight matches for regexps at least this long." + :type 'integer) + +(defvar swiper--window nil + "Store the current window.") + +(defun swiper-font-lock-ensure () + "Ensure the entired buffer is highlighted." + (unless (or (derived-mode-p 'magit-mode) + (memq major-mode '(package-menu-mode emms-playlist-mode))) + (if (fboundp 'font-lock-ensure) + (font-lock-ensure) + (font-lock-fontify-buffer)))) + +(defvar swiper--format-spec "" + "Store the current candidates format spec.") + +(defun swiper--candidates () + "Return a list of this buffer lines." + (let ((n-lines (count-lines (point-min) (point-max)))) + (unless (zerop n-lines) + (setq swiper--format-spec + (format "%%-%dd %%s" (1+ (floor (log n-lines 10))))) + (let ((line-number 0) + candidates) + (save-excursion + (goto-char (point-min)) + (swiper-font-lock-ensure) + (while (< (point) (point-max)) + (push (format swiper--format-spec + (cl-incf line-number) + (buffer-substring + (line-beginning-position) + (line-end-position))) + candidates) + (zerop (forward-line 1))) + (nreverse candidates)))))) + +(defvar swiper--opoint 1 + "The point when `swiper' starts.") + +;;;###autoload +(defun swiper (&optional initial-input) + "`isearch' with an overview. +When non-nil, INITIAL-INPUT is the initial search pattern." + (interactive) + (swiper--ivy initial-input)) + +(defun swiper--init () + "Perform initialization common to both completion methods." + (deactivate-mark) + (setq swiper--opoint (point)) + (setq swiper--len 0) + (setq swiper--anchor (line-number-at-pos)) + (setq swiper--window (selected-window))) + +(defun swiper--ivy (&optional initial-input) + "`isearch' with an overview using `ivy'. +When non-nil, INITIAL-INPUT is the initial search pattern." + (interactive) + (ido-mode -1) + (swiper--init) + (let ((candidates (swiper--candidates)) + (preselect (format + swiper--format-spec + (line-number-at-pos) + (regexp-quote + (buffer-substring-no-properties + (line-beginning-position) + (line-end-position))))) + res) + (unwind-protect + (setq res (ivy-read + (replace-regexp-in-string + "%s" "pattern: " swiper--format-spec) + candidates + initial-input + #'swiper--update-input-ivy + preselect)) + (ido-mode 1) + (swiper--cleanup) + (if (null ivy-exit) + (goto-char swiper--opoint) + (swiper--action res ivy-text))))) + +(defun swiper--ensure-visible () + "Remove overlays hiding point." + (let ((overlays (overlays-at (point))) + ov expose) + (while (setq ov (pop overlays)) + (if (and (invisible-p (overlay-get ov 'invisible)) + (setq expose (overlay-get ov 'isearch-open-invisible))) + (funcall expose ov))))) + +(defun swiper--cleanup () + "Clean up the overlays." + (while swiper--overlays + (delete-overlay (pop swiper--overlays))) + (save-excursion + (goto-char (point-min)) + (isearch-clean-overlays))) + +(defvar swiper--overlays nil + "Store overlays.") + +(defvar swiper--anchor nil + "A line number to which the search should be anchored.") + +(defvar swiper--len 0 + "The last length of input for which an anchoring was made.") + +(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-selected-window swiper--window + (goto-char (point-min)) + (when (cl-plusp num) + (goto-char (point-min)) + (forward-line (1- num)) + (isearch-range-invisible (line-beginning-position) + (line-end-position)) + (unless (and (> (point) (window-start)) + (< (point) (window-end swiper--window t))) + (recenter))) + (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 + (window-start swiper--window) + (window-end swiper--window t))))) + +(defun swiper--add-overlays (re beg end) + "Add overlays for RE regexp in current buffer between BEG and END." + (when (>= (length re) swiper-min-highlight) + (save-excursion + (goto-char beg) + ;; RE can become an invalid regexp + (while (and (ignore-errors (re-search-forward re end t)) + (> (- (match-end 0) (match-beginning 0)) 0)) + (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--action (x input) + "Goto line X and search for INPUT." + (if (null x) + (user-error "No candidates") + (goto-char (point-min)) + (forward-line (1- (read x))) + (re-search-forward + (ivy--regex input) (line-end-position) t) + (swiper--ensure-visible) + (when (/= (point) swiper--opoint) + (unless (and transient-mark-mode mark-active) + (push-mark swiper--opoint t) + (message "Mark saved where search started"))))) + +(provide 'swiper) + +;;; swiper.el ends here