├── README.md ├── wgrep-test.el └── wgrep.el /README.md: -------------------------------------------------------------------------------- 1 | # wgrep.el 2 | 3 | wgrep allows you to edit a grep buffer and apply those changes to 4 | the file buffer. 5 | 6 | ## Install: 7 | 8 | Put this file into load-path'ed directory, and byte compile it if 9 | desired. And put the following expression into your ~/.emacs. 10 | 11 | (require 'wgrep) 12 | 13 | ## Usage: 14 | 15 | You can edit the text in the *grep* buffer after typing `C-c C-p`. 16 | After that the changed text is highlighted. 17 | The following keybindings are defined: 18 | 19 | * `C-c C-e`: Apply the changes to file buffers. 20 | * `C-c C-u`: All changes are unmarked and ignored. 21 | * `C-c C-d`: Delete current line (including newline). This is immediately reflected in the file's buffer. 22 | * `C-c C-r`: Remove the changes in the region (these changes are not applied to the files. Of course, the remaining changes can still be applied to the files.) 23 | * `C-c C-p`: Toggle read-only area. 24 | * `C-c C-k`: Discard all changes and exit. 25 | * `C-x C-q`: Exit wgrep mode. 26 | 27 | To save all buffers that wgrep has changed, run 28 | 29 | M-x wgrep-save-all-buffers 30 | 31 | You can change the default key binding to switch to wgrep. 32 | 33 | (setq wgrep-enable-key "r") 34 | 35 | To apply all changes wheather or not buffer is read-only. 36 | 37 | (setq wgrep-change-readonly-file t) 38 | 39 | ## History: 40 | 41 | This program is a forked version. the original version can be downloaded from 42 | http://www.bookshelf.jp/elc/grep-edit.el 43 | 44 | Following added implementations and differences. 45 | 46 | * Support GNU grep context option -A -B and -C 47 | * Some bugfix. (wrong coloring text etc..) 48 | * wdired.el like interface. 49 | * Remove all advice. 50 | * Bind to local variables. (grep-a-lot.el works well) 51 | * After save buffer, colored face will be removed. 52 | * Change face easy to see. 53 | * Reinforce checking error. 54 | * Support removing whole line include new-line. 55 | -------------------------------------------------------------------------------- /wgrep-test.el: -------------------------------------------------------------------------------- 1 | (require 'ert) 2 | 3 | (defun wgrep-test--grep (command) 4 | (let* ((buf (grep command)) 5 | (proc (get-buffer-process buf))) 6 | (while (eq (process-status proc) 'run) 7 | (sit-for 0.1)) 8 | (switch-to-buffer buf))) 9 | 10 | (defun wgrep-test--contents (file &optional cs) 11 | (let ((coding-system-for-read cs)) 12 | (with-temp-buffer 13 | (insert-file-contents file) 14 | (buffer-string)))) 15 | 16 | (defun wgrep-test--file (file contents &optional cs) 17 | ;; cleanup for convinience 18 | (let ((buf (get-file-buffer file))) 19 | (kill-buffer buf)) 20 | (let ((coding-system-for-write cs)) 21 | (write-region contents nil file))) 22 | 23 | (ert-deftest wgrep-normal () 24 | :tags '(wgrep) 25 | (wgrep-test--file "test-data.txt" "HOGE\nFOO\nBAZ\n") 26 | (wgrep-test--grep "grep -nH -e FOO -C 1 test-data.txt") 27 | (wgrep-change-to-wgrep-mode) 28 | (goto-char (point-min)) 29 | ;; header is readonly 30 | (should (re-search-forward "^grep" nil t)) 31 | (should-error (delete-char 1) :type 'text-read-only) 32 | ;; search hit line (hit by -C option) 33 | (should (re-search-forward "HOGE" nil t)) 34 | ;; delete 1st line 35 | (wgrep-flush-current-line) 36 | (should (re-search-forward "FOO" nil t)) 37 | ;; replace 2nd line 38 | (replace-match "FOO2") 39 | ;; footer is readonly 40 | (goto-char (point-max)) 41 | (should-error (delete-char -1) :type 'text-read-only) 42 | ;; apply to buffer 43 | (wgrep-finish-edit) 44 | ;; save to file 45 | (wgrep-save-all-buffers) 46 | ;; compare file contents is valid 47 | (should (equal "FOO2\nBAZ\n" (wgrep-test--contents "test-data.txt"))) 48 | (delete-file "test-data.txt")) 49 | 50 | (ert-deftest wgrep-bom-with-multibyte () 51 | :tags '(wgrep) 52 | (wgrep-test--file "test-data.txt" "あ\nい\nう\n" 'utf-8-with-signature) 53 | (wgrep-test--grep "grep -nH -e 'あ' -A 2 test-data.txt") 54 | (wgrep-change-to-wgrep-mode) 55 | (goto-char (point-min)) 56 | ;; BOM check is valid. 57 | (should (re-search-forward "test-data\\.txt:[0-9]+:.*\\(あ\\)$" nil t)) 58 | (replace-match "へのへのも" nil nil nil 1) 59 | ;; 2nd line 60 | (should (re-search-forward "test-data\\.txt:[0-9]+:.*\\(い\\)$" nil t)) 61 | (replace-match "へじ" nil nil nil 1) 62 | ;; apply to buffer 63 | (wgrep-finish-edit) 64 | ;; save to file 65 | (wgrep-save-all-buffers) 66 | ;; compare file contents is valid 67 | (should (equal "へのへのも\nへじ\nう\n" (wgrep-test--contents "test-data.txt"))) 68 | (delete-file "test-data.txt")) 69 | 70 | (ert-deftest wgrep-bom-with-unibyte () 71 | :tags '(wgrep) 72 | (wgrep-test--file "test-data.txt" "a\nb\n" 'utf-8-with-signature) 73 | (wgrep-test--grep "grep -nH -e 'a' -A 2 test-data.txt") 74 | (wgrep-change-to-wgrep-mode) 75 | (goto-char (point-min)) 76 | ;; BOM check is valid. 77 | (should (re-search-forward "test-data\\.txt:[0-9]+:.*\\(a\\)$" nil t)) 78 | (replace-match "ABCD" nil nil nil 1) 79 | ;; apply to buffer 80 | (wgrep-finish-edit) 81 | ;; save to file 82 | (wgrep-save-all-buffers) 83 | ;; compare file contents is valid 84 | (should (equal "ABCD\nb\n" (wgrep-test--contents "test-data.txt"))) 85 | (delete-file "test-data.txt")) 86 | 87 | (ert-deftest wgrep-with-modify () 88 | :tags '(wgrep) 89 | (wgrep-test--file "test-data.txt" "a\nb\nc\n") 90 | (with-current-buffer (find-file-noselect "test-data.txt") 91 | ;; modify file buffer 92 | (goto-char (point-min)) 93 | (and (re-search-forward "^a" nil t) 94 | (replace-match "hoge")) 95 | (and (re-search-forward "^b" nil t) 96 | (replace-match "foo"))) 97 | (wgrep-test--grep "grep -nH -e 'a' -A 2 test-data.txt") 98 | (wgrep-change-to-wgrep-mode) 99 | (goto-char (point-min)) 100 | (should (re-search-forward "test-data\\.txt:[0-9]+:.*\\(a\\)$" nil t)) 101 | (wgrep-flush-current-line) 102 | (should (re-search-forward "test-data\\.txt:[0-9]+:.*\\(b\\)$" nil t)) 103 | (replace-match "B" nil nil nil 1) 104 | (should (re-search-forward "test-data\\.txt:[0-9]+:.*\\(c\\)$" nil t)) 105 | (replace-match "C" nil nil nil 1) 106 | ;; apply to buffer 107 | (wgrep-finish-edit) 108 | ;; save to file 109 | (wgrep-save-all-buffers) 110 | ;; compare file contents is valid 111 | (should (equal "hoge\nfoo\nC\n" (wgrep-test--contents "test-data.txt"))) 112 | (delete-file "test-data.txt")) 113 | 114 | ;; TODO 115 | ;; * wgrep-toggle-readonly-area 116 | ;; * wgrep-abort-changes 117 | ;; * wgrep-exit 118 | ;; * broken file contents (invalid coding system) 119 | -------------------------------------------------------------------------------- /wgrep.el: -------------------------------------------------------------------------------- 1 | ;;; wgrep.el --- Writable grep buffer and apply the changes to files 2 | 3 | ;; Author: Masahiro Hayashi 4 | ;; Keywords: grep edit extensions 5 | ;; URL: http://github.com/mhayashi1120/Emacs-wgrep/raw/master/wgrep.el 6 | ;; Emacs: GNU Emacs 22 or later 7 | ;; Version: 1.0.5 8 | 9 | ;; This program is free software; you can redistribute it and/or 10 | ;; modify it under the terms of the GNU General Public License as 11 | ;; published by the Free Software Foundation; either version 3, or (at 12 | ;; your option) any later version. 13 | 14 | ;; This program is distributed in the hope that it will be useful, but 15 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | ;; General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with GNU Emacs; see the file COPYING. If not, write to the 21 | ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 | ;; Boston, MA 02110-1301, USA. 23 | 24 | ;;; Commentary: 25 | 26 | ;; wgrep allows you to edit a grep buffer and apply those changes to 27 | ;; the file buffer. 28 | 29 | ;;; Install: 30 | 31 | ;; Put this file into load-path'ed directory, and byte compile it if 32 | ;; desired. And put the following expression into your ~/.emacs. 33 | ;; 34 | ;; (require 'wgrep) 35 | 36 | ;;; Usage: 37 | 38 | ;; You can edit the text in the *grep* buffer after typing C-c C-p. 39 | ;; After that the changed text is highlighted. 40 | ;; The following keybindings are defined: 41 | 42 | ;; C-c C-e : Apply the changes to file buffers. 43 | ;; C-c C-u : All changes are unmarked and ignored. 44 | ;; C-c C-d : Delete current line (including newline). 45 | ;; This is immediately reflected in the file's buffer. 46 | ;; C-c C-r : Remove the changes in the region (these changes are not 47 | ;; applied to the files. Of course, the remaining 48 | ;; changes can still be applied to the files.) 49 | ;; C-c C-p : Toggle read-only area. 50 | ;; C-c C-k : Discard all changes and exit. 51 | ;; C-x C-q : Exit wgrep mode. 52 | 53 | ;; * To save all buffers that wgrep has changed, run 54 | ;; 55 | ;; M-x wgrep-save-all-buffers 56 | 57 | ;; * You can change the default key binding to switch to wgrep. 58 | ;; 59 | ;; (setq wgrep-enable-key "r") 60 | 61 | ;; * To apply all changes wheather or not buffer is read-only. 62 | ;; 63 | ;; (setq wgrep-change-readonly-file t) 64 | 65 | ;;; History: 66 | 67 | ;; This program is a forked version. the original version can be downloaded from 68 | ;; http://www.bookshelf.jp/elc/grep-edit.el 69 | 70 | ;; Following added implementations and differences. 71 | ;; * Support GNU grep context option -A -B and -C 72 | ;; * Some bugfix. (wrong coloring text etc..) 73 | ;; * wdired.el like interface. 74 | ;; * Remove all advice. 75 | ;; * Bind to local variables. (grep-a-lot.el works well) 76 | ;; * After save buffer, colored face will be removed. 77 | ;; * Change face easy to see. 78 | ;; * Reinforce checking error. 79 | ;; * Support removing whole line include new-line. 80 | 81 | ;;; Code: 82 | 83 | (require 'grep) 84 | 85 | (declare-function image-get-display-property "image-mode.el" ()) 86 | (declare-function image-mode-as-text "image-mode.el" ()) 87 | 88 | (defgroup wgrep nil 89 | "Customize wgrep" 90 | :group 'grep) 91 | 92 | (defcustom wgrep-change-readonly-file nil 93 | "*Non-nil means to enable change read-only files." 94 | :group 'wgrep 95 | :type 'boolean) 96 | 97 | (defcustom wgrep-enable-key "\C-c\C-p" 98 | "*Key to enable `wgrep-mode'." 99 | :type 'string 100 | :group 'wgrep) 101 | 102 | (defvar wgrep-setup-hook nil 103 | "Hooks to run when setting up wgrep.") 104 | 105 | (defface wgrep-face 106 | '((((class color) 107 | (background dark)) 108 | (:background "SlateGray1" :weight bold :foreground "Black")) 109 | (((class color) 110 | (background light)) 111 | (:background "ForestGreen" :weight bold :foreground "white")) 112 | (t 113 | ())) 114 | "*Face used for the changed text in the grep buffer." 115 | :group 'wgrep) 116 | 117 | (defface wgrep-file-face 118 | '((((class color) 119 | (background dark)) 120 | (:background "gray30" :weight bold :foreground "white")) 121 | (((class color) 122 | (background light)) 123 | (:background "ForestGreen" :weight bold :foreground "white")) 124 | (t 125 | ())) 126 | "*Face used for the changed text in the file buffer." 127 | :group 'wgrep) 128 | 129 | (defface wgrep-reject-face 130 | '((((class color) 131 | (background dark)) 132 | (:foreground "hot pink" :weight bold)) 133 | (((class color) 134 | (background light)) 135 | (:foreground "red" :weight bold)) 136 | (t 137 | ())) 138 | "*Face used for the line in the grep buffer that can not be applied to a file." 139 | :group 'wgrep) 140 | 141 | (defface wgrep-done-face 142 | '((((class color) 143 | (background dark)) 144 | (:foreground "LightSkyBlue" :weight bold)) 145 | (((class color) 146 | (background light)) 147 | (:foreground "blue" :weight bold)) 148 | (t 149 | ())) 150 | "*Face used for the line in the grep buffer that can be applied to a file." 151 | :group 'wgrep) 152 | 153 | (defvar wgrep-overlays nil) 154 | (make-variable-buffer-local 'wgrep-overlays) 155 | 156 | (defvar wgrep-file-overlays nil) 157 | (make-variable-buffer-local 'wgrep-file-overlays) 158 | 159 | (defvar wgrep-readonly-state nil) 160 | (make-variable-buffer-local 'wgrep-readonly-state) 161 | 162 | (defvar wgrep-each-other-buffer nil) 163 | (make-variable-buffer-local 'wgrep-each-other-buffer) 164 | 165 | ;; Suppress elint warning 166 | ;; GNU Emacs have this variable at least version 21 or later 167 | (defvar auto-coding-regexp-alist) 168 | 169 | (defconst wgrep-line-file-regexp (caar grep-regexp-alist)) 170 | 171 | (defvar wgrep-mode-map nil) 172 | (unless wgrep-mode-map 173 | (setq wgrep-mode-map 174 | (let ((map (make-sparse-keymap))) 175 | 176 | (define-key map "\C-c\C-c" 'wgrep-finish-edit) 177 | (define-key map "\C-c\C-d" 'wgrep-flush-current-line) 178 | (define-key map "\C-c\C-e" 'wgrep-finish-edit) 179 | (define-key map "\C-c\C-p" 'wgrep-toggle-readonly-area) 180 | (define-key map "\C-c\C-r" 'wgrep-remove-change) 181 | (define-key map "\C-x\C-s" 'wgrep-finish-edit) 182 | (define-key map "\C-c\C-u" 'wgrep-remove-all-change) 183 | (define-key map "\C-c\C-[" 'wgrep-remove-all-change) 184 | (define-key map "\C-c\C-k" 'wgrep-abort-changes) 185 | (define-key map "\C-x\C-q" 'wgrep-exit) 186 | (define-key map "\C-m" 'ignore) 187 | (define-key map "\C-j" 'ignore) 188 | (define-key map "\C-o" 'ignore) 189 | 190 | map))) 191 | 192 | ;;;###autoload 193 | (defun wgrep-setup () 194 | "Setup wgrep preparation." 195 | (define-key grep-mode-map wgrep-enable-key 'wgrep-change-to-wgrep-mode) 196 | ;; delete previous wgrep overlays 197 | (wgrep-cleanup-overlays (point-min) (point-max)) 198 | (remove-hook 'post-command-hook 'wgrep-maybe-echo-error-at-point t) 199 | (run-hooks 'wgrep-setup-hook)) 200 | 201 | (defun wgrep-maybe-echo-error-at-point () 202 | (when (null (current-message)) 203 | (let ((o (wgrep-find-if 204 | (lambda (o) 205 | (overlay-get o 'wgrep-reject-message)) 206 | (overlays-in (line-beginning-position) (line-end-position))))) 207 | (when o 208 | (let (message-log-max) 209 | (message "%s" (overlay-get o 'wgrep-reject-message))))))) 210 | 211 | (defun wgrep-set-readonly-area (state) 212 | (let ((inhibit-read-only t) 213 | (regexp (format "\\(?:%s\\|\n\\)" wgrep-line-file-regexp)) 214 | after-change-functions) 215 | (save-excursion 216 | (wgrep-goto-first-found) 217 | (while (re-search-forward regexp nil t) 218 | (wgrep-set-readonly-property 219 | (match-beginning 0) (match-end 0) state))) 220 | (setq wgrep-readonly-state state))) 221 | 222 | (defun wgrep-after-change-function (beg end leng-before) 223 | (cond 224 | ((= (point-min) (point-max)) 225 | ;; cleanup when first executing 226 | (wgrep-cleanup-overlays (point-min) (point-max))) 227 | (t 228 | (wgrep-put-change-face beg end)))) 229 | 230 | (defun wgrep-get-line-info (&optional flush) 231 | (forward-line 0) 232 | (when (looking-at (concat wgrep-line-file-regexp "\\([^\n]*$\\)")) 233 | (let ((name (match-string-no-properties 1)) 234 | (line (match-string-no-properties 3)) 235 | (text (and (not flush) (match-string-no-properties 4))) 236 | (start (match-beginning 4)) 237 | ov) 238 | (setq ov 239 | (or 240 | ;; get existing overlay 241 | (wgrep-find-if 242 | (lambda (o) 243 | (memq (overlay-get o 'face) '(wgrep-reject-face wgrep-done-face))) 244 | (overlays-in start (line-end-position))) 245 | (wgrep-make-overlay start (line-end-position)))) 246 | (list (expand-file-name name default-directory) 247 | (string-to-number line) 248 | text 249 | ov)))) 250 | 251 | (put 'wgrep-error 'error-conditions '(wgrep-error error)) 252 | (put 'wgrep-error 'error-message "Error while applying changes.") 253 | 254 | (defun wgrep-get-file-buffer (file) 255 | (unless (file-exists-p file) 256 | (signal 'wgrep-error "File does not exist.")) 257 | (unless (file-writable-p file) 258 | (signal 'wgrep-error "File is not writable.")) 259 | (or (get-file-buffer file) 260 | (find-file-noselect file))) 261 | 262 | (defun wgrep-check-buffer () 263 | "Check the file's status. If it is possible to change the file, return t" 264 | (when (and (not wgrep-change-readonly-file) 265 | buffer-read-only) 266 | (signal 'wgrep-error (format "Buffer \"%s\" is read-only." (buffer-name))))) 267 | 268 | (defun wgrep-display-physical-data () 269 | (cond 270 | ;; `funcall' is a trick to suppress the elint warnings. 271 | ((derived-mode-p 'image-mode) 272 | ;; toggle to raw data if buffer has image. 273 | (when (image-get-display-property) 274 | (image-mode-as-text))) 275 | (t nil))) 276 | 277 | ;; not consider other edit. (ex: Undo or self-insert-command) 278 | (defun wgrep-after-save-hook () 279 | (remove-hook 'after-save-hook 'wgrep-after-save-hook t) 280 | (mapc 281 | (lambda (ov) 282 | (delete-overlay ov)) 283 | wgrep-file-overlays) 284 | (kill-local-variable 'wgrep-file-overlays)) 285 | 286 | (defun wgrep-apply-to-buffer (buffer info old-text) 287 | "*The changes in the grep buffer are applied to the file" 288 | (with-current-buffer buffer 289 | (let ((line (nth 1 info)) 290 | (new-text (nth 2 info)) 291 | (result (nth 3 info)) 292 | (inhibit-read-only wgrep-change-readonly-file)) 293 | (wgrep-check-buffer) 294 | (wgrep-display-physical-data) 295 | (save-restriction 296 | (widen) 297 | (wgrep-goto-line line) 298 | ;;FIXME simply do this? 299 | (when (and (= line 1) 300 | buffer-file-coding-system 301 | (coding-system-get buffer-file-coding-system :bom)) 302 | (setq old-text (wgrep-string-replace-bom old-text buffer-file-coding-system)) 303 | (when new-text 304 | (setq new-text (wgrep-string-replace-bom new-text buffer-file-coding-system)))) 305 | (unless (string= old-text 306 | (buffer-substring (line-beginning-position) (line-end-position))) 307 | (signal 'wgrep-error "Buffer was changed after grep.")) 308 | (cond 309 | (new-text 310 | (wgrep-replace-to-new-line new-text)) 311 | (t 312 | ;; new-text nil means flush whole line. 313 | (wgrep-flush-pop-deleting-line))))))) 314 | 315 | (defun wgrep-replace-to-new-line (new-text) 316 | (delete-region (line-beginning-position) (line-end-position)) 317 | (insert new-text) 318 | ;; hilight the changed line 319 | (wgrep-put-color-file)) 320 | 321 | ;;Hack function 322 | (defun wgrep-string-replace-bom (string cs) 323 | (let ((regexp (car (rassq (coding-system-base cs) auto-coding-regexp-alist))) 324 | ;; FIXME: `find-operation-coding-system' is not exactly correct. 325 | ;; However almost case is ok like this bom function. 326 | ;; ex: (let ((default-process-coding-system 'some-coding)) 327 | ;; (call-interactively 'grep)) 328 | (grep-cs (or (find-operation-coding-system 'call-process grep-program) 329 | (terminal-coding-system))) 330 | str) 331 | (if (and regexp 332 | (setq str (encode-coding-string string grep-cs)) 333 | (string-match regexp str)) 334 | (decode-coding-string (substring str (match-end 0)) cs) 335 | string))) 336 | 337 | (defun wgrep-put-color-file () 338 | "*Highlight the changes in the file" 339 | (let ((ov (wgrep-make-overlay 340 | (line-beginning-position) 341 | (line-end-position)))) 342 | (overlay-put ov 'face 'wgrep-file-face) 343 | (overlay-put ov 'priority 0) 344 | (add-hook 'after-save-hook 'wgrep-after-save-hook nil t) 345 | (setq wgrep-file-overlays (cons ov wgrep-file-overlays)))) 346 | 347 | (defun wgrep-put-done-face (ov) 348 | (wgrep-set-face ov 'wgrep-done-face)) 349 | 350 | (defun wgrep-put-reject-face (ov message) 351 | (wgrep-set-face ov 'wgrep-reject-face message)) 352 | 353 | (defun wgrep-set-face (ov face &optional message) 354 | (overlay-put ov 'face face) 355 | (overlay-put ov 'priority 1) 356 | (overlay-put ov 'wgrep-reject-message message)) 357 | 358 | (defun wgrep-put-change-face (beg end) 359 | (save-excursion 360 | ;; looking-at destroy replace regexp.. 361 | (save-match-data 362 | (forward-line 0) 363 | (let ((inhibit-it nil) 364 | header value origin ovs ov) 365 | (when (looking-at wgrep-line-file-regexp) 366 | ;; check file name point or not 367 | (setq inhibit-it (> (match-end 0) beg)) 368 | (setq header (match-string-no-properties 0)) 369 | (setq value (buffer-substring-no-properties 370 | (match-end 0) (line-end-position))) 371 | (unless inhibit-it 372 | (setq ovs (overlays-in (line-beginning-position) (line-end-position))) 373 | (while ovs 374 | (when (overlay-get (car ovs) 'wgrep-changed) 375 | (when (string= (overlay-get (car ovs) 'wgrep-original-value) value) 376 | (setq wgrep-overlays (remove (car ovs) wgrep-overlays)) 377 | (delete-overlay (car ovs))) 378 | (setq inhibit-it t)) 379 | (setq ovs (cdr ovs)))) 380 | (unless inhibit-it 381 | (setq origin (wgrep-get-original-value header)) 382 | (setq ov (wgrep-make-overlay 383 | (line-beginning-position) 384 | (line-end-position))) 385 | (overlay-put ov 'wgrep-changed t) 386 | (overlay-put ov 'face 'wgrep-face) 387 | (overlay-put ov 'priority 0) 388 | (overlay-put ov 'wgrep-original-value origin) 389 | (setq wgrep-overlays (cons ov wgrep-overlays)))))))) 390 | 391 | (defun wgrep-to-grep-mode () 392 | (kill-local-variable 'query-replace-skip-read-only) 393 | (remove-hook 'after-change-functions 'wgrep-after-change-function t) 394 | ;; do not remove `wgrep-maybe-echo-error-at-point' that display errors at point 395 | (use-local-map grep-mode-map) 396 | (set-buffer-modified-p nil) 397 | (setq buffer-undo-list nil) 398 | (setq buffer-read-only t)) 399 | 400 | (defun wgrep-changed-overlay-action (ov) 401 | (let (info) 402 | (if (eq (overlay-start ov) (overlay-end ov)) 403 | ;; ignore removed line or removed overlay 404 | t 405 | (goto-char (overlay-start ov)) 406 | (cond 407 | ((null (setq info (wgrep-get-line-info))) 408 | ;; ignore non grep result line. 409 | t) 410 | (t 411 | (let ((file (nth 0 info)) 412 | (result-ov (nth 3 info))) 413 | (condition-case err 414 | (progn 415 | (wgrep-apply-to-buffer (wgrep-get-file-buffer file) info 416 | (overlay-get ov 'wgrep-original-value)) 417 | (wgrep-put-done-face result-ov) 418 | t) 419 | (wgrep-error 420 | (wgrep-put-reject-face result-ov (cdr err)) 421 | nil) 422 | (error 423 | (wgrep-put-reject-face result-ov (prin1-to-string err)) 424 | nil)))))))) 425 | 426 | (defun wgrep-finish-edit () 427 | "Apply changes to file buffers." 428 | (interactive) 429 | (let ((count 0)) 430 | (save-excursion 431 | (let ((not-yet (copy-sequence wgrep-overlays))) 432 | (while wgrep-overlays 433 | (let ((ov (car wgrep-overlays))) 434 | (setq wgrep-overlays (cdr wgrep-overlays)) 435 | (when (wgrep-changed-overlay-action ov) 436 | (delete-overlay ov) 437 | (setq not-yet (delq ov not-yet)) 438 | (setq count (1+ count))))) 439 | ;; restore overlays 440 | (setq wgrep-overlays not-yet))) 441 | (wgrep-cleanup-temp-buffer) 442 | (wgrep-to-grep-mode) 443 | (let ((msg (format "(%d changed)" count))) 444 | (cond 445 | ((null wgrep-overlays) 446 | (if (= count 0) 447 | (message "(No changes to be performed)") 448 | (message "Successfully finished. %s" msg))) 449 | ((= (length wgrep-overlays) 1) 450 | (message "There is an unapplied change. %s" msg)) 451 | (t 452 | (message "There are %d unapplied changes. %s" 453 | (length wgrep-overlays) msg)))))) 454 | 455 | (defun wgrep-exit () 456 | "Return to `grep-mode'" 457 | (interactive) 458 | (if (and (buffer-modified-p) 459 | (y-or-n-p (format "Buffer %s modified; save changes? " 460 | (current-buffer)))) 461 | (wgrep-finish-edit) 462 | (wgrep-abort-changes))) 463 | 464 | (defun wgrep-abort-changes () 465 | "Discard all changes and return to `grep-mode'" 466 | (interactive) 467 | (wgrep-cleanup-overlays (point-min) (point-max)) 468 | (wgrep-restore-from-temp-buffer) 469 | (wgrep-to-grep-mode) 470 | (message "Changes discarded")) 471 | 472 | (defun wgrep-remove-change (beg end) 473 | "Remove changes in the region between BEG and END." 474 | (interactive "r") 475 | (wgrep-cleanup-overlays beg end) 476 | (setq mark-active nil)) 477 | 478 | (defun wgrep-remove-all-change () 479 | "Remove changes in the whole buffer." 480 | (interactive) 481 | (wgrep-cleanup-overlays (point-min) (point-max))) 482 | 483 | (defun wgrep-toggle-readonly-area () 484 | "Toggle read-only area to remove a whole line. 485 | 486 | See the following example: you obviously don't want to edit the first line. 487 | If grep matches a lot of lines, it's hard to edit the grep buffer. 488 | After toggling to editable, you can call 489 | `delete-matching-lines', `delete-non-matching-lines'. 490 | 491 | Example: 492 | ---------------------------------------------- 493 | ./.svn/text-base/some.el.svn-base:87:(hoge) 494 | ./some.el:87:(hoge) 495 | ---------------------------------------------- 496 | " 497 | (interactive) 498 | (let ((modified (buffer-modified-p)) 499 | (read-only (not wgrep-readonly-state))) 500 | (wgrep-set-readonly-area read-only) 501 | (wgrep-set-header/footer-read-only read-only) 502 | (set-buffer-modified-p modified) 503 | (if wgrep-readonly-state 504 | (message "Removing the whole line is now disabled.") 505 | (message "Removing the whole line is now enabled.")))) 506 | 507 | (defun wgrep-change-to-wgrep-mode () 508 | "Change to wgrep mode. 509 | 510 | When the *grep* buffer is huge, this might freeze your Emacs for several minutes. 511 | " 512 | (interactive) 513 | (unless (eq major-mode 'grep-mode) 514 | (error "Not a grep buffer")) 515 | (unless (wgrep-process-exited-p) 516 | (error "Active process working")) 517 | (wgrep-prepare-to-edit) 518 | (wgrep-set-readonly-area t) 519 | (set (make-local-variable 'query-replace-skip-read-only) t) 520 | (add-hook 'after-change-functions 'wgrep-after-change-function nil t) 521 | (add-hook 'post-command-hook 'wgrep-maybe-echo-error-at-point nil t) 522 | (use-local-map wgrep-mode-map) 523 | (buffer-disable-undo) 524 | (wgrep-clone-to-temp-buffer) 525 | (setq buffer-read-only nil) 526 | (buffer-enable-undo) 527 | (set-buffer-modified-p wgrep-overlays) ;; restore modified status 528 | (setq buffer-undo-list nil) 529 | (message "%s" (substitute-command-keys 530 | "Press \\[wgrep-finish-edit] when finished \ 531 | or \\[wgrep-abort-changes] to abort changes."))) 532 | 533 | (defun wgrep-save-all-buffers () 534 | "Save the buffers that wgrep changed." 535 | (interactive) 536 | (let ((count 0)) 537 | (mapc 538 | (lambda (b) 539 | (with-current-buffer b 540 | (when (and (local-variable-p 'wgrep-file-overlays) 541 | wgrep-file-overlays 542 | (buffer-modified-p)) 543 | (basic-save-buffer) 544 | (setq count (1+ count))))) 545 | (buffer-list)) 546 | (cond 547 | ((= count 0) 548 | (message "No buffer has been saved.")) 549 | ((= count 1) 550 | (message "Buffer has been saved.")) 551 | (t 552 | (message "%d buffers have been saved." count))))) 553 | 554 | (defun wgrep-flush-current-line () 555 | "Flush current line and file buffer. Undo is disabled for this command. 556 | This command immediately changes the file buffer, although the buffer is not saved. 557 | " 558 | (interactive) 559 | (save-excursion 560 | (let ((inhibit-read-only t)) 561 | (forward-line 0) 562 | (unless (looking-at wgrep-line-file-regexp) 563 | (error "Not a grep result")) 564 | (let* ((header (match-string-no-properties 0)) 565 | (file (match-string-no-properties 1)) 566 | (line (string-to-number (match-string 3))) 567 | (origin (wgrep-get-original-value header)) 568 | (info (wgrep-get-line-info t)) 569 | (buffer (wgrep-get-file-buffer file))) 570 | (let ((inhibit-quit t)) 571 | (when (wgrep-flush-apply-to-buffer buffer info origin) 572 | (wgrep-cleanup-overlays (line-beginning-position) (line-end-position)) 573 | ;; disable undo and change *grep* buffer. 574 | (let ((buffer-undo-list t)) 575 | (wgrep-delete-whole-line) 576 | (wgrep-after-delete-line file line)) 577 | (with-current-buffer wgrep-each-other-buffer 578 | (let ((inhibit-read-only t)) 579 | (wgrep-after-delete-line file line))))))))) 580 | 581 | (defun wgrep-after-delete-line (filename delete-line) 582 | (save-excursion 583 | (wgrep-goto-first-found) 584 | (let ((regexp (format "^%s\\(?::\\)\\([0-9]+\\)\\(?::\\)" (regexp-quote filename)))) 585 | (while (not (eobp)) 586 | (when (looking-at regexp) 587 | (let ((line (string-to-number (match-string 1))) 588 | (read-only (get-text-property (point) 'read-only))) 589 | (cond 590 | ((= line delete-line) 591 | ;; for cloned buffer (flush same line number) 592 | (wgrep-delete-whole-line) 593 | (forward-line -1)) 594 | ((> line delete-line) 595 | ;; down line number 596 | (let ((line-head (format "%s:%d:" filename (1- line)))) 597 | (wgrep-set-readonly-property 0 (length line-head) read-only line-head) 598 | (replace-match line-head nil nil nil 0)))))) 599 | (forward-line 1))))) 600 | 601 | (defun wgrep-prepare-context () 602 | (wgrep-goto-first-found) 603 | (while (not (eobp)) 604 | (cond 605 | ((looking-at wgrep-line-file-regexp) 606 | (let ((filename (match-string 1)) 607 | (line (string-to-number (match-string 3)))) 608 | ;; delete backward and forward following options. 609 | ;; -A (--after-context) -B (--before-context) -C (--context) 610 | (save-excursion 611 | (wgrep-prepare-context-while filename line nil)) 612 | (wgrep-prepare-context-while filename line t) 613 | (forward-line -1))) 614 | ((looking-at "^--$") 615 | (wgrep-delete-whole-line) 616 | (forward-line -1))) 617 | (forward-line 1))) 618 | 619 | (defun wgrep-delete-whole-line () 620 | (wgrep-delete-region 621 | (line-beginning-position) (line-beginning-position 2))) 622 | 623 | (defun wgrep-goto-first-found () 624 | (goto-char (point-min)) 625 | (when (re-search-forward "^Grep " nil t) 626 | ;; See `compilation-start' 627 | (forward-line 3))) 628 | 629 | (defun wgrep-goto-end-of-found () 630 | (goto-char (point-max)) 631 | (re-search-backward "^Grep " nil t)) 632 | 633 | (defun wgrep-goto-line (line) 634 | (goto-char (point-min)) 635 | (forward-line (1- line))) 636 | 637 | ;; -A -B -C output may be misunderstood and set read-only. 638 | ;; (ex: filename-20-2010/01/01 23:59:99) 639 | (defun wgrep-prepare-context-while (filename line forward) 640 | (let ((diff (if forward 1 -1)) 641 | next line-head) 642 | (setq next (+ diff line)) 643 | (forward-line diff) 644 | (while (looking-at (format "^%s\\(-\\)%d\\(-\\)" filename next)) 645 | (setq line-head (format "%s:%d:" filename next)) 646 | (replace-match line-head nil nil nil 0) 647 | (forward-line diff) 648 | (setq next (+ diff next))))) 649 | 650 | (defun wgrep-delete-region (min max) 651 | (remove-text-properties min max '(read-only) (current-buffer)) 652 | (delete-region min max)) 653 | 654 | (defun wgrep-process-exited-p () 655 | (let ((proc (get-buffer-process (current-buffer)))) 656 | (or (null proc) 657 | (eq (process-status proc) 'exit)))) 658 | 659 | (defun wgrep-set-readonly-property (start end value &optional object) 660 | (put-text-property start end 'read-only value object) 661 | ;; This means grep header (filename and line num) that rear is editable text. 662 | ;; Header text length will always be greater than 2. 663 | (when (> end (1+ start)) 664 | (add-text-properties (1- end) end '(rear-nonsticky t) object))) 665 | 666 | (defun wgrep-prepare-to-edit () 667 | (save-excursion 668 | (let ((inhibit-read-only t) 669 | after-change-functions buffer-read-only 670 | beg end) 671 | ;; Set read-only grep result header 672 | (setq beg (point-min)) 673 | (wgrep-goto-first-found) 674 | (setq end (point)) 675 | (put-text-property beg end 'read-only t) 676 | (put-text-property beg end 'wgrep-header t) 677 | ;; Set read-only grep result footer 678 | (wgrep-goto-end-of-found) 679 | (setq beg (point)) 680 | (setq end (point-max)) 681 | (when beg 682 | (put-text-property beg end 'read-only t) 683 | (put-text-property beg end 'wgrep-footer t)) 684 | (wgrep-prepare-context)))) 685 | 686 | (defun wgrep-set-header/footer-read-only (state) 687 | (let ((inhibit-read-only t) 688 | after-change-functions 689 | beg end) 690 | ;; header 691 | (setq end (next-single-property-change (point-min) 'wgrep-header)) 692 | (when end 693 | (put-text-property (point-min) end 'read-only state)) 694 | ;; footer 695 | (setq beg (next-single-property-change (point-min) 'wgrep-footer)) 696 | (when beg 697 | (put-text-property beg (point-max) 'read-only state)))) 698 | 699 | (defun wgrep-cleanup-overlays (beg end) 700 | (let ((ovs (overlays-in beg end))) 701 | (while ovs 702 | (when (overlay-get (car ovs) 'wgrep) 703 | (delete-overlay (car ovs))) 704 | (setq ovs (cdr ovs))))) 705 | 706 | (defun wgrep-make-overlay (beg end) 707 | (let ((o (make-overlay beg end nil nil t))) 708 | (overlay-put o 'wgrep t) 709 | o)) 710 | 711 | (defun wgrep-clone-to-temp-buffer () 712 | (wgrep-cleanup-temp-buffer) 713 | (let ((grepbuf (current-buffer)) 714 | (tmpbuf (generate-new-buffer " *wgrep temp* "))) 715 | (setq wgrep-each-other-buffer tmpbuf) 716 | (add-hook 'kill-buffer-hook 'wgrep-cleanup-temp-buffer nil t) 717 | (append-to-buffer tmpbuf (point-min) (point-max)) 718 | (with-current-buffer tmpbuf 719 | (setq wgrep-each-other-buffer grepbuf)) 720 | tmpbuf)) 721 | 722 | (defun wgrep-restore-from-temp-buffer () 723 | (cond 724 | ((and wgrep-each-other-buffer 725 | (buffer-live-p wgrep-each-other-buffer)) 726 | (let ((grepbuf (current-buffer)) 727 | (tmpbuf wgrep-each-other-buffer) 728 | (savedh (wgrep-current-header)) 729 | (savedc (current-column)) 730 | (savedp (point)) 731 | (inhibit-read-only t) 732 | after-change-functions 733 | buffer-read-only) 734 | (erase-buffer) 735 | (with-current-buffer tmpbuf 736 | (append-to-buffer grepbuf (point-min) (point-max))) 737 | (goto-char (point-min)) 738 | (or (and savedh 739 | (re-search-forward (concat "^" (regexp-quote savedh)) nil t) 740 | (move-to-column savedc)) 741 | (goto-char (min (point-max) savedp))) 742 | (wgrep-cleanup-temp-buffer) 743 | (setq wgrep-overlays nil))) 744 | (t 745 | ;; non fatal error 746 | (message "Error! Saved buffer is unavailable.")))) 747 | 748 | (defun wgrep-cleanup-temp-buffer () 749 | "Cleanup temp buffer in *grep* buffer." 750 | (when (memq major-mode '(grep-mode)) 751 | (let ((grep-buffer (current-buffer))) 752 | (mapc 753 | (lambda (buf) 754 | (with-current-buffer buf 755 | (when (eq grep-buffer wgrep-each-other-buffer) 756 | (kill-buffer buf)))) 757 | (buffer-list))) 758 | (setq wgrep-each-other-buffer nil))) 759 | 760 | (defun wgrep-current-header () 761 | (save-excursion 762 | (forward-line 0) 763 | (when (looking-at wgrep-line-file-regexp) 764 | (match-string-no-properties 0)))) 765 | 766 | (defun wgrep-get-original-value (header) 767 | (when (and wgrep-each-other-buffer 768 | (buffer-live-p wgrep-each-other-buffer)) 769 | (with-current-buffer wgrep-each-other-buffer 770 | (goto-char (point-min)) 771 | (when (re-search-forward (concat "^" (regexp-quote header)) nil t) 772 | (buffer-substring-no-properties (point) (line-end-position)))))) 773 | 774 | (defun wgrep-flush-pop-deleting-line () 775 | (save-window-excursion 776 | (set-window-buffer (selected-window) (current-buffer)) 777 | (wgrep-put-color-file) 778 | (sit-for 0.3) 779 | (wgrep-delete-whole-line) 780 | (sit-for 0.3))) 781 | 782 | (defun wgrep-flush-apply-to-buffer (buffer info origin) 783 | (let ((ov (nth 3 info))) 784 | (condition-case err 785 | (progn 786 | (wgrep-apply-to-buffer buffer info origin) 787 | t) 788 | (wgrep-error 789 | (wgrep-put-reject-face ov (cdr err)) 790 | nil) 791 | (error 792 | (wgrep-put-reject-face ov (prin1-to-string err)) 793 | nil)))) 794 | 795 | (defun wgrep-find-if (pred list) 796 | (catch 'found 797 | (while list 798 | (when (funcall pred (car list)) 799 | (throw 'found (car list))) 800 | (setq list (cdr list))))) 801 | 802 | ;;; 803 | ;;; activate/deactivate marmalade install or github install. 804 | ;;; 805 | 806 | ;;;###autoload(add-hook 'grep-setup-hook 'wgrep-setup) 807 | (add-hook 'grep-setup-hook 'wgrep-setup) 808 | 809 | ;; For `unload-feature' 810 | (defun wgrep-unload-function () 811 | (remove-hook 'grep-setup-hook 'wgrep-setup)) 812 | 813 | (provide 'wgrep) 814 | 815 | ;;; wgrep.el ends here 816 | --------------------------------------------------------------------------------