├── .gitignore ├── Readme.org ├── img ├── replace.gif └── search.gif ├── phi-replace.el ├── phi-search-core.el └── phi-search.el /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | 3 | -------------------------------------------------------------------------------- /Readme.org: -------------------------------------------------------------------------------- 1 | * phi-search.el 2 | 3 | multiple-cursors.el対応のインクリメンタルサーチ 4 | 5 | another incremental search & replace, compatible with "multiple-cursors" 6 | 7 | ** Screencast 8 | 9 | [[img/search.gif]] 10 | 11 | ** Usage 12 | 13 | Require this script 14 | 15 | : (require 'phi-search) 16 | 17 | then command "phi-search" and "phi-search-backward" are available. You 18 | may bind a key to the command. 19 | 20 | : (global-set-key (kbd "C-s") 'phi-search) 21 | : (global-set-key (kbd "C-r") 'phi-search-backward) 22 | 23 | While searching, following commands are available. 24 | 25 | - [C-s] phi-search-again-or-next 26 | 27 | Move to the next matching item. If query is blank, use the last 28 | query. 29 | 30 | - [C-r] phi-search-again-or-previous 31 | 32 | Similar to phi-search-again-or-next, but move to the previous item. 33 | 34 | - [C-v] phi-search-scroll-up 35 | 36 | Scroll the target window up, to check candidates. 37 | 38 | - [M-v] phi-search-scroll-down 39 | 40 | Scroll the target window down. 41 | 42 | - [C-l] phi-search-recenter 43 | 44 | Recenter the target window. 45 | 46 | - [M-c] phi-search-case-toggle 47 | 48 | Toggle `phi-search-case-sensitive` on-the-fly. 49 | 50 | - [C-w] phi-search-yank-word 51 | 52 | Expand query by yanking one word from the target buffer. 53 | 54 | - [RET] phi-search-complete 55 | 56 | Finish searching. 57 | 58 | - [C-RET] phi-search-complete-at-beginning 59 | 60 | Finish searching at the beginning of the match. 61 | 62 | - [C-c C-c] phi-search-unlimit 63 | 64 | Force update results regardless of "phi-search-limit" 65 | 66 | - [C-g] phi-search-abort 67 | 68 | Finish searching, and move back to the original position. 69 | 70 | ** Working with Regions 71 | 72 | (in following examples, assume "[" as mark, and "|" as point) 73 | 74 | When phi-search is called with an active region, then the substring is 75 | used as the default query. 76 | 77 | : [region| another region |region another region region another |region 78 | : _______________________ ______________________ ______________________ 79 | : query: region query: region 80 | 81 | Or, if phi-search is called with an active mark but no region there, 82 | mark stays active until phi-search ends. So you may use this command 83 | to expand region. 84 | 85 | : [|mark me ! [|mark me ! [mark |me ! 86 | : ___________ ___________ ___________ 87 | : query: query: me 88 | 89 | ** Working with multiple-cursors.el 90 | 91 | phi-search is available even when multiple-cursors-mode is 92 | active. Here are some examples. 93 | 94 | : |foo bar baz |foo bar baz foo bar |baz 95 | : |foo bar baz |foo bar baz foo bar |baz 96 | : ____________ ____________ ____________ 97 | : query: query: baz 98 | 99 | : [|mark us ! [|mark us ! [mark |us ! 100 | : [|mark us with a region ! [|mark us with a region ! [mark |us with a region ! 101 | : _________________________ _________________________ _________________________ 102 | : query: query: us 103 | 104 | : [foo| ABC foo |foo ABC foo foo ABC |foo 105 | : [bar| EFGHIJK bar |bar EFGHIJK bar bar EFGHIJK |bar 106 | : _________________ ________________ ________________ 107 | : query: foo query: foo 108 | 109 | ** phi-replace.el 110 | 111 | "phi-replace" and "phi-replace-query" in "phi-replace.el" are 112 | interactive replace commands, that use the same interface as 113 | "phi-search". 114 | 115 | [[img/replace.gif]] 116 | 117 | To use these commands, require 118 | 119 | : (require 'phi-replace) 120 | 121 | and bind a key. 122 | 123 | : (global-set-key (kbd "M-%") 'phi-replace-query) 124 | 125 | Keybinds are basically the same as phi-search, by default. 126 | 127 | ** Customization 128 | *** Keybinds 129 | 130 | You may change keybindings by changing "phi-search-default-map". 131 | 132 | : (define-key phi-search-default-map (kbd "<") 'phi-search-again-or-previous) 133 | : (define-key phi-search-default-map (kbd ">") 'phi-search-again-or-next) 134 | 135 | This will affect all commands based on phi-search. If you want to add 136 | commands only for a specific command, you may use variables below. 137 | 138 | - phi-search-default-map :: the phi-search common keymap 139 | 140 | - phi-search-additional-keybinds :: list of (KEY . COMMAND) used in 141 | "phi-search(-backward)" command 142 | 143 | - phi-replace-additional-keybinds :: list of (KEY . COMMAND) used in 144 | "phi-replace(-query)" command 145 | 146 | : (push '((kbd "") . 'phi-search-complete-at-beginning) 147 | : phi-search-additional-keybinds) 148 | 149 | *** Searching 150 | 151 | You may change limit of search by setting "phi-search-limit", and 152 | case-sensitivity by "phi-search(replace)-case-sensitive". 153 | 154 | : (setq phi-search-limit 10000 155 | : phi-search-case-sensitive t) 156 | 157 | You may also set "phi-search-case-sensitive" to 'guess, to make 158 | phi-search case sensitive only when some upcase letters are in the 159 | query. 160 | 161 | *** Hooks 162 | 163 | Hooks "phi-search-init-hook" and "phi-replace-init-hook" are hooks run 164 | after initializing the minibuffer. 165 | 166 | *** Faces 167 | 168 | Matching items are propertized with "phi-search-match-face", and the 169 | selected item is propertized with "phi-search-selection-face". If they 170 | look not pretty in your colorscheme, you may modify them. 171 | 172 | : (set-face-attribute 'phi-search-selection-face nil 173 | : :background "orange") 174 | 175 | ** Notes 176 | 177 | This command uses "multiple-cursors" variables and behavior that are 178 | not documented. Therefore, after you update "multiple-cursors", it is 179 | good idea to test if this command works still correctly, before you 180 | actually use this command. 181 | -------------------------------------------------------------------------------- /img/replace.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zk-phi/phi-search/c34f5800968922d1f9e7b10092b8705d6640ad18/img/replace.gif -------------------------------------------------------------------------------- /img/search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zk-phi/phi-search/c34f5800968922d1f9e7b10092b8705d6640ad18/img/search.gif -------------------------------------------------------------------------------- /phi-replace.el: -------------------------------------------------------------------------------- 1 | ;;; phi-replace.el --- another incremental search & replace, compatible with "multiple-cursors" 2 | 3 | ;; Copyright (C) 2013- zk_phi 4 | 5 | ;; This program is free software; you can redistribute it and/or modify 6 | ;; it under the terms of the GNU General Public License as published by 7 | ;; the Free Software Foundation; either version 2 of the License, or 8 | ;; (at your option) any later version. 9 | ;; 10 | ;; This program is distributed in the hope that it will be useful, 11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ;; GNU General Public License for more details. 14 | ;; 15 | ;; You should have received a copy of the GNU General Public License 16 | ;; along with this program; if not, write to the Free Software 17 | ;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | 19 | ;; Author: zk_phi 20 | ;; URL: http://hins11.yu-yake.com/ 21 | ;; Version: 2.3.1 22 | 23 | ;;; Commentary: 24 | 25 | ;; Add following expression in your init file : 26 | ;; 27 | ;; (require 'phi-replace) 28 | ;; 29 | ;; and bind command "phi-replace" or "phi-replace-query" 30 | ;; 31 | ;; (global-set-key (kbd "M-%") 'phi-replace) 32 | 33 | ;; For more details, see "Readme". 34 | 35 | ;;; Change Log: 36 | 37 | ;; 1.0.0 first released 38 | ;; 1.0.1 added weight for phi-replace 39 | ;; 1.0.2 use "sublimity" not "nurumacs" 40 | ;; 1.0.3 better integration with sublimity 41 | ;; 1.0.4 added a hook 42 | ;; 1.0.5 added some commands 43 | ;; 1.0.6 better handling of narrowed buffer 44 | ;; 1.0.7 fixed bug on completing replace without matches 45 | ;; 1.0.8 use "remap" for default keybindings 46 | ;; 2.0.0 follow "phi-search" update 47 | ;; changed the default value of phi-replace-weight 48 | ;; 2.0.1 added phi-replace-init-hook 49 | ;; 2.0.2 compatible with phi-search-core v1.2.0 50 | ;; 2.1.0 provide '!' for phi-replace-query 51 | ;; 2.2.0 compatibility with phi-search-core v2.0.0 52 | ;; 2.3.0 add interactive preview feature 53 | ;; 2.3.1 call phi-replace even in popup window 54 | 55 | ;;; Code: 56 | 57 | (require 'phi-search-core) 58 | 59 | ;; + constant 60 | 61 | (defconst phi-replace-version "2.3.1") 62 | 63 | ;; + suppress byte-compiler 64 | 65 | (declare-function sublimity--pre-command "sublimity") 66 | (declare-function sublimity--post-command "sublimity") 67 | 68 | ;; + customs 69 | 70 | (defcustom phi-replace-weight nil 71 | "weight for \"phi-replace\"" 72 | :group 'phi-search 73 | :type 'number) 74 | 75 | (defcustom phi-replace-init-hook nil 76 | "hook run after initialization of phi-replace" 77 | :group 'phi-search 78 | :type 'hook) 79 | 80 | (defcustom phi-replace-additional-keybinds 81 | '(([remap phi-search-complete] . 'phi-replace-again-or-complete)) 82 | "additional bindings used in phi-replace" 83 | :group 'phi-search 84 | :type 'list) 85 | 86 | (defcustom phi-replace-enable-preview t 87 | "wnen non-nil, show interactive preview of replace." 88 | :group 'phi-search 89 | :type 'boolean) 90 | 91 | ;; + faces 92 | 93 | (defface phi-replace-preview-face 94 | '((t (:inherit 'highlight))) 95 | "Face used to show interactive preview." 96 | :group 'phi-search) 97 | 98 | ;; + variables 99 | 100 | (defvar phi-replace--original-restriction nil) 101 | (make-variable-buffer-local 'phi-replace--original-restriction) 102 | 103 | (defvar phi-replace--query-mode nil) 104 | (make-variable-buffer-local 'phi-replace--query-mode) 105 | 106 | ;; + start/end phi-replace 107 | 108 | (defvar phi-replace--mode-line-format 109 | '(" *phi-replace*" (:eval (format " [ %d ]" (length phi-search--overlays))))) 110 | 111 | (defun phi-replace--update-visual-preview (query replac) 112 | (save-excursion 113 | (dolist (ov phi-search--overlays) 114 | (goto-char (overlay-start ov)) 115 | (looking-at query) 116 | (overlay-put 117 | ov 'after-string 118 | (ignore-errors 119 | (propertize 120 | (concat "=>" (match-substitute-replacement replac)) 121 | 'face 'phi-replace-preview-face)))))) 122 | 123 | (defun phi-replace--complete-function () 124 | (phi-search--with-target-buffer 125 | (when phi-search--overlays 126 | (let* ((orig-cursor (make-overlay phi-search--original-position 127 | phi-search--original-position)) 128 | (enable-recursive-minibuffers t) 129 | (str (minibuffer-with-setup-hook 130 | (lambda () 131 | (when phi-replace-enable-preview 132 | (add-hook 'after-change-functions 133 | (lambda (&rest _) 134 | (let ((str (minibuffer-contents))) 135 | (with-current-buffer (cdr target) 136 | (phi-replace--update-visual-preview query str)))) 137 | nil t)) 138 | (with-current-buffer (cdr target) 139 | (phi-replace--update-visual-preview query ""))) 140 | (read-from-minibuffer "replace with ? ")))) 141 | (dotimes (n (length phi-search--overlays)) 142 | (if phi-replace--query-mode 143 | (phi-search--with-sublimity (phi-search--select n)) 144 | (phi-search--select n)) 145 | (let* ((ov (nth n phi-search--overlays)) 146 | (match-data (progn (goto-char (overlay-start ov)) 147 | (looking-at query) 148 | (match-data)))) 149 | (if (and phi-replace--query-mode 150 | (let ((ch (read-char-choice 151 | (format "replace with %s (y, n or !) ? " 152 | (match-substitute-replacement str)) 153 | '(?y ?n ?!)))) 154 | (if (= ch ?!) 155 | (setq phi-replace--query-mode nil) 156 | (= ch ?n)))) 157 | (overlay-put ov 'face 'defualt) 158 | (set-match-data match-data) 159 | (replace-match str)) 160 | (overlay-put ov 'after-string nil)) 161 | (when (and (not phi-replace--query-mode) phi-replace-weight) 162 | (sit-for phi-replace-weight))) 163 | (goto-char (overlay-start orig-cursor)))) 164 | (when phi-replace--original-restriction 165 | (let ((beg (car phi-replace--original-restriction)) 166 | (end (cdr phi-replace--original-restriction))) 167 | (narrow-to-region (overlay-start beg) (overlay-start end)) 168 | (delete-overlay beg) 169 | (delete-overlay end))) 170 | (setq phi-replace--original-restriction nil 171 | phi-replace--query-mode nil))) 172 | 173 | (defun phi-replace--initialize (&optional query) 174 | (setq phi-replace--query-mode query) 175 | ;; narrow to region 176 | (when (use-region-p) 177 | (setq phi-replace--original-restriction 178 | (cons 179 | (make-overlay (point-min) (point-min)) 180 | (make-overlay (point-max) (point-max)))) 181 | (narrow-to-region (region-beginning) (region-end)) 182 | (deactivate-mark)) 183 | (phi-search--initialize 184 | phi-replace--mode-line-format phi-replace-additional-keybinds nil nil 185 | 'phi-replace--complete-function 186 | nil (lambda () (run-hooks 'phi-replace-init-hook)) "phi-replace: ")) 187 | 188 | ;; + commands 189 | 190 | ;;;###autoload 191 | (defun phi-replace () 192 | "replace command using phi-search" 193 | (interactive) 194 | (phi-replace--initialize nil)) 195 | 196 | ;;;###autoload 197 | (defun phi-replace-query () 198 | "replace command using phi-search" 199 | (interactive) 200 | (phi-replace--initialize t)) 201 | 202 | (defun phi-replace-again-or-complete () 203 | "execute phi-replace. if the query is empty, use the last 204 | query." 205 | (interactive) 206 | (let ((str (phi-search--with-target-buffer 207 | phi-search--last-executed))) 208 | (when (and (string= (minibuffer-contents) "") str) 209 | (insert str))) 210 | (phi-search-complete)) 211 | 212 | ;; + provide 213 | 214 | (provide 'phi-replace) 215 | 216 | ;;; phi-replace.el ends here 217 | -------------------------------------------------------------------------------- /phi-search-core.el: -------------------------------------------------------------------------------- 1 | ;;; phi-search-core.el --- another incremental search & replace, compatible with "multiple-cursors" 2 | 3 | ;; Copyright (C) 2013- zk_phi 4 | 5 | ;; This program is free software; you can redistribute it and/or modify 6 | ;; it under the terms of the GNU General Public License as published by 7 | ;; the Free Software Foundation; either version 2 of the License, or 8 | ;; (at your option) any later version. 9 | ;; 10 | ;; This program is distributed in the hope that it will be useful, 11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ;; GNU General Public License for more details. 14 | ;; 15 | ;; You should have received a copy of the GNU General Public License 16 | ;; along with this program; if not, write to the Free Software 17 | ;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | 19 | ;; Author: zk_phi 20 | ;; URL: http://hins11.yu-yake.com/ 21 | ;; Version: 2.1.5 22 | 23 | ;;; Commentary: 24 | 25 | ;; This library is required by phi-search, phi-replace, etc. 26 | 27 | ;;; Change Log: 28 | 29 | ;; 1.0.0 divided from phi-search.el 1.2.2 30 | ;; 1.1.0 handle "isearch-open-invisible" properties 31 | ;; 1.2.0 implement "guess" option for "phi-search-case-sensitive" 32 | ;; 1.2.1 add variable "convert-query-function" 33 | ;; 1.2.2 add customizable variable "phi-search-hook" 34 | ;; 1.2.3 bug fix 35 | ;; 1.3.0 add highlight to mismatch part of search string 36 | ;; 1.3.1 add support for subword/jaword-mode 37 | ;; 2.0.0 use minibuffer to read query 38 | ;; 2.1.0 use "phi-search--message" to display messages 39 | ;; 2.1.1 add option phi-search-highlight-mismatch-part 40 | ;; 2.1.2 prefer direct keymapping to remapping 41 | ;; 2.1.3 add option "phi-search-overlay-priority" 42 | ;; 2.1.4 add command "phi-search-case-toggle" 43 | ;; 2.1.5 add option "phi-search-use-modeline" 44 | 45 | ;;; Code: 46 | 47 | (defconst phi-search-core-version "2.1.3") 48 | 49 | ;; + customs 50 | 51 | (defgroup phi-search nil 52 | "another incremental search interface" 53 | :group 'emacs) 54 | 55 | (defcustom phi-search-overlay-priority 1 56 | "Priority which phi-search overlays get." 57 | :group 'phi-search 58 | :type 'integer) 59 | 60 | (defcustom phi-search-limit 1000 61 | "maximum number of accepted matches" 62 | :group 'phi-search 63 | :type 'integer) 64 | 65 | (defcustom phi-search-case-sensitive nil 66 | "when non-nil, phi-search become case sensitive" 67 | :group 'phi-search 68 | :type 'boolean) 69 | 70 | (defcustom phi-search-highlight-mismatch-part t 71 | "when non-nil, mismatch part of the input is highlighted." 72 | :group 'phi-search 73 | :type 'boolean) 74 | 75 | (defcustom phi-search-use-modeline t 76 | "when non-nil, phi-search will override the modeline to display 77 | the status." 78 | :group 'phi-search 79 | :type 'boolean) 80 | 81 | (defcustom phi-search-default-map 82 | (let ((kmap (make-sparse-keymap))) 83 | (define-key kmap (kbd "C-s") 'phi-search-again-or-next) 84 | (define-key kmap (kbd "C-r") 'phi-search-again-or-previous) 85 | (define-key kmap [remap phi-search] 'phi-search-again-or-next) 86 | (define-key kmap [remap phi-search-backward] 'phi-search-again-or-previous) 87 | (define-key kmap (kbd "C-g") 'phi-search-abort) 88 | (define-key kmap (kbd "C-v") 'phi-search-scroll-up) 89 | (define-key kmap (kbd "M-v") 'phi-search-scroll-down) 90 | (define-key kmap (kbd "C-l") 'phi-search-recenter) 91 | (define-key kmap (kbd "M-c") 'phi-search-case-toggle) 92 | (define-key kmap (kbd "C-w") 'phi-search-yank-word) 93 | (define-key kmap (kbd "RET") 'phi-search-complete) 94 | (define-key kmap (kbd "C-c C-c") 'phi-search-unlimit) 95 | kmap) 96 | "keymap for the phi-search prompt buffers" 97 | :group 'phi-search 98 | :type 'sexp) 99 | 100 | (defcustom phi-search-hook nil 101 | "hook run when phi-search buffer is prepared." 102 | :group 'phi-search 103 | :type 'hook) 104 | 105 | ;; + faces 106 | 107 | (defface phi-search-match-face 108 | '((t (:inherit 'lazy-highlight))) 109 | "Face used to highlight matching items in phi-search." 110 | :group 'phi-search) 111 | 112 | (defface phi-search-selection-face 113 | '((t (:inherit 'isearch))) 114 | "Face used to highlight selected items in phi-search." 115 | :group 'phi-search) 116 | 117 | (defface phi-search-failpart-face 118 | '((t (:inherit 'isearch-fail))) 119 | "Face used to highlight mismatch part in phi-search buffer." 120 | :group 'phi-search) 121 | 122 | ;; + internal vars 123 | 124 | (defvar phi-search--active nil 125 | "non-nil if phi-search is active.") 126 | 127 | (defvar phi-search--last-executed nil 128 | "the last query used") 129 | (make-variable-buffer-local 'phi-search--last-executed) 130 | 131 | (defvar phi-search--filter-function nil 132 | "when non-nil, candidates are filtered with this function.") 133 | 134 | (defvar phi-search--original-position nil 135 | "position where this search is started from.") 136 | 137 | (defvar phi-search--overlays nil 138 | "an ordered list of active overlays in this target buffer.") 139 | 140 | (defvar phi-search--failed nil 141 | "non-nil if the last search was failed. 'err especially when 142 | the failure is caused by an error. `phi-search--overlays' can 143 | be nil on both failure and too-many-matches error, but this 144 | variable become non-nil only on failure.") 145 | 146 | (defvar phi-search--selection nil 147 | "index of currently selected item. if nothing's selected, this 148 | variable must be set nil.") 149 | 150 | (defvar phi-search--after-update-function nil 151 | "function called IN THE TARGET BUFFER as soon as overlays are updated") 152 | 153 | (defvar phi-search--saved-mode-line-format nil 154 | "saved modeline-format of the target buffer.") 155 | 156 | (defvar phi-search--target nil 157 | "the target (window . buffer) which this prompt buffer is for") 158 | 159 | (defvar phi-search--before-complete-function nil 160 | "function called IN THIS PROMPT BUFFER just before phi-search 161 | completes") 162 | 163 | (defvar phi-search--convert-query-function nil 164 | "function that converts search query.") 165 | 166 | (defvar phi-search--fail-pos nil 167 | "save position where search fail begin with.") 168 | 169 | (defvar phi-search--message-start nil 170 | "starting position of a message. nil if no message is active.") 171 | 172 | (defvar phi-search--pending-message nil 173 | "a pending message string, or nil.") 174 | 175 | (defvar phi-search--case-sensitive nil 176 | "case sensitivity for this phi-search session.") 177 | 178 | ;; + utilities 179 | 180 | (defun phi-search--search-forward (query limit &optional filter inclusive) 181 | "a handy version of search-forward-regexp, that phi-search uses 182 | to search for candidates. like (search-forward-regexp <> nil t) 183 | but case-sensitivity is handled automatically and result is 184 | filtered with FILTER. zero-width match is accepted only when 185 | INCLUSIVE is non-nil." 186 | (let* ((case-fold-search (or (not phi-search--case-sensitive) 187 | (and (eq phi-search--case-sensitive 'guess) 188 | (string= query (downcase query))))) 189 | (pos1 (point)) 190 | (pos2 (search-forward-regexp query limit t))) 191 | ;; recursion is not good idea here since specpdl may overflow if 192 | ;; there're too many matches that don't satisfy FILTER. 193 | (if (and (or inclusive (not pos2) (not (= pos1 pos2))) 194 | (or (null filter) (save-match-data (funcall filter)))) 195 | pos2 196 | ;; try another match 197 | (while (unless (eobp) 198 | (forward-char 1) 199 | (setq pos2 (search-forward-regexp query limit t)) 200 | (and filter (not (save-match-data (funcall filter)))))) 201 | pos2))) 202 | 203 | (defun phi-search--open-invisible-temporary (hidep) 204 | "show invisible text at point temporary. when optional arg 205 | HIDEP is non-nil, hide the opened text instead." 206 | (mapc (lambda (ov) 207 | (let ((ioit (overlay-get ov 'isearch-open-invisible-temporary))) 208 | (cond (ioit 209 | (funcall ioit ov hidep)) 210 | ((overlay-get ov 'isearch-open-invisible) 211 | (if hidep 212 | (overlay-put ov 'invisible (overlay-get ov 'phi-invisible)) 213 | (overlay-put ov 'phi-invisible (overlay-get ov 'invisible)) 214 | (overlay-put ov 'invisible nil)))))) 215 | (overlays-at (point)))) 216 | 217 | (defun phi-search--open-invisible-permanently () 218 | "show invisible text at point permanently" 219 | (mapc (lambda (ov) 220 | (let ((ioi (overlay-get ov 'isearch-open-invisible))) 221 | (when ioi (funcall ioi ov)))) 222 | (overlays-at (point)))) 223 | 224 | (defun phi-search--valid-regex-p (regex) 225 | "non-nil if REGEX is a valid regular expression" 226 | (ignore-errors 227 | (string-match regex "") 228 | t)) 229 | 230 | (declare-function sublimity--pre-command "sublimity") 231 | (declare-function sublimity--post-command "sublimity") 232 | (defmacro phi-search--with-sublimity (&rest body) 233 | "if sublimity is installed, use it" 234 | `(cond ((and (boundp 'sublimity-mode) sublimity-mode) 235 | (sublimity--pre-command) 236 | (prog1 (progn ,@body) 237 | (sublimity--post-command))) 238 | (t 239 | ,@body))) 240 | 241 | ;; + private functions for TARGET buffer 242 | 243 | (defun phi-search--delete-overlays (&optional keep-point) 244 | "delete all overlays in THIS target buffer, and go back to the 245 | original position. when optional arg KEEP-POINT is non-nil, 246 | delete overlays without movind the cursor." 247 | (mapc 'delete-overlay phi-search--overlays) 248 | (setq phi-search--overlays nil 249 | phi-search--selection nil) 250 | (unless keep-point 251 | (phi-search--open-invisible-temporary t) 252 | (goto-char phi-search--original-position))) 253 | 254 | (defun phi-search--make-overlays-for (query &optional unlimited) 255 | "perform search with QUERY, and make overlays for all matching 256 | items in THIS target buffer. when optional arg UNLIMITED is 257 | omitted or nil, number of matches is limited to 258 | `phi-search-limit'." 259 | (cond 260 | ((not (phi-search--valid-regex-p query)) 261 | (setq phi-search--failed 'err) 262 | (phi-search--message "invalid input")) 263 | (t 264 | (save-excursion 265 | (let ((before nil) (after nil) (cnt 0)) 266 | (goto-char (point-min)) 267 | (while (and (phi-search--search-forward query nil phi-search--filter-function) 268 | (let ((ov (make-overlay (match-beginning 0) (match-end 0)))) 269 | (overlay-put ov 'face 'phi-search-match-face) 270 | (overlay-put ov 'priority phi-search-overlay-priority) 271 | (if (< (match-beginning 0) phi-search--original-position) 272 | (push ov before) 273 | (push ov after)) 274 | (setq cnt (1+ cnt)) 275 | (or unlimited (< cnt phi-search-limit))))) 276 | (setq phi-search--overlays (nconc (nreverse after) (nreverse before))))) 277 | (let ((num (length phi-search--overlays))) 278 | (cond ((zerop num) 279 | (setq phi-search--failed t)) 280 | (t 281 | (setq phi-search--failed nil) 282 | (when (and (not unlimited) 283 | (>= num phi-search-limit)) 284 | (phi-search--message "too short") 285 | (phi-search--delete-overlays)))))))) 286 | 287 | (defun phi-search--select (n) 288 | "select Nth item and move cursor there. return point on 289 | success, or nil on failuare." 290 | (when (and (>= n 0) 291 | (< n (length phi-search--overlays))) 292 | ;; unselect old item 293 | (when phi-search--selection 294 | (phi-search--open-invisible-temporary t) 295 | (overlay-put (nth phi-search--selection phi-search--overlays) 296 | 'face 'phi-search-match-face)) 297 | ;; select new item if there 298 | (let ((ov (nth n phi-search--overlays))) 299 | (unless phi-search-use-modeline 300 | (phi-search--message (format "%d/%d" (1+ n) (length phi-search--overlays)))) 301 | (setq phi-search--selection n) 302 | (overlay-put ov 'face 'phi-search-selection-face) 303 | (goto-char (overlay-end ov)) 304 | (phi-search--open-invisible-temporary nil) 305 | (point)))) 306 | 307 | ;; + private functions for PROMPT buffer 308 | 309 | (defun phi-search--generate-query (q) 310 | (if (null phi-search--convert-query-function) q 311 | (funcall phi-search--convert-query-function q))) 312 | 313 | (defun phi-search--message (msg) 314 | (with-selected-window (minibuffer-window) 315 | (setq phi-search--pending-message msg))) 316 | 317 | (defmacro phi-search--with-target-buffer (&rest body) 318 | "eval body with the target buffer selected. 319 | \"target\" and \"query\" are brought from the prompt buffer" 320 | `(progn 321 | ;; assert that the window and the buffer live 322 | (cond ((null phi-search--target) 323 | (error "phi-search: unexpected error (phi-search--target is nil)")) 324 | ((not (window-live-p (car phi-search--target))) 325 | (error "phi-search: target window is deleted")) 326 | ((not (buffer-live-p (cdr phi-search--target))) 327 | (error "phi-search: target buffer is killed"))) 328 | ;; visit the window, with variables from the prompt buffer 329 | (let ((target phi-search--target) 330 | (query (phi-search--generate-query (minibuffer-contents)))) 331 | (with-selected-window (car target) 332 | ;; if buffer is switched, switch back to the target 333 | (unless (eq (current-buffer) (cdr target)) 334 | (switch-to-buffer (cdr target))) 335 | ;; eval body 336 | ,@body)))) 337 | 338 | (defun phi-search-next () 339 | "select next item." 340 | (phi-search--with-target-buffer 341 | (when (null phi-search--selection) 342 | (signal 'search-failed "No matches")) 343 | (phi-search--with-sublimity 344 | (unless (phi-search--select (1+ phi-search--selection)) 345 | (phi-search--select 0) 346 | (ding) 347 | (message "no more matches"))))) 348 | 349 | (defun phi-search-previous () 350 | "select previous item." 351 | (phi-search--with-target-buffer 352 | (when (null phi-search--selection) 353 | (signal 'search-failed "No matches")) 354 | (phi-search--with-sublimity 355 | (unless (phi-search--select (1- phi-search--selection)) 356 | (phi-search--select (1- (length phi-search--overlays))) 357 | (ding) 358 | (message "no more matches"))))) 359 | 360 | (defun phi-search--update (&optional pos &rest _) 361 | "update overlays for the target buffer. POS specifies the 362 | position from where input is highlighted when search failed." 363 | (let ((status (phi-search--with-target-buffer 364 | (phi-search--with-sublimity 365 | (phi-search--delete-overlays) 366 | (phi-search--make-overlays-for query) 367 | (phi-search--select 0) 368 | (when phi-search--after-update-function 369 | (funcall phi-search--after-update-function))) 370 | phi-search--failed))) 371 | (when phi-search-highlight-mismatch-part 372 | (cond 373 | ((null status) ; success 374 | (setq phi-search--fail-pos nil) 375 | (put-text-property (minibuffer-prompt-end) (point-max) 'face nil)) 376 | (t ; failure 377 | (setq phi-search--fail-pos (if (eq status 'err) 378 | (minibuffer-prompt-end) 379 | (or pos (point)))) 380 | (put-text-property 381 | phi-search--fail-pos (or phi-search--message-start (point-max)) 382 | 'face 'phi-search-failpart-face)))))) 383 | 384 | (defun phi-search--clear-message (&rest _) 385 | "delete message part from minibuffer." 386 | (when phi-search--message-start 387 | (let ((inhibit-modification-hooks t)) 388 | (delete-region phi-search--message-start (point-max))) 389 | (setq phi-search--message-start nil))) 390 | 391 | (defun phi-search--restore-message () 392 | "insert pending message to minibuffer" 393 | (when phi-search--pending-message 394 | (save-excursion 395 | (setq phi-search--message-start (goto-char (point-max))) 396 | (let ((inhibit-modification-hooks t)) 397 | (insert " [" phi-search--pending-message "]") 398 | (setq phi-search--pending-message nil))))) 399 | 400 | ;; + select commands 401 | 402 | (defun phi-search-again-or-next () 403 | "search again with the last query, or search next item" 404 | (interactive) 405 | (let ((str (phi-search--with-target-buffer 406 | phi-search--last-executed))) 407 | (if (not (string= (minibuffer-contents) "")) 408 | (condition-case nil 409 | (phi-search-next) 410 | ((search-failed) 411 | (if (called-interactively-p 'interactive) 412 | (message "No matches.") 413 | (error "No matches.")))) 414 | (when str (insert str))))) 415 | 416 | (defun phi-search-again-or-previous () 417 | "search again with the last query, or search previous item" 418 | (interactive) 419 | (let ((str (phi-search--with-target-buffer 420 | phi-search--last-executed))) 421 | (if (not (string= (minibuffer-contents) "")) 422 | (condition-case nil 423 | (phi-search-previous) 424 | ((search-failed) 425 | (if (called-interactively-p 'interactive) 426 | (message "No matches.") 427 | (error "No matches.")))) 428 | (when str (insert str))))) 429 | 430 | ;; + replace scroll commands 431 | 432 | (defun phi-search-recenter () 433 | "recenter target buffer" 434 | (interactive) 435 | (phi-search--with-target-buffer 436 | (when phi-search--selection 437 | (phi-search--with-sublimity 438 | (phi-search--select phi-search--selection) 439 | (recenter))))) 440 | 441 | (defun phi-search-scroll-down () 442 | "scroll down the target buffer" 443 | (interactive) 444 | (phi-search--with-target-buffer 445 | (phi-search--with-sublimity 446 | (call-interactively 'scroll-down)))) 447 | 448 | (defun phi-search-scroll-up () 449 | "scroll up the target buffer" 450 | (interactive) 451 | (phi-search--with-target-buffer 452 | (phi-search--with-sublimity 453 | (call-interactively 'scroll-up)))) 454 | 455 | ;; + other commands 456 | 457 | (defun phi-search-unlimit () 458 | "search for all occurrences, regardless of phi-search-limit" 459 | (interactive) 460 | (phi-search--with-target-buffer 461 | (phi-search--with-sublimity 462 | (phi-search--delete-overlays) 463 | (phi-search--make-overlays-for query t) 464 | (phi-search--select 0) 465 | (when phi-search--after-update-function 466 | (funcall phi-search--after-update-function))))) 467 | 468 | (declare-function subword-forward "subword" (&optional arg)) 469 | (declare-function jaword-forward "jaword" (&optional arg)) 470 | (defun phi-search-yank-word () 471 | "If there's a region in query buffer, kill-region as usual. 472 | Otherwise yank a word from target buffer and expand query." 473 | (interactive) 474 | (if (or (not (use-region-p)) 475 | (= (region-beginning) (region-end))) 476 | (insert 477 | (phi-search--with-target-buffer 478 | (buffer-substring-no-properties 479 | (point) 480 | (save-excursion 481 | (cond ((and (boundp 'jaword-mode) jaword-mode) (jaword-forward 1)) 482 | ((and (boundp 'subword-mode) subword-mode) (subword-forward 1)) 483 | (t (forward-word))) 484 | (point))))) 485 | (kill-region (region-beginning) (region-end)))) 486 | 487 | (defun phi-search-case-toggle (arg) 488 | "change case sensitivity for phi-search on-the-fly. 489 | parameter prefix arg: C-u or C-4: activate case sensitivity 490 | C-0: Case deactivate sensitivity 491 | nil: toggle value" 492 | (interactive "p") 493 | (let ((newvalue 494 | (cond ((= 4 arg) t) 495 | ((= 0 arg) nil) 496 | (t (not phi-search--case-sensitive))))) 497 | (setq phi-search--case-sensitive newvalue) 498 | (message (if newvalue 499 | "Enabled phi-search-case-sensitive." 500 | "Disabled phi-search-case-sensitive."))) 501 | (phi-search--update)) 502 | 503 | ;; + start/end phi-search 504 | 505 | (defun phi-search--initialize (modeline-fmt keybinds filter-fn update-fn 506 | complete-fn &optional conv-fn init-fn prompt) 507 | ;; 508 | ;; *FIXME* `phi-search--active' sometimes be non-nil even when 509 | ;; `active-minibuffer-window' returns `nil'. (GitHub Issue #41) 510 | ;; 511 | (if (and phi-search--active (active-minibuffer-window)) 512 | ;; if phi-search is already active, just switch to the minibuffer 513 | (select-window (active-minibuffer-window)) 514 | (let ((wnd (selected-window)) 515 | (buf (current-buffer))) 516 | (when phi-search-use-modeline 517 | (setq phi-search--saved-mode-line-format mode-line-format 518 | mode-line-format modeline-fmt)) 519 | (setq phi-search--active t 520 | phi-search--pending-message nil 521 | phi-search--original-position (point) 522 | phi-search--filter-function filter-fn 523 | phi-search--after-update-function update-fn 524 | phi-search--selection nil 525 | phi-search--overlays nil 526 | phi-search--target (cons wnd buf) 527 | phi-search--convert-query-function conv-fn 528 | phi-search--before-complete-function complete-fn 529 | phi-search--case-sensitive phi-search-case-sensitive) 530 | (minibuffer-with-setup-hook 531 | (lambda () 532 | ;; *FIXME* does wrong when a timer modifies the minibuffer 533 | ;; ('cause message is not cleared yet) 534 | (add-hook 'pre-command-hook 'phi-search--clear-message nil t) 535 | (add-hook 'after-change-functions 'phi-search--update nil t) 536 | (add-hook 'post-command-hook 'phi-search--restore-message nil t) 537 | (run-hooks 'phi-search-hook) 538 | (when init-fn (funcall init-fn))) 539 | (read-from-minibuffer 540 | (or prompt "phi-search: ") nil 541 | (let ((kmap (copy-keymap phi-search-default-map))) 542 | (dolist (bind (reverse keybinds)) 543 | (eval `(define-key kmap ,(car bind) ,(cdr bind)))) 544 | kmap)))))) 545 | 546 | (defun phi-search-complete (&rest args) 547 | "finish phi-search. (for developers: ARGS are passed to complete-function)" 548 | (interactive) 549 | (when phi-search--before-complete-function 550 | (apply phi-search--before-complete-function args)) 551 | (let ((wnd (car phi-search--target)) 552 | (str (minibuffer-contents))) 553 | (phi-search--with-target-buffer 554 | (phi-search--delete-overlays t) 555 | (phi-search--open-invisible-permanently) 556 | (when phi-search-use-modeline 557 | (setq mode-line-format phi-search--saved-mode-line-format)) 558 | (setq phi-search--active nil 559 | phi-search--last-executed str))) 560 | (exit-minibuffer) ; exit-minibuffer must be called at last 561 | ) 562 | 563 | (defun phi-search-abort () 564 | "abort phi-search" 565 | (interactive) 566 | (phi-search--with-target-buffer 567 | (phi-search--with-sublimity 568 | (phi-search--delete-overlays))) 569 | (let ((phi-search--before-complete-function nil)) 570 | (phi-search-complete))) 571 | 572 | ;; + provide 573 | 574 | (provide 'phi-search-core) 575 | 576 | ;;; phi-search-core.el ends here 577 | -------------------------------------------------------------------------------- /phi-search.el: -------------------------------------------------------------------------------- 1 | ;;; phi-search.el --- another incremental search & replace, compatible with "multiple-cursors" 2 | 3 | ;; Copyright (C) 2013- zk_phi 4 | 5 | ;; This program is free software; you can redistribute it and/or modify 6 | ;; it under the terms of the GNU General Public License as published by 7 | ;; the Free Software Foundation; either version 2 of the License, or 8 | ;; (at your option) any later version. 9 | ;; 10 | ;; This program is distributed in the hope that it will be useful, 11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ;; GNU General Public License for more details. 14 | ;; 15 | ;; You should have received a copy of the GNU General Public License 16 | ;; along with this program; if not, write to the Free Software 17 | ;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | 19 | ;; Author: zk_phi 20 | ;; URL: http://hins11.yu-yake.com/ 21 | ;; Version: 2.2.2 22 | 23 | ;;; Commentary: 24 | 25 | ;; Add following expression in your init file : 26 | ;; 27 | ;; (require 'phi-search) 28 | ;; 29 | ;; and bind command "phi-search" 30 | ;; 31 | ;; (global-set-key (kbd "C-s") 'phi-search) 32 | ;; (global-set-key (kbd "C-r") 'phi-search-backward) 33 | 34 | ;; In *phi-search* buffer, following commands are available. 35 | 36 | ;; - phi-search-again-or-next (replaces "phi-search") 37 | ;; 38 | ;; Move to the next matching item. If query is blank, use the last 39 | ;; query. 40 | 41 | ;; - phi-search-again-or-previous (replaces "phi-search-backward") 42 | ;; 43 | ;; Similar to phi-search-again-or-next, but move to the previous item. 44 | 45 | ;; - [M-v] phi-search-scroll-up 46 | ;; 47 | ;; Scroll the target window up, to check candidates. 48 | 49 | ;; - [C-v] phi-search-scroll-down 50 | ;; 51 | ;; Scroll the target window down. 52 | 53 | ;; - [C-l] phi-search-recenter 54 | ;; 55 | ;; Recenter the target window. 56 | 57 | ;; - [C-w] phi-search-yank-word 58 | ;; 59 | ;; Expand query by yanking one word from the target buffer. 60 | 61 | ;; - [RET] phi-search-complete 62 | ;; 63 | ;; Finish searching. 64 | 65 | ;; - [C-RET] phi-search-complete-at-beginning 66 | ;; 67 | ;; Finish searching at the beginning of the match. 68 | 69 | ;; - [C-c C-c] phi-search-unlimit 70 | ;; 71 | ;; Force update results regardless of "phi-search-limit" 72 | 73 | ;; - [C-g] phi-search-abort 74 | ;; 75 | ;; Finish searching, and move back to the original position. 76 | 77 | ;; For more details, see "Readme". 78 | 79 | ;;; Change Log: 80 | 81 | ;; 1.0.0 first released 82 | ;; 1.0.1 working better with regions 83 | ;; added phi-search-complete-and-xxxx commands 84 | ;; 1.0.2 fixed phi-search-complete-and-xxxx commands 85 | ;; better compatibility for multiple-cursors 86 | ;; 1.0.3 fixed bug that nurumacs does not work while inserting query 87 | ;; changed mode-line-format 88 | ;; renamed some private functions 89 | ;; 1.1.0 cleaned-up 90 | ;; removed "phi-search-keybindings" and added "phi-search-mode-map" 91 | ;; now calls "isearch" if the window is popwin window 92 | ;; 1.1.1 use "sublimity" not "nurumacs" 93 | ;; 1.1.2 added phi-search-backward command 94 | ;; 1.1.3 better integration with sublimity 95 | ;; 1.1.4 fixed a bug in adjacent matches 96 | ;; 1.1.5 added a hook 97 | ;; 1.1.6 added an option phi-search-case-sensitive 98 | ;; 1.1.7 added phi-search-recenter, phi-search-yank-word 99 | ;; 1.1.8 added phi-search-scroll-up/down 100 | ;; 1.1.9 improved fallback behavior when called with region 101 | ;; fixed bug on invoking multiple-cursors just after phi-search 102 | ;; 1.2.0 added command "phi-search-complete-at-beginning" 103 | ;; 1.2.1 use "remap" for default keybindings 104 | ;; 2.0.0 divided into two files ("phi-search-core.el") 105 | ;; added "phi-search-unlimit" command 106 | ;; 2.0.1 added phi-search-init-hook 107 | ;; accept prefix-argument 108 | ;; 2.1.0 handle "isearch-open-invisible" properties 109 | ;; 2.1.1 compatible with phi-search-core v1.2.0 110 | ;; 2.2.0 compatibility with phi-search-core v2.0.0 111 | ;; 2.2.1 call phi-search even in a popup window 112 | ;; 2.2.2 prefer direct keymapping to remapping 113 | 114 | ;;; Code: 115 | 116 | (require 'phi-search-core) 117 | 118 | ;; + constants 119 | 120 | (defconst phi-search-version "2.2.2") 121 | 122 | ;; + suppress byte-compiler 123 | 124 | (defvar mc--this-command) 125 | 126 | ;; + customs 127 | 128 | (defcustom phi-search-init-hook nil 129 | "hook run after initialization of phi-search" 130 | :group 'phi-search 131 | :type '(repeat function)) 132 | 133 | (defcustom phi-search-additional-keybinds 134 | '(((kbd "C-n") . 'phi-search-maybe-next-line) 135 | ((kbd "C-p") . 'phi-search-maybe-previous-line) 136 | ((kbd "C-f") . 'phi-search-maybe-forward-char) 137 | ((kbd "C-") . 'phi-search-complete-at-beginning)) 138 | "additional bindings used in phi-search" 139 | :group 'phi-search 140 | :type 'list) 141 | 142 | (defcustom phi-search-mode-line-format 143 | '(" *phi-search*" 144 | (:eval (let ((total (length phi-search--overlays)) 145 | (selection phi-search--selection)) 146 | (when selection 147 | (format " [ %d / %d ]" (1+ selection) total))))) 148 | "mode-line-format for phi-search(-backward)" 149 | :group 'phi-search 150 | :type 'list) 151 | 152 | ;; + variables 153 | 154 | (defvar phi-search--original-region nil 155 | "stores region substring this search started with.") 156 | (make-variable-buffer-local 'phi-search--original-region) 157 | 158 | ;; + generate repeatable commands 159 | 160 | (defvar phi-search--region-query nil 161 | "query for a generated command, must be cursor-local") 162 | (eval-after-load 'multiple-cursors 163 | '(add-to-list 'mc/cursor-specific-vars 'phi-search--region-query)) 164 | 165 | (defun phi-search--generate-command (query n &optional filter cmd use-region) 166 | (let* ((pre-process 167 | (if use-region 168 | '(progn (setq phi-search--region-query 169 | (buffer-substring (region-beginning) (region-end))) 170 | (deactivate-mark)) 171 | nil)) 172 | (query 173 | (if use-region 'phi-search--region-query query)) 174 | (post-process 175 | (if cmd `(call-interactively (quote ,cmd))))) 176 | `(lambda () 177 | (interactive) 178 | ,pre-process 179 | (dotimes (n ,(1+ n)) 180 | (unless (phi-search--search-forward ,query nil ,filter (zerop n)) 181 | (goto-char (point-min)) 182 | (phi-search--search-forward ,query nil ,filter t))) 183 | (phi-search--open-invisible-permanently) 184 | ,post-process))) 185 | 186 | ;; + start/end phi-search 187 | 188 | (defun phi-search--backward-after-update-function () 189 | (when phi-search--selection 190 | (or (phi-search--select (1- phi-search--selection)) 191 | (phi-search--select (1- (length phi-search--overlays)))))) 192 | 193 | (defun phi-search--complete-function (&optional cmd) 194 | (phi-search--with-target-buffer 195 | ;; generate a repeatable command for multiple-cursors 196 | (let ((command 197 | (cond ((null phi-search--selection) 198 | (lambda () nil)) 199 | ((and phi-search--original-region 200 | (string= query phi-search--original-region)) 201 | (phi-search--generate-command 202 | nil phi-search--selection nil cmd 'use-region)) 203 | (t 204 | (phi-search--generate-command 205 | query phi-search--selection nil cmd))))) 206 | (setq this-command command 207 | this-original-command command) 208 | (when (and (boundp 'multiple-cursors-mode) multiple-cursors-mode) 209 | (setq mc--this-command command))) 210 | ;; move cursor back to the selection 211 | (when phi-search--selection 212 | (phi-search--select phi-search--selection)) 213 | ;; run command 214 | (when cmd (call-interactively cmd)) 215 | ;; push mark 216 | (unless (or (= (point) phi-search--original-position) 217 | (use-region-p)) 218 | (push-mark phi-search--original-position t) 219 | (unless (or executing-kbd-macro 220 | (> (minibuffer-depth) 0)) 221 | (message "Mark saved where search started"))) 222 | ;; clean-up variable 223 | (setq phi-search--original-region nil))) 224 | 225 | (defun phi-search--search-initialize (&optional backward) 226 | (when (and (use-region-p) 227 | (not (= (region-beginning) (region-end)))) 228 | (setq phi-search--original-region 229 | (buffer-substring (region-beginning) (region-end))) 230 | (deactivate-mark)) 231 | (let ((str phi-search--original-region)) 232 | (phi-search--initialize 233 | phi-search-mode-line-format 234 | (if backward 235 | phi-search-additional-keybinds 236 | phi-search-additional-keybinds) 237 | nil 238 | (when backward 'phi-search--backward-after-update-function) 239 | 'phi-search--complete-function 240 | nil 241 | (lambda () 242 | (when str (insert str)) 243 | (run-hooks 'phi-search-init-hook))))) 244 | 245 | ;; + commands 246 | 247 | ;;;###autoload 248 | (defun phi-search (&optional use-isearch) 249 | "incremental search command compatible with \"multiple-cursors\"" 250 | (interactive "P") 251 | (if (not use-isearch) 252 | (phi-search--search-initialize nil) 253 | (call-interactively 'isearch-forward-regexp) 254 | (when (use-region-p) 255 | (let ((string (buffer-substring (region-beginning) (region-end)))) 256 | (deactivate-mark) 257 | (isearch-yank-string string))))) 258 | 259 | ;;;###autoload 260 | (defun phi-search-backward (&optional use-isearch) 261 | "incremental search command compatible with \"multiple-cursors\"" 262 | (interactive "P") 263 | (if (not use-isearch) 264 | (phi-search--search-initialize t) 265 | (call-interactively 'isearch-backward-regexp) 266 | (when (use-region-p) 267 | (let ((string (buffer-substring (region-beginning) (region-end)))) 268 | (deactivate-mark) 269 | (isearch-yank-string string))))) 270 | 271 | (defun phi-search-complete-at-beginning () 272 | (interactive) 273 | (let ((query (phi-search--with-target-buffer query))) 274 | (phi-search-complete 275 | `(lambda () 276 | (interactive) 277 | (when (looking-back ,query) 278 | (goto-char (match-beginning 0))))))) 279 | 280 | (defun phi-search-maybe-next-line () 281 | "quit phi-search with next-line" 282 | (interactive) 283 | (condition-case err 284 | (call-interactively 'next-line) 285 | (error 286 | (phi-search-complete 'next-line)))) 287 | 288 | (defun phi-search-maybe-previous-line () 289 | "quit phi-search with previous-line" 290 | (interactive) 291 | (condition-case err 292 | (call-interactively 'previous-line) 293 | (error 294 | (phi-search-complete 'previous-line)))) 295 | 296 | (defun phi-search-maybe-forward-char () 297 | "quit phi-search with forward-char" 298 | (interactive) 299 | (condition-case err 300 | (call-interactively 'forward-char) 301 | (error 302 | (phi-search-complete 'forward-char)))) 303 | 304 | ;; + provide 305 | 306 | (provide 'phi-search) 307 | 308 | ;;; phi-search.el ends here 309 | --------------------------------------------------------------------------------