branch: elpa/isl
commit 125bdf16b75b795ebdc8ea4b14af832eb84eff3c
Author: Thierry Volpiatto <[email protected]>
Commit: Thierry Volpiatto <[email protected]>
Add isl.el for further rename of isearch-light.el
---
isl.el | 953 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 953 insertions(+)
diff --git a/isl.el b/isl.el
new file mode 100644
index 00000000000..8fa43b22bd9
--- /dev/null
+++ b/isl.el
@@ -0,0 +1,953 @@
+;;; isearch-light.el --- Simple incremental search in current-buffer -*-
lexical-binding: t -*-
+
+;; Author: Thierry Volpiatto <[email protected]>
+;; Copyright (C) 2021 Thierry Volpiatto <[email protected]>
+
+;; Version: 1.0
+;; URL: https://github.com/thierryvolpiatto/isearch-light
+
+;; Compatibility: GNU Emacs 24.3+
+;; Package-Requires: ((emacs "24.3"))
+
+;; 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:
+
+;; Isearch-light is a small package to search regexp incrementaly in
+;; current-buffer. It is simple to use, just call M-x `isl-search'.
+
+;;; Code:
+
+(require 'cl-lib)
+
+(defvar iedit-aborting)
+(defvar iedit-read-only-occurrences-overlays)
+(defvar iedit-read-only-occurrences-overlays)
+(defvar iedit-case-sensitive)
+(defvar iedit-occurrences-overlays)
+(defvar iedit-mode)
+(defvar helm-occur-always-search-in-current)
+(defvar hs-minor-mode)
+(defvar hs-show-hook)
+(declare-function iedit-make-read-only-occurrence-overlay "ext:iedit-lib.el")
+(declare-function iedit-make-occurrence-overlay "ext:iedit-lib.el")
+(declare-function iedit-update-index "ext:iedit-lib.el")
+(declare-function iedit-lib-cleanup "ext:iedit-lib.el")
+(declare-function iedit-start "ext:iedit.el")
+(declare-function iedit-done "ext:iedit.el")
+(declare-function outline-show-entry "outline.el")
+(declare-function org-reveal "org.el")
+(declare-function helm-multi-occur-1 "ext:helm-occur.el")
+(declare-function hs-show-block "hideshow.el")
+(declare-function markdown-show-entry "ext:markdown-mode.el")
+
+;; Internals
+(defvar isl-pattern "")
+(defvar-local isl-last-query nil)
+(defvar-local isl-last-object nil)
+(defvar isl-visited-buffers nil)
+(defvar isl-current-buffer nil)
+(defvar isl--item-overlays nil)
+(defvar isl--iterator nil)
+(defvar isl--last-overlay nil)
+(defvar isl--direction nil)
+(defvar isl-initial-pos nil)
+(defvar isl--number-results 0)
+(defvar isl-history nil)
+(defvar isl--yank-point nil)
+(defvar isl--quit nil)
+(defvar isl--invalid nil)
+(defvar-local isl--buffer-invisibility-spec nil)
+(defconst isl-space-regexp "\\s\\\\s-"
+ "Match a quoted space in a string.")
+(defconst isl--case-fold-choices '(smart nil t))
+(defvar isl--case-fold-choices-iterator nil)
+(defvar isl-help-buffer-name "*isl help*")
+(defvar isl--hidding nil)
+(defvar isl--point-min nil)
+(defvar isl--point-max nil)
+(defvar isl-help-string
+ "* Isearch-light help\n
+** Commands
+\\<isl-map>
+\\[isl-display-or-quit-help]\t\tDisplay or quit this help buffer
+\\[isl-help-quit]\t\tQuit this help buffer
+\\[abort-recursive-edit]\t\tQuit isl and restore initial position
+\\[isl-goto-next]\t\tGoto next occurence
+\\[isl-goto-prev]\t\tGoto previous occurence
+\\[isl-scroll-down]\t\tScroll down
+\\[isl-scroll-up]\t\tScroll up
+\\[isl-exit-at-point]\t\tExit at current position
+\\[abort-recursive-edit]\t\tQuit and restore position
+\\[isl-yank-word-at-point]\t\tYank word at point
+\\[isl-recenter]\t\tRecenter current buffer
+\\[isl-change-matching-style]\t\tChange matching style
+\\[isl-select-case-fold-search]\t\tChange case fold search (cycle: *=smart,
1=t, 0=nil)
+\\[isl-goto-first]\t\tGoto first occurence
+\\[isl-goto-last]\t\tGoto last occurence
+\\[isl-goto-closest-from-start]\t\tGoto closest occurence from start
+\\[isl-jump-to-helm-occur]\t\tJump to helm-occur
+\\[isl-jump-to-iedit-mode]\t\tJump to iedit-mode
+\\[isl-query-replace]\t\tJump to query replace
+\\[isl-show-or-hide-context-lines]\t\tHide or show non matching lines")
+
+;; User vars
+(defvar isl-timer-delay 0.01)
+
+(defgroup isearch-light nil
+ "Open `isl-search'."
+ :prefix "isl-"
+ :group 'matching)
+
+(defcustom isl-search-function #'re-search-forward
+ "The search function that will be used by default when starting `isl-search'.
+Possible values are `re-search-forward' and `search-forward', the
+first use regexp matching while the second is using literal matching.
+Its value can be changed during `isl-search' session with
`\\<isl-map>\\[isl-change-matching-style]'."
+ :type '(choice
+ (function :tag "Regexp matching" re-search-forward)
+ (function :tag "Literal matching" search-forward)))
+
+(defcustom isl-case-fold-search 'smart
+ "The `case-fold-search' value.
+Possible value are nil, t or smart.
+Value smart means use `case-fold-search' when upcase chars are detected
+in pattern."
+ :type 'symbol)
+
+(defcustom isl-after-position-string ">"
+ "The string used to notify in mode-line when position is above initial pos."
+ :type 'string)
+
+(defcustom isl-before-position-string "<"
+ "The string used to notify in mode-line when position is below initial pos."
+ :type 'string)
+
+(defcustom isl-direction-down-string "↓"
+ "The string used in mode-line to notify search direction."
+ :type 'string)
+
+(defcustom isl-direction-up-string "↑"
+ "The string used in mode-line to notify search direction."
+ :type 'string)
+
+(defcustom isl-warning-char "⚠"
+ "Used to signal invalid regexp in mode-line."
+ :type 'string)
+
+(defcustom isl-save-pos-to-mark-ring t
+ "Save initial position to mark-ring on exit when non nil."
+ :type 'boolean)
+
+(defcustom isl-requires-pattern 1
+ "Start updating after this number of chars."
+ :type 'integer)
+
+(defcustom isl-visible-context-lines 1
+ "Number of lines to show around line when hiding non matching lines."
+ :type 'integer)
+
+(defface isl-match
+ '((t :background "Brown4"))
+ "Face used to highlight the items matched.")
+
+(defface isl-on
+ '((t :background "SandyBrown"
+ :foreground "black"))
+ "Face used to highlight the item where point is.")
+
+(defface isl-line
+ '((t :background "Darkgoldenrod1" :extend t))
+ "Face used to flash line on exit.")
+
+(defface isl-number
+ '((t :foreground "green"))
+ "Face used to highlight number in mode-line.")
+
+(defface isl-string
+ '((t :foreground "Lightgoldenrod1" :bold t))
+ "Face used to highlight pattern in mode-line.")
+
+(defface isl-case-fold
+ '((t :inherit isl-string))
+ "Face used to highlight case sensitivity string in mode-line.")
+
+(defvar isl-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map minibuffer-local-map)
+ (define-key map (kbd "C-s") 'isl-goto-next)
+ (define-key map (kbd "C-r") 'isl-goto-prev)
+ (define-key map (kbd "C-n") 'isl-goto-next)
+ (define-key map (kbd "C-p") 'isl-goto-prev)
+ (define-key map (kbd "<down>") 'isl-goto-next)
+ (define-key map (kbd "<up>") 'isl-goto-prev)
+ (define-key map (kbd "RET") 'isl-exit-at-point)
+ (define-key map (kbd "C-w") 'isl-yank-word-at-point)
+ (define-key map (kbd "M-r") 'isl-change-matching-style)
+ (define-key map (kbd "C-c f") 'isl-select-case-fold-search)
+ (define-key map (kbd "M-<") 'isl-goto-first)
+ (define-key map (kbd "M->") 'isl-goto-last)
+ (define-key map (kbd "M-=") 'isl-goto-closest-from-start)
+ (define-key map (kbd "M-s") 'isl-jump-to-helm-occur)
+ (define-key map (kbd "C-;") 'isl-jump-to-iedit-mode)
+ (define-key map (kbd "M-%") 'isl-query-replace)
+ (define-key map (kbd "C-h m") 'isl-display-or-quit-help)
+ (define-key map (kbd "C-q") 'isl-help-quit)
+ (define-key map (kbd "C-'") 'isl-show-or-hide-context-lines)
+ (define-key map (kbd "C-l") 'isl-recenter)
+ (define-key map (kbd "C-v") 'isl-scroll-up)
+ (define-key map (kbd "M-v") 'isl-scroll-down)
+ (define-key map (kbd "C-k") 'isl-delete-minibuffer-contents)
+ map))
+
+;;; Actions
+;;
+(defun isl--goto-overlay (overlay)
+ "Goto OVERLAY."
+ (let ((pos (and overlay (overlay-end overlay))))
+ (when (and overlay pos)
+ (setq isl--last-overlay overlay)
+ (overlay-put overlay 'face 'isl-on)
+ (goto-char pos)
+ (setq isl--yank-point pos))))
+
+(defun isl--highlight-last-overlay (face)
+ "Highlight `isl--last-overlay' with FACE."
+ (when (overlayp isl--last-overlay)
+ (overlay-put isl--last-overlay 'face face)))
+
+(defun isl-goto-next-1 (arg)
+ "Main function that allow moving from one to another overlay.
+It put overlay on current position, move to next overlay using
+`isl--iterator', set `isl--yank-point' and then setup mode-line."
+ (with-selected-window (minibuffer-selected-window)
+ (isl--highlight-last-overlay 'isl-match)
+ (when isl--iterator
+ ;; This is a noop when ARG==1 i.e. (1- 1) == 0.
+ (cl-loop repeat (1- arg) do (isl-iter-next isl--iterator))
+ (isl--goto-overlay (isl-iter-next isl--iterator)))
+ (isl-setup-mode-line)))
+
+(defun isl-scroll-1 (arg)
+ "Scroll up if ARG is positive, down if it is negative."
+ (let (ov)
+ (with-selected-window (minibuffer-selected-window)
+ (when isl--iterator
+ (setq ov (if (> arg 0)
+ (isl--first-ov-after-pos (window-end))
+ (isl--first-ov-before-pos (window-start))))))
+ (when ov
+ (isl--find-and-goto-overlay ov)
+ (with-selected-window (minibuffer-selected-window)
+ (recenter)))))
+
+(defun isl--first-ov-after-pos (pos)
+ (cl-loop for ov in isl--item-overlays
+ when (> (overlay-start ov) pos)
+ return ov))
+
+(defun isl--first-ov-before-pos (pos)
+ (cl-loop for ov in (reverse isl--item-overlays)
+ when (< (overlay-start ov) pos)
+ return ov))
+
+(defun isl-scroll-up ()
+ "Scroll up to closest overlay in next screen."
+ (interactive)
+ (isl-scroll-1 1))
+(put 'isl-scroll-up 'no-helm-mx t)
+
+(defun isl-scroll-down ()
+ "Scroll down to closest overlay in previous screen."
+ (interactive)
+ (isl-scroll-1 -1))
+(put 'isl-scroll-down 'no-helm-mx t)
+
+(defun isl-delete-minibuffer-contents ()
+ "No docstring."
+ (interactive)
+ (with-selected-window (minibuffer-window)
+ (if (eolp)
+ (delete-region (minibuffer-prompt-end) (point))
+ (delete-region (point) (point-max)))))
+(put 'isl-delete-minibuffer-contents 'no-helm-mx t)
+
+(defun isl--find-and-goto-overlay (overlay)
+ "Consume iterators up to OVERLAY and jump to it."
+ (with-selected-window (minibuffer-selected-window)
+ (let (ov)
+ (while (not (eql (setq ov (isl-iter-next isl--iterator))
+ overlay)))
+ (isl--highlight-last-overlay 'isl-match)
+ (and ov (isl--goto-overlay ov)))
+ (isl-setup-mode-line)))
+
+(defun isl-goto-first ()
+ "Goto first match."
+ (interactive)
+ (let ((ov (car isl--item-overlays)))
+ (if (eql ov isl--last-overlay)
+ (user-error "Already at first occurence")
+ (isl--find-and-goto-overlay ov))))
+(put 'isl-goto-first 'no-helm-mx t)
+
+(defun isl-goto-last ()
+ "Goto last match."
+ (interactive)
+ (let ((ov (car (last isl--item-overlays))))
+ (if (eql ov isl--last-overlay)
+ (user-error "Already at last occurence")
+ (isl--find-and-goto-overlay ov))))
+(put 'isl-goto-last 'no-helm-mx t)
+
+(defun isl-goto-closest-from-start ()
+ "Goto closest match from start."
+ (interactive)
+ (let ((ov (isl-closest-overlay
+ isl-initial-pos isl--item-overlays)))
+ (if (eql ov isl--last-overlay)
+ (user-error "Already at closest occurence from start")
+ (isl--find-and-goto-overlay ov))))
+(put 'isl-goto-closest-from-start 'no-helm-mx t)
+
+(defun isl-goto-next (&optional arg)
+ "Go to next match."
+ (interactive "p")
+ (when (eq isl--direction 'backward)
+ (setq isl--direction 'forward)
+ (isl-set-iterator t))
+ (isl-goto-next-1 arg))
+(put 'isl-goto-next 'no-helm-mx t)
+
+(defun isl-goto-prev (&optional arg)
+ "Go to previous match"
+ (interactive "p")
+ (when (eq isl--direction 'forward)
+ (setq isl--direction 'backward)
+ (isl-set-iterator t))
+ (isl-goto-next-1 arg))
+(put 'isl-goto-prev 'no-helm-mx t)
+
+(defun isl-exit-at-point ()
+ "Exit minibuffer and jump at current position."
+ (interactive)
+ (with-selected-window (minibuffer-selected-window)
+ ;; Ensure user haven't scrolled to another place.
+ (goto-char (overlay-end isl--last-overlay))
+ (when isl-save-pos-to-mark-ring
+ (set-marker (mark-marker) isl-initial-pos)
+ (push-mark isl-initial-pos 'nomsg))
+ (let ((ov (make-overlay (point-at-bol) (1+ (point-at-eol)))))
+ (overlay-put ov 'face 'isl-line)
+ (sit-for 0.2)
+ (delete-overlay ov)))
+ ;; Call `exit-minibuffer' out of the `with-selected-window' block to
+ ;; avoid error with the emacs-28 version.
+ (exit-minibuffer))
+(put 'isl-exit-at-point 'no-helm-mx t)
+
+(defun isl-yank-word-at-point ()
+ "Yank word at point in minibuffer.
+The word at point is relative to the current position in buffer, not
+the initial position i.e. the position before launching `isl-search'."
+ (interactive)
+ (let (str)
+ (with-current-buffer isl-current-buffer
+ (when (or (memq (char-syntax (or (char-after) 0)) '(?w ?_))
+ (memq (char-syntax (or (char-after (1+ (point))) 0))
+ '(?w ?_)))
+ (setq str (buffer-substring-no-properties (or isl--yank-point (point))
+ (save-excursion
+ (forward-word)
+ (point))))
+ (when (string-match "\\` " str)
+ (setq str (replace-match "\\\\ " nil nil str)))
+ (with-selected-window (minibuffer-window)
+ (insert str))))))
+(put 'isl-yank-word-at-point 'no-helm-mx t)
+
+(defun isl-recenter ()
+ "Recenter from isl."
+ (interactive)
+ (with-selected-window (minibuffer-selected-window)
+ (recenter)))
+(put 'isl-recenter 'no-helm-mx t)
+
+(defun isl-matching-style ()
+ "Return current matching style as a string."
+ (cl-ecase isl-search-function
+ (re-search-forward "Regex")
+ (search-forward "Literal")))
+
+(defun isl-change-matching-style ()
+ "Toggle style matching in `isl-search' i.e. regexp/literal."
+ (interactive)
+ (with-current-buffer isl-current-buffer
+ (setq-local isl-search-function
+ (cl-ecase isl-search-function
+ (re-search-forward #'search-forward)
+ (search-forward #'re-search-forward)))
+ (when (string= isl-pattern "")
+ (let* ((style (isl-matching-style))
+ (mode-line-format (format " Switching to %s searching" style)))
+ (force-mode-line-update)
+ (sit-for 1)))
+ (isl-update)))
+(put 'isl-change-matching-style 'no-helm-mx t)
+
+(defun isl-jump-to-helm-occur ()
+ "Invoke `helm-occur' from `isl-search'."
+ (interactive)
+ (cl-assert (require 'helm-occur nil t) nil "Please install Helm package")
+ (let ((input isl-pattern)
+ (bufs (list isl-current-buffer)))
+ (run-at-time 0.1 nil
+ (lambda ()
+ ;; Use `helm-occur-always-search-in-current' as a
+ ;; flag for `helm-occur--select-closest-candidate'.
+ (let ((helm-occur-always-search-in-current t))
+ (helm-multi-occur-1 bufs input))))
+ (abort-recursive-edit)))
+(put 'isl-jump-to-helm-occur 'no-helm-mx t)
+
+(defun isl-query-replace (&optional arg)
+ (interactive "P")
+ (let ((style (isl-matching-style))
+ (regexp isl-pattern)
+ (start (overlay-start isl--last-overlay)))
+ (run-at-time
+ 0.1 nil
+ (lambda ()
+ (let* ((regexp-flag (string= style "Regex"))
+ (prompt (if regexp-flag
+ "Query replace %s regexp"
+ "Query replace %s"))
+ (args (list
+ regexp
+ (query-replace-read-to
+ regexp
+ (format prompt (if arg "word" ""))
+ regexp-flag)
+ arg)))
+ (with-current-buffer isl-current-buffer
+ (save-excursion
+ (let ((case-fold-search t))
+ (goto-char start)
+ (apply #'perform-replace
+ (list (nth 0 args) (nth 1 args)
+ t regexp-flag (nth 2 args) nil
+ multi-query-replace-map))))))))
+ (abort-recursive-edit)))
+(put 'isl-query-replace 'no-helm-mx t)
+
+;; Iedit
+;;
+(defun isl--advice-iedit-start (old--fn &rest args)
+ "Allow iedit matching multi pattern."
+ (cl-letf (((symbol-function 'iedit-make-occurrences-overlays)
+ #'isl--iedit-make-occurrences-overlays))
+ (apply old--fn args)))
+
+(defun isl--iedit-make-occurrences-overlays (occurrence-regexp beg end)
+ "Same as `iedit-make-occurrences-overlays' but handle multiple regexps."
+ (setq iedit-aborting nil)
+ (setq iedit-occurrences-overlays nil)
+ (setq iedit-read-only-occurrences-overlays nil)
+ ;; Find and record each occurrence's markers and add the overlay to the
occurrences
+ (let ((counter 0)
+ (case-fold-search (not iedit-case-sensitive))
+ (length 0)
+ bounds)
+ (save-excursion
+ (save-selected-window
+ (goto-char beg)
+ (while (setq bounds (isl-multi-search-fwd occurrence-regexp end t))
+ (let ((beginning (car bounds))
+ (ending (cdr bounds)))
+ (if (and (> length 0) (/= (- ending beginning) length))
+ (throw 'not-same-length 'not-same-length)
+ (setq length (- ending beginning)))
+ (if (text-property-not-all beginning ending 'read-only nil)
+ (push (iedit-make-read-only-occurrence-overlay beginning
ending)
+ iedit-read-only-occurrences-overlays)
+ (push (iedit-make-occurrence-overlay beginning ending)
+ iedit-occurrences-overlays))
+ (setq counter (1+ counter))))))
+ (iedit-update-index)
+ counter))
+
+(defun isl-jump-to-iedit-mode ()
+ "Start Iedit mode from `isl' using last search string as the regexp."
+ (interactive)
+ (cl-assert (require 'iedit nil t))
+ (let ((regexp (if (eq isl-search-function 'search-forward)
+ (regexp-quote isl-pattern)
+ isl-pattern))
+ (pos (with-current-buffer isl-current-buffer
+ (overlay-end isl--last-overlay))))
+ (run-at-time
+ 0.1 nil
+ (lambda ()
+ (save-restriction
+ (when (and isl--point-min isl--point-max)
+ (narrow-to-region isl--point-min isl--point-max))
+ (let ((case-fold-search (isl-set-case-fold-search regexp))
+ result)
+ (setq mark-active nil)
+ (run-hooks 'deactivate-mark-hook)
+ (when iedit-mode
+ (iedit-lib-cleanup))
+ (advice-add 'iedit-start :around #'isl--advice-iedit-start)
+ (unwind-protect
+ (progn
+ (setq result
+ (catch 'not-same-length
+ (iedit-start regexp (point-min) (point-max))))
+ (cond ((not iedit-occurrences-overlays)
+ (message "No matches found for %s" regexp)
+ (iedit-done))
+ ((equal result 'not-same-length)
+ (message "Matches are not the same length.")
+ (iedit-done)))
+ (goto-char pos))
+ (advice-remove 'iedit-start #'isl--advice-iedit-start))))))
+ (abort-recursive-edit)))
+(put 'isl-jump-to-iedit-mode 'no-helm-mx t)
+
+(defun isl-display-or-quit-help ()
+ "Display or quit isl help buffer."
+ (interactive)
+ (if (get-buffer-window isl-help-buffer-name 'visible)
+ (progn
+ (switch-to-buffer isl-help-buffer-name)
+ (quit-window t))
+ (with-current-buffer-window
+ (get-buffer-create isl-help-buffer-name)
+ (cons 'display-buffer-below-selected
+ '((window-height . fit-window-to-buffer)
+ (preserve-size . (nil . t))))
+ nil
+ (with-current-buffer standard-output
+ (insert
+ (substitute-command-keys
+ isl-help-string)))
+ (outline-mode)
+ (setq buffer-read-only t)
+ (local-set-key (kbd "q") 'quit-window))))
+(put 'isl-display-or-quit-help 'no-helm-mx t)
+
+(defun isl-help-quit ()
+ (interactive)
+ (let ((win (get-buffer-window isl-help-buffer-name 'visible)))
+ (if win
+ (with-selected-window win
+ (quit-window))
+ (user-error "No help buffer found"))))
+(put 'isl-help-quit 'no-helm-mx t)
+
+(defun isl-show-or-hide-context-lines ()
+ "Hide or show non matching lines."
+ (interactive)
+ (when isl--item-overlays
+ (with-selected-window (minibuffer-selected-window)
+ (if (setq isl--hidding (not isl--hidding))
+ (let ((start 1) ; start at point-min.
+ ov-end bol)
+ (save-excursion
+ (goto-char (overlay-end (car isl--item-overlays)))
+ (setq ov-end (point))
+ (set (make-local-variable 'line-move-ignore-invisible) t)
+ (add-to-invisibility-spec '(isl-invisible . t))
+ (while (not (eobp))
+ (forward-line (- isl-visible-context-lines))
+ ;; Store position from n lines before
+ ;; this overlay and bol and move to next overlay.
+ (when (> (setq bol (point-at-bol)) start)
+ (isl--put-invisible-overlay start (1- bol)))
+ (goto-char ov-end)
+ ;; Go to n lines after last overlay found and jump to
+ ;; next overlay from there.
+ (forward-line isl-visible-context-lines)
+ (setq start (1+ (point-at-eol)))
+ (goto-char (next-single-char-property-change ov-end 'isl))
+ (setq ov-end (point)))
+ ;; Store maybe remaining lines up to eob.
+ (when (< start (point-max))
+ (isl--put-invisible-overlay start (point-max)))))
+ (remove-overlays nil nil 'isl-invisible t)
+ (remove-from-invisibility-spec '(isl-invisible . t))))))
+(put 'isl-show-or-hide-context-lines 'no-helm-mx t)
+
+(defun isl--put-invisible-overlay (beg end)
+ "Make an invisible overlay from BEG to END."
+ (let ((ol (make-overlay beg end)))
+ (overlay-put ol 'isl-invisible t)
+ (overlay-put ol 'invisible 'isl-invisible)))
+
+(defun isl-iter-circular (seq)
+ "Infinite iteration on SEQ."
+ (let ((lis seq))
+ (lambda ()
+ (let ((elm (car lis)))
+ (setq lis (pcase lis (`(,_ . ,ll) (or ll seq))))
+ elm))))
+
+(defun isl-iter-next (iterator)
+ "Return next elm of ITERATOR."
+ (and iterator (funcall iterator)))
+
+(defun isl-delete-overlays ()
+ "Cleanup ovelays."
+ (when isl--item-overlays
+ (remove-overlays nil nil 'isl t)
+ (setq isl--item-overlays nil)))
+
+(cl-defun isl-set-case-fold-search (&optional (pattern isl-pattern))
+ "Return a suitable value for `case-fold-search'.
+This is done according to `isl-case-fold-search'.
+Optional argument PATTERN default to `isl-pattern'."
+ (cl-case isl-case-fold-search
+ (smart (let ((case-fold-search nil))
+ (if (string-match "[[:upper:]]" pattern) nil t)))
+ (t isl-case-fold-search)))
+
+(defun isl-select-case-fold-search ()
+ "Set `case-fold-search' from `isl-search' session."
+ (interactive)
+ (with-current-buffer isl-current-buffer
+ (if (eq last-command 'isl-select-case-fold-search)
+ (setq-local isl-case-fold-search
+ (isl-iter-next isl--case-fold-choices-iterator))
+ (setq isl--case-fold-choices-iterator
+ (isl-iter-circular
+ (append (remove isl-case-fold-search isl--case-fold-choices)
+ (list isl-case-fold-search))))
+ (setq-local isl-case-fold-search
+ (isl-iter-next isl--case-fold-choices-iterator)))
+ (isl-update)))
+(put 'isl-select-case-fold-search 'no-helm-mx t)
+
+(defun isl-split-string (str)
+ "Split string STR at non quoted spaces."
+ (split-string
+ (replace-regexp-in-string
+ isl-space-regexp "\\s-" str nil t)))
+
+(defun isl-patterns (str)
+ "Returns an alist of (pred . regexp) elements from STR."
+ (cl-loop for s in (isl-split-string str)
+ collect (if (char-equal ?! (aref s 0))
+ (cons 'not (substring s 1))
+ (cons 'identity s))))
+
+(defun isl-multi-search-fwd (str &optional _bound _noerror _count)
+ "Returns position of symbol matched by STR.
+When arg STR contains spaces, it is converted in patterns with
+`isl-patterns' , when first pattern of list match a symbol
+subsequent patterns are used to check if all patterns match this
+symbol. The return value is a cons cell (beg . end) denoting
+symbol position."
+ ;; Prevent infloop crashing Emacs with incorrect configuration.
+ (cl-assert (memq isl-search-function '(re-search-forward search-forward)))
+ (let* ((pattern (isl-patterns str))
+ (initial (or (assq 'identity pattern)
+ '(identity . "")))
+ (rest (cdr pattern)))
+ (cl-loop while (funcall isl-search-function (cdr initial) nil t)
+ for bounds = (if rest
+ (bounds-of-thing-at-point
+ (if (derived-mode-p 'prog-mode)
+ 'symbol 'filename))
+ (cons (match-beginning 0) (match-end 0)))
+ if (or (not rest)
+ (cl-loop for (pred . re) in rest
+ always (funcall pred
+ (progn
+ (goto-char (car bounds))
+ (funcall isl-search-function
+ re (cdr bounds) t)))))
+ do (goto-char (cdr bounds)) and return bounds
+ else do (goto-char (cdr bounds))
+ finally return nil)))
+
+(defun isl-update ()
+ "Update `current-buffer' when `isl-pattern' changes."
+ (with-selected-window (minibuffer-selected-window)
+ (while-no-input
+ (when isl--hidding
+ (remove-overlays nil nil 'isl-invisible t)
+ (remove-from-invisibility-spec '(isl-invisible . t))
+ (setq isl--hidding nil))
+ (isl-delete-overlays)
+ (setq isl--invalid nil)
+ ;; We don't use the isearch-invisible mechanism which is heavy
+ ;; and don't behave as we want, instead remove invisibility in
+ ;; all buffer and on exit restore it and unhide only the place
+ ;; where point is with appropriate functions belonging to
+ ;; major-mode e.g. org => org-reveal etc...
+ (when (and buffer-invisibility-spec
+ (listp buffer-invisibility-spec))
+ (mapc 'remove-from-invisibility-spec buffer-invisibility-spec))
+ (let ((case-fold-search (isl-set-case-fold-search))
+ (count 1)
+ ov
+ bounds)
+ (unless (string= isl-pattern "")
+ (save-excursion
+ (goto-char (point-min))
+ (condition-case-unless-debug nil
+ (while (setq bounds (isl-multi-search-fwd isl-pattern nil t))
+ (setq ov (make-overlay (car bounds) (cdr bounds)))
+ (push ov isl--item-overlays)
+ (overlay-put ov 'isl t)
+ (overlay-put ov 'pos count)
+ (overlay-put ov 'face 'isl-match)
+ (cl-incf count))
+ (invalid-regexp (setq isl--invalid t) nil))
+ (setq isl--item-overlays (reverse isl--item-overlays)))
+ (if (null isl--item-overlays)
+ (progn (setq isl--number-results 0) (goto-char isl-initial-pos))
+ (setq isl--last-overlay
+ (isl-closest-overlay isl-initial-pos isl--item-overlays)
+ isl--number-results (length isl--item-overlays))
+ (isl--highlight-last-overlay 'isl-on)
+ (isl-set-iterator)
+ (goto-char (overlay-end (isl-iter-next isl--iterator)))
+ (setq isl--yank-point (point)))))
+ (isl-setup-mode-line))))
+
+(defun isl-setup-mode-line ()
+ "Setup `mode-line-format' for `isl-search'."
+ (let ((style (isl-matching-style))
+ (position (with-current-buffer isl-current-buffer
+ (if (> (point) isl-initial-pos)
+ isl-after-position-string
+ isl-before-position-string)))
+ (direction (if (eq isl--direction 'forward)
+ isl-direction-down-string
+ isl-direction-up-string)))
+ (setq mode-line-format
+ (cond ((or (string= isl-pattern "")
+ (<= (length isl-pattern)
+ isl-requires-pattern))
+ (default-value 'mode-line-format))
+ ((zerop isl--number-results)
+ `(" " mode-line-buffer-identification " "
+ (:eval ,(format "%s `%s' [%s %s]"
+ (if isl--invalid
+ (propertize
+ (format "%s Invalid regexp:"
isl-warning-char)
+ 'face 'font-lock-warning-face)
+ "No results found for pattern")
+ (propertize isl-pattern
+ 'face 'isl-string)
+ style
+ direction))
+ " " mode-line-position))
+ (t `(" " mode-line-buffer-identification " "
+ (:eval ,(format
+ "[%s/%s] result(s) found [%s %s %s %s]"
+ (propertize
+ (number-to-string
+ (overlay-get isl--last-overlay 'pos))
+ 'face 'isl-number)
+ (propertize (number-to-string
+ isl--number-results)
+ 'face 'isl-number)
+ style
+ direction
+ position
+ (propertize (pcase isl-case-fold-search
+ (`smart "*")
+ (`t "1")
+ (`nil "0"))
+ 'face 'isl-case-fold)))
+ " " mode-line-position))))))
+
+(defun isl-closest-overlay (pos overlays)
+ "Return closest overlay from POS in OVERLAYS list."
+ (cl-loop for ov in overlays
+ for ovpos = (overlay-start ov)
+ for diff = (if (> pos ovpos) (- pos ovpos) (- ovpos pos))
+ collect (cons diff ov) into res
+ minimize diff into min
+ finally return (cdr (assq min res))))
+
+(defun isl-set-iterator (&optional skip-first)
+ "Build `isl--iterator' against `isl--item-overlays' according to context.
+When SKIP-FIRST is specified build overlay with the current overlay
+appended at end."
+ (let* ((revlst (if (eq isl--direction 'forward)
+ isl--item-overlays
+ (reverse isl--item-overlays)))
+ (fwdlst (memql isl--last-overlay revlst))
+ (ovlst (append (if skip-first (cdr fwdlst) fwdlst)
+ (butlast revlst (length fwdlst))
+ (and skip-first (list (car fwdlst))))))
+ (setq isl--iterator (isl-iter-circular ovlst))))
+
+(defun isl-check-input ()
+ "Check minibuffer input."
+ (with-selected-window (minibuffer-window)
+ (let ((input (minibuffer-contents)))
+ (when (not (string= input isl-pattern))
+ (setq isl-pattern input)
+ (if (> (length input) isl-requires-pattern)
+ (isl-update)
+ (with-selected-window (minibuffer-selected-window)
+ (isl-delete-overlays)
+ (isl-setup-mode-line)
+ (goto-char isl-initial-pos)))))))
+
+(defun isl-read-from-minibuffer (prompt &optional initial-input)
+ "Read input from minibuffer with prompt PROMPT."
+ (let (timer)
+ (unwind-protect
+ (minibuffer-with-setup-hook
+ (lambda ()
+ (setq timer (run-with-idle-timer
+ isl-timer-delay 'repeat #'isl-check-input)))
+ (read-from-minibuffer
+ prompt initial-input isl-map nil 'isl-history
+ (if (region-active-p)
+ (buffer-substring-no-properties
+ (region-beginning)
+ (region-end))
+ (thing-at-point 'symbol t))))
+ (cancel-timer timer))))
+
+(defun isl-cleanup ()
+ "Cleanup various things when `isl-search' exit."
+ (with-current-buffer isl-current-buffer
+ (let* ((pos (and isl--last-overlay ; nil when quitting with no results.
+ (overlay-end isl--last-overlay)))
+ (hs-show-hook (list (lambda () (and pos (goto-char pos))))))
+ (when (buffer-live-p (get-buffer isl-help-buffer-name))
+ (kill-buffer isl-help-buffer-name))
+ (setq isl-last-object
+ `(lambda ()
+ (setq-local mode-line-format ',mode-line-format
+ isl-last-query ,isl-pattern
+ isl-initial-pos ,pos
+ isl--yank-point ,isl--yank-point
+ isl--number-results ,isl--number-results
+ isl-case-fold-search ',isl-case-fold-search
+ isl-search-function ',isl-search-function
+ buffer-invisibility-spec ,buffer-invisibility-spec
+ isl--hidding ,isl--hidding
+ cursor-in-non-selected-windows
,cursor-in-non-selected-windows)))
+ (isl-delete-overlays)
+ (setq mode-line-format (default-value 'mode-line-format)
+ isl--yank-point nil
+ isl--iterator nil
+ isl--item-overlays nil
+ isl--last-overlay nil
+ isl--number-results nil
+ isl-case-fold-search (default-value 'isl-case-fold-search)
+ isl-search-function (default-value 'isl-search-function)
+ buffer-invisibility-spec isl--buffer-invisibility-spec
+ isl--hidding nil
+ cursor-in-non-selected-windows
+ (default-value 'cursor-in-non-selected-windows))
+ (remove-overlays nil nil 'isl-invisible t)
+ (if isl--quit
+ (setq isl--quit nil)
+ (condition-case-unless-debug _err
+ (cond ((eq major-mode 'org-mode)
+ (org-reveal))
+ ((eq major-mode 'outline-mode)
+ (outline-show-entry))
+ ((and (boundp 'hs-minor-mode)
+ hs-minor-mode)
+ (hs-show-block))
+ ((and (boundp 'markdown-mode-map)
+ (derived-mode-p 'markdown-mode))
+ (markdown-show-entry)))
+ (error nil))))))
+
+
+(defun isl-search-1 (&optional resume)
+ "Launch isl in current-buffer."
+ (unless resume
+ (setq isl-initial-pos (point)
+ isl-pattern ""
+ isl--direction 'forward
+ isl-current-buffer (current-buffer)
+ isl--buffer-invisibility-spec buffer-invisibility-spec
+ cursor-in-non-selected-windows nil))
+ (when isl-current-buffer
+ (setq isl-visited-buffers
+ (cons isl-current-buffer
+ (delete isl-current-buffer isl-visited-buffers))))
+ (unwind-protect
+ (condition-case-unless-debug nil
+ (isl-read-from-minibuffer
+ "Search: " (when resume
+ (buffer-local-value
+ 'isl-last-query isl-current-buffer)))
+ (quit
+ (setq isl--quit t)
+ (goto-char isl-initial-pos)))
+ (isl-cleanup)
+ ;; Avoid loosing focus in helm help buffer.
+ (unless (eq (window-buffer (selected-window))
+ isl-current-buffer)
+ (switch-to-buffer isl-current-buffer))))
+
+;;;###autoload
+(defun isl-search ()
+ "Start incremental searching in current buffer."
+ (interactive)
+ (setq isl--point-min nil
+ isl--point-max nil)
+ (isl-search-1))
+
+(defun isl-resume (arg)
+ "Resume isl session in current buffer.
+With a prefix arg choose one of the last buffers isl had visited."
+ (interactive "P")
+ (setq isl-current-buffer
+ (cond ((and arg isl-visited-buffers)
+ (get-buffer
+ (completing-read
+ "Resume from buffer: "
+ (mapcar 'buffer-name isl-visited-buffers)
+ nil t)))
+ (isl-visited-buffers
+ (car (memql (current-buffer)
+ isl-visited-buffers)))
+ (t isl-current-buffer)))
+ (cl-assert isl-current-buffer
+ nil "No buffer handling an isl session found")
+ (switch-to-buffer isl-current-buffer)
+ (with-current-buffer isl-current-buffer
+ (funcall isl-last-object)
+ (setq isl-pattern ""))
+ (isl-search-1 'resume))
+
+;;;###autoload
+(defun isl-narrow-to-defun ()
+ "Start incremental searching in current defun."
+ (interactive)
+ (setq isl--point-min nil
+ isl--point-max nil)
+ (save-restriction
+ (narrow-to-defun)
+ (setq isl--point-min (point-min)
+ isl--point-max (point-max))
+ (isl-search-1)))
+
+(provide 'isearch-light)
+
+;;; isearch-light.el ends here