;;; wrap-search.el --- wrapped, non-incremental search -*- lexical-binding: t -*- ;; ;; Copyright (C) 2023 Free Software Foundation, Inc. ;; ;; Author: Emanuel Berg ;; Created: 2021-05-23 ;; Git: git clone https://dataswamp.org/~incal/wrap-search.git ;; Keywords: matching ;; License: GPL3+ ;; URL: https://dataswamp.org/~incal/elpa/wrap-search.el ;; Version: 4.17.6 ;; ;; This file is part of GNU Emacs. ;; ;; GNU Emacs 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. ;; ;; GNU Emacs 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 GNU Emacs. If not, see . ;; ;;; Commentary: ;; ;; The `wrap-search' package offers non-incremental search. ;; It shows hitss (hits and only hits) and search only starts ;; when one presses RET. ;; ;; `wrap-search' searches forward by default, but it wraps ;; and continues up and until the search start position if ;; necessary. This is so one doesn't have to wonder if the ;; target is north or south in the buffer. ;; ;; The philosophy is that `wrap-search' mostly shouldn't be ;; used for searching - that is possible as well, of course, ;; using the same command - rather, it is used to quickly ;; navigate a buffer. ;; ;; To use the package without keyboard shortcuts isn't ;; recommended. Instead, do for example ;; ;; (keymap-global-set "C-s" #'wrap-search) ;; (keymap-global-set "C-r" #'wrap-search-again) ;; ;; One can use the `universal-argument' key, which is ;; typically C-u, before those functions to set two search ;; options before the search. If we assume the above keys are ;; used, then, for `wrap-search', here are the keys and what ;; they do, ;; ;; C-u C-s do case-sensitive search ;; C-u C-u C-s do reverse search, direction north from point ;; C-u C-u C-u C-s do case-sensitive, reverse search ;; ;; and for `wrap-search-again', using a corresponding interface ;; ;; C-u C-r reverse previous search case-sensitive setting ;; C-u C-u C-r reverse previous search reverse setting ;; C-u C-u C-u C-r reverse previous search case-sensitive and reverse settings ;; ;; and the search is done again, with those settings. ;; ;; See the `wrap-search' and `wrap-search-again' docstrings. ;; ;;; Code: (let ((prev-str "dummy search string") (prev-case) (prev-rev) (prev-beg) (prev-end)) (defun wrap-search-again (&optional rev-case rev-rev) "Repeat the search previously done with `wrap-search'. Interactively, use a preceding \\[universal-argument] to set REV-CASE (reverse current case-sensitivity setting) \\[universal-argument] \\[universal-argument] to set REV-REV (ditto search direction) \\[universal-argument] \\[universal-argument] \\[universal-argument] to set both" (interactive (list (member current-prefix-arg '( (4) (64))) (member current-prefix-arg '((16) (64))))) (when rev-case (setq prev-case (not prev-case))) (when rev-rev (setq prev-rev (not prev-rev))) (wrap-search prev-str prev-case prev-rev prev-beg prev-end)) (declare-function wrap-search-again nil) (defun wrap-search-show-default () (truncate-string-to-width prev-str 10 nil nil t)) (declare-function wrap-search-show-default nil) (defun wrap-search (str &optional case rev beg end) "Search for STR in the current buffer. With CASE the search is case-sensitive. With REV the search direction is reversed, so north in the buffer from point. BEG and END, or a region, delimits the search area, defaults to whole buffer. Interactively, use a preceeding \\[universal-argument] to set CASE (case-sensitive search) \\[universal-argument] \\[universal-argument] to set REV (search direction north) \\[universal-argument] \\[universal-argument] \\[universal-argument] to set both CASE and REV Do \\[wrap-search-again] to repeat, with `wrap-search-again'." (interactive `(,(read-string (format-prompt "search" (wrap-search-show-default))) ,(member current-prefix-arg '( (4) (64))) ,(member current-prefix-arg '((16) (64))) ,@(when (use-region-p) (list (region-beginning) (region-end))))) (let ((pos (point))) (when (or (not beg) (and rev (< pos beg))) (setq beg (point-min))) (when (or (not end) (and (not rev) (> pos end))) (setq end (point-max))) (if (string= "" str) (wrap-search-again) (setq prev-str str) (setq prev-case case) (setq prev-rev rev) (setq prev-beg beg) (setq prev-end end) (pcase-let ((case-fold-search (not case)) (`(,search-f ,search-beg ,search-end) (if rev (list #'search-backward end beg) (list #'search-forward beg end)))) (if (funcall search-f str search-end t) (message "hit: %s" (point)) (goto-char search-beg) (if (funcall search-f str (+ pos (if rev 0 (length str))) t) (if (= pos (point)) (message "this is the only occurrence") (message "wrap: %s" (point))) (goto-char pos) (message "no hit"))))))) (declare-function wrap-search nil)) (provide 'wrap-search) ;;; wrap-search.el ends here