dotfiles/.emacs.d/vim-mode/vim-search.el
michener 5b6729933d Add emacs directory
git-svn-id: http://photonzero.com/dotfiles/trunk@54 23f722f6-122a-0410-8cef-c75bd312dd78
2010-08-12 01:19:45 +00:00

243 lines
10 KiB
EmacsLisp

;;; vim-search.el - Search und substitute commands for ex-mode.
;; Copyright (C) 2009, 2010 Frank Fischer
;; Author: Frank Fischer <frank.fischer@mathematik.tu-chemnitz.de>,
;;
;; This file is not part of GNU Emacs.
;; TODO:
;;
;; - searching currently uses isearch. Although this is quite powerful,
;; it's only usuably as interactive search and difficult to use with
;; semi-interactive stuff like the "*" command. The current implementation
;; using unread-command-events is quite ugly.
;; - the substitute command should be more interactive and especially an operation
;; without the 'g' option should highlight all future occurences
;;; Code:
(defconst vim:search-keymap (make-sparse-keymap))
(vim:set-keymap-default-binding vim:search-keymap 'vim:search-mode-exit)
(vim:deflocalvar vim:search-last-direction nil
"The last search direction, either 'forward or 'backward.")
(defun vim:search-mode-activate ()
(setq cursor-type vim:normal-mode-cursor))
(defun vim:search-mode-deactivate ()
(isearch-exit))
(vim:defcmd vim:search-mode-exit ()
(vim:activate-normal-mode)
(push last-command-event unread-command-events))
;; Search mode is a very special mode being activated during a search
;; command. Its purpose is to disable highlighting of search results
;; if something else than a repeat-search event occurs.
(vim:define-mode search "VIM search mode"
:ident "S"
:keymaps '(vim:search-keymap)
:command-function 'vim:search-mode-command)
(add-hook 'vim:search-mode-on-hook 'vim:search-mode-activate)
(add-hook 'vim:search-mode-off-hook 'vim:search-mode-activate)
(defun vim:search-mode-command (command)
"Executes a simple-command in search-mode."
(case (vim:cmd-type command)
('simple (vim:normal-execute-simple-command command))
(t (error "Only simple commands allowed in search-mode."))))
(vim:defcmd vim:search-start (nonrepeatable)
"Starts an incremental regexp search."
(let ((search-nonincremental-instead nil))
(ad-activate 'isearch-message-prefix)
(isearch-forward-regexp)
(ad-deactivate 'isearch-message-prefix)
(setq vim:last-search-direction (if isearch-forward 'forward 'backward))))
(vim:defcmd vim:search-start-backward (nonrepeatable)
"Starts an incremental regexp search."
(let ((search-nonincremental-instead nil))
(ad-activate 'isearch-message-prefix)
(isearch-backward-regexp)
(ad-deactivate 'isearch-message-prefix)
(setq vim:last-search-direction (if isearch-forward 'forward 'backward))))
(vim:defcmd vim:search-repeat (nonrepeatable)
"Repeats the last incremental search."
(unless (vim:search-mode-p)
(vim:activate-search-mode))
(ad-activate 'isearch-message-prefix)
(isearch-repeat vim:last-search-direction)
(ad-deactivate 'isearch-message-prefix))
(vim:defcmd vim:search-repeat-opposite (nonrepeatable)
"Starts an incremental regexp search."
(unless (vim:search-mode-p)
(vim:activate-search-mode))
(ad-activate 'isearch-message-prefix)
(isearch-repeat (if (eq vim:last-search-direction 'forward) 'backward 'forward))
(ad-deactivate 'isearch-message-prefix))
(defadvice isearch-message-prefix (after vim:isearch-message-prefix (&optional c-q-hack ellipsis nonincremental))
"This advice changes the minibuffer indicator to '/' or '?'"
(setq ad-return-value (if isearch-forward "/" "?")))
(defun vim:start-word-search (unbounded direction)
(condition-case nil
(goto-char (vim:motion-bwd-word-end :count 1))
(error nil))
(save-excursion
(re-search-forward (concat "\\<[" vim:word "]+\\>")))
(when (eq direction 'backward)
(goto-char (1+ (match-end 0))))
(let ((events (reverse (append (if (eq direction 'forward)
"/"
"?")
(if unbounded
(regexp-quote (match-string 0))
(concat "\\<"
(regexp-quote (match-string 0))
"\\>"))
[return]
"n"
nil))))
(while events
(push (car events) unread-command-events)
(setq events (cdr events)))))
(vim:defcmd vim:search-word (nonrepeatable)
"Searches the next occurence of word under the cursor."
(vim:start-word-search nil 'forward))
(vim:defcmd vim:search-word-backward (nonrepeatable)
"Searches the next occurence of word under the cursor."
(vim:start-word-search nil 'backward))
(vim:defcmd vim:search-unbounded-word (nonrepeatable)
"Searches the next occurence of word under the cursor."
(vim:start-word-search t 'forward))
(vim:defcmd vim:search-unbounded-word-backward (nonrepeatable)
"Searches the next occurence of word under the cursor."
(vim:start-word-search t 'backward))
(vim:defcmd vim:cmd-substitute (motion argument nonrepeatable)
"The VIM substitutde command: [range]s/pattern/replacement/flags"
(multiple-value-bind (pattern replacement flags) (vim:parse-substitute argument)
(lexical-let* ((pattern pattern)
(replacement replacement)
(first-line (if motion (vim:motion-first-line motion) (line-number-at-pos (point))))
(last-line (if motion (vim:motion-last-line motion) (line-number-at-pos (point))))
(whole-line (and flags (find ?g flags)))
(confirm (and flags (find ?c flags)))
(ignore-case (and flags (find ?i flags)))
(dont-ignore-case (and flags (find ?I flags)))
(case-fold-search (or (and case-fold-search
(not dont-ignore-case))
(and (not case-fold-search)
ignore-case)))
(case-replace case-fold-search)
(last-point (point))
(overlay (make-overlay (point) (point)))
(next-line (line-number-at-pos (point)))
(nreplaced 0))
(unwind-protect
(if whole-line
;; this one is easy, just use the built in function
(vim:perform-replace pattern replacement confirm t nil nil nil
(save-excursion
(goto-line first-line)
(line-beginning-position))
(save-excursion
(goto-line last-line)
(line-end-position)))
(if confirm
(progn
;; this one is more difficult, we have to do the
;; highlighting and questioning on our own
(overlay-put overlay 'face
(if (internal-find-face 'isearch nil)
'isearch 'region))
(map-y-or-n-p #'(lambda (x)
(set-match-data x)
(move-overlay overlay (match-beginning 0) (match-end 0))
(concat "Query replacing "
(match-string 0)
" with "
(match-substitute-replacement replacement case-fold-search)
": "))
#'(lambda (x)
(set-match-data x)
(replace-match replacement case-fold-search)
(incf nreplaced)
(setq last-point (point)))
#'(lambda ()
(let ((end (save-excursion
(goto-line last-line)
(line-end-position))))
(goto-line next-line)
(beginning-of-line)
(when (and (> end (point))
(re-search-forward pattern end t nil))
(setq last-point (point))
(setq next-line (1+ (line-number-at-pos (point))))
(match-data))))))
;; just replace the first occurences per line
;; without highlighting and asking
(goto-line first-line)
(beginning-of-line)
(while (and (<= (line-number-at-pos (point)) last-line)
(re-search-forward pattern (save-excursion
(goto-line last-line)
(line-end-position))
t nil))
(incf nreplaced)
(replace-match replacement)
(setq last-point (point))
(forward-line)
(beginning-of-line)))
(goto-char last-point)
(if (= nreplaced 1)
(message "Replaced 1 occurence")
(message "Replaced %d occurences" nreplaced)))
;; clean-up the overlay
(delete-overlay overlay)))))
(defun vim:parse-substitute (text)
(when (string-match "\\`\\s-*/\\(\\(?:[^/]\\|\\\\.\\)+\\)/\\(\\(?:[^/]\\|\\\\.\\)*\\)\\(?:/\\([giIc]*\\)\\)?\\s-*\\'"
text)
(let ((pattern (match-string 1 text))
(replacement (match-string 2 text))
(flags (match-string 3 text)))
(values pattern
;; handle some special escapes, especially \\ and \/
(replace-regexp-in-string "\\\\."
(lambda (x)
(cond ((string= x "\\n") "\n")
((string= x "\\t") "\t")
((string= x "\\r") "\r")
((string= x "\\/") "/")
((string= x "\\\\") "\\\\\\\\")
(t x)))
replacement)
flags))))
(provide 'vim-search)
;;; vim-search.el ends here