;;; hippie-vi.el --- 

;; Copyleft (C) 2009 Cecil Curry <http://raiazome.com>

;; Author:     Cecil Curry <http://raiazome.com>
;; Maintainer: Cecil Curry <http://raiazome.com>
;; Time-stamp: "2009-05-21 18:09:43 leycec"
;; Created: 1 Mar 2009
;; Version: 0.0.1
;; URL: http://hippie.raiazome.com
;; Keywords: abbrev,c,convenience,data,emulations,faces,files,lisp,matching,tools

;; This file is not part of GNU Emacs.

;;; Commentary:
; --------------------( KEY BINDINGS                       )--------------------
; ViPER (ViPER Is a Package for Emacs Rebels) emulates Vi[M] key bindings. (Note
; that {register} and {marker}, below, are any of 52 possible letters: a to Z.)
;
; Type <%> in Vi mode to move to the bracket matching the bracket at the current
; point if there is a bracket at the current point.
;
; Type << and >> in Vi Mode to indent and deindent the current line one level.
; Type <Tab> to auto-reindent the current line, <Ctrl-Alt-\> to auto-reindent
; the current region, and <={region}> to auto-reindent that {region}.
;
; Type <~> in Vi mode to change the case of the character at the current point.
;
; Type <Ctrl-s> and <Ctrl-r> in either mode to incrementally, interactively
; search forward and backward, respectively. Type <*> and <#> to incrementally
; (but not interactively) search forward and backward, respectively, for the
; word under the current point.
;
; Type <u> in Vi mode to undo the most recent edit. Also, after typing <u>,
; type <.> to incrementally undo less recent edits. Lastly, after typing <u>,
; type <u> again or <Ctrl-r> to redo the most recently undone edit.
;
; Type <Q> in Vi mode to perform a minibuffer-style query-replace operation.
;
; Type <@#> in Vi mode to begin defining a keyboard macro, a sequence of key
; commands to be assigned to that macro, and <@{register}> to store that macro.
; Type <@{register}>, later, to run that macro; or <@@> or <*>, later, to run
; the most recently stored macro.
;
; Type <]{register}> to view the contents of {register}.
;
; Type <m{marker}> to mark your current point into {marker}. Type <'{marker}>,
; later, to move the point back to that point. Type <[{marker}> to show the
; text around the point at {marker} in a new, temporary buffer.
;
; Type <Ctrl-z> in Insert mode to enter Vi commands without leaving Insert mode.
;
; Type <Ctrl-y> in Insert mode to yank (paste) the most recently killed (cut)
; text from the kill ring (clipboard) onto the current insertion point. Also,
; after typing <Ctrl-y>, type <Alt-y> to incrementally replace that yanked
; text with less recently killed text from the kill ring.
;
; Type <Ctrl-a> or <Ctrl-b> in Insert mode to move to the beginning or end of
; the current line, respectively. Likewise, type <Alt-f> or <Alt-b> to move to
; the next or previous word, respectively.
;
; Type <Alt-p> or <Alt-n> in the minibuffer to move to the next or previous
; history item, respectively.
;
; Type <Alt-s> or <Alt-r> in the minibuffer to find the next or previous
; history match, respectively.
;
; --------------------( CUSTOMIZATION                      )--------------------
;FIXME: This is better accomplished with before advice. Have I made this change?
; ViPER's notion of what constitutes a word has been hardcoded to include '_'.
; To change that so that ViPER no longer jumps over underscore characters when
; moving over words, change the following lines of your locally-installed
; "${EMACS_HOME}/lisp/emulation/viper-util.el[.gz]" file, as follows:
;     
;  (defun viper-skip-alpha-forward (&optional addl-chars)
;    ; Add this line!
;    (setq addl-chars "")
;    ; Comment out this line.
;    ;(or (stringp addl-chars) (setq addl-chars ""))
;    ; Leave all other lines as is...
;    (viper-skip-syntax
;
;  (defun viper-skip-alpha-backward (&optional addl-chars)
;    ; Add this line!
;    (setq addl-chars "")
;    ; Comment out this line.
;    ;(or (stringp addl-chars) (setq addl-chars ""))
;    ; Leave all other lines as is...
;    (viper-skip-syntax
;
; --------------------( TODO                               )--------------------
; * Integrate this, which looks quite intriguing:
;   http://edward.oconnor.cx/config/.viper
; * Integrate with `fundamental-mode', so that Vi mode always comes up.
; * Add `compilation-mode' to the `viper-vi-state-mode-list' list, so as to
;   ensure that mode starts up in VI mode. This can, of course, be extended to
;   other such modes. Note that we must also remove each such mode from
;   `viper-emacs-state-mode-list', when it appears there. For details, see those
;   variable's definitions in `viper.el'.

;;; History:
;; 
;; 2009-05-01  Cecil Curry  <http://raiazome.com>
;;   * Created.

;;; Code:
; ....................{ DEPENDENCIES                       }....................
(require 'hippie)

; ....................{ LIFECYCLE                          }....................
(defun hippie-vi/load ()
  "Load `hippie-vi'. Specifically:

* Load the external ViPER and Vimpulse libraries.
* Set somewhat sane defaults for these libraries."
  (hippie-vi/load-libraries)
  (hippie-vi/setq-libraries))

(defun hippie-vi/unload ()
  "Unload `hippie-vi'. Specifically, undo all global side-effects produced by
`hippie-vi/load'."
  (hippie-vi/unload-libraries))

; ....................{ LIBRARIES                          }....................
(defun hippie-vi/load-libraries ()
  "Load ViPER and Vimpulse."
  ; Enable ViPER and inhibit it, when loaded below, from displaying its rather
  ; noisy "startup message." (Some messages, we may do without.)
  (hippie/set 'viper-mode t
              'viper-always t
              'viper-inhibit-startup-message t
              'viper-expert-level 5)
  ; Bind <Ctrl-`> to switch between ViPER's Vi and Emacs modes.
  (hippie/set 'viper-toggle-key [(control ?`)])
  ; Release <Ctrl-h> back to Emacs for use as the canonical help command.
  (hippie/set 'viper-want-ctl-h-help nil)
  ; Permit Emacs key bindings in command and insert modes.
  (hippie/set 'viper-want-emacs-keys-in-insert t
              'viper-want-emacs-keys-in-vi t)
  ; Import ViPER.
  (require 'viper)
  ; Import Vimpulse. (This adds support for Visual mode to ViPER, among other
  ; additions.
  (require 'vimpulse))

(defun hippie-vi/unload-libraries ()
  "Unload ViPER and Vimpulse."
  (unload-feature 'vimpulse)
  (unload-feature 'viper))

; ....................{ LIBRARIES =variables               }....................
;FIXME: Exceptionally complete list! Quite good. Integrate.
;'(viper-emacs-state-mode-list (quote (dired-mode efs-mode tar-mode browse-kill-ring-mode recentf-mode recentf-dialog-mode occur-mode mh-folder-mode gnus-group-mode gnus-summary-mode completion-list-mode Buffer-menu-mode compilation-mode rcirc-mode jde-javadoc-checker-report-mode view-mode vm-mode vm-summary-mode)))
 ;; '(viper-insert-state-mode-list (quote (internal-ange-ftp-mode comint-mode inferior-emacs-lisp-mode erc-mode eshell-mode shell-mode log-view-mode grep-mode custom-mode Info-mode help-mode mail-mode)))
 ;; '(viper-vi-state-mode-list (quote (fundamental-mode makefile-mode awk-mode m4-mode xrdb-mode winmgr-mode autoconf-mode cvs-edit-mode html-mode html-helper-mode emacs-lisp-mode lisp-mode lisp-interaction-mode jde-mode java-mode cc-mode c-mode c++-mode objc-mode fortran-mode f90-mode basic-mode bat-mode asm-mode prolog-mode flora-mode sql-mode text-mode indented-text-mode tex-mode latex-mode bibtex-mode ps-mode diff-mode idl-mode perl-mode cperl-mode javascript-mode tcl-mode python-mode sh-mode ksh-mode csh-mode gnus-article-mode mh-show-mode)))
 
(defun hippie-vi/setq-libraries ()
  "Customize ViPER and Vimpulse-specific variables."
  ; ....................{ VIPER UI                           }....................
  ;FIXME: Should be handled by `hippie-theme', no?
  ; Use this cursor color when in a replacement region.
  ;; (hippie/setq viper-replace-overlay-cursor-color "red")
  ; Use this cursor color when in Insert mode. 
  ;; (hippie/setq viper-insert-state-cursor-color "green")
  ; ....................{ VIPER MODES                        }....................
  ; Do not move the cursor back 1 character when switching from Insert to Vi mode.
  (hippie/setq-default viper-ESC-moves-cursor-back nil)
  ; Permit multiple escape keys. By default, ViPER "compresses" multiple presses
  ; of the <escape> key to just one press.
  (hippie/setq viper-no-multiple-ESC nil)
  ; ....................{ MINIBUFFER EDITING                 }....................
;FIXME: This doesn't quite do the right thing. Note that
;`viper-minibuffer-setup-sentinel' in "viper-cmd.el.gz" places the minibuffer in
;Emacs state when `viper-vi-style-in-minibuffer' is nil. Very well; but we still
;cannot override ViPER's Emacs keymap for minibuffer modes (like IDo) or major
;modes (like dired). I don't know why not. It's quite annoying, actually.
;
;Woops. Looks like I've been misusing ViPER's global keymaps. See:
;http://www.gnu.org/software/emacs/manual/html_node/viper/Key-Bindings.html
  ; Do not allow Vi-style key bindings in the minibuffer.
  ;
  ; Note that enabling this currently breaks integration with non-ViPER,
  ; minibuffer-using keymaps -- particularly, the IDO-specific
  ; `ido-completion' keymap. As such, this should be disabled. (The issue
  ; probably lies in the `viper-minibuffer-setup-sentinel' function in
  ; `viper-cmd.el'. Alas, but I'm not competent enough to correct it.)
  ;
  ; Note that disabling this requires modification of the Emacs-specific
  ; minibuffer keymap, `minibuffer-local-map', rather than the ViPER-specific
  ; minibuffer keymap, `viper-minibuffer-map'.
  (hippie/setq viper-vi-style-in-minibuffer nil)
  ; ....................{ TEXT NAVIGATING                    }....................
  ; Make ViPER words consist of alphanumeric and non-alphanumeric symbol
  ; characters. This is most convenient when programming.
  (hippie/setq-default viper-syntax-preference "emacs")
  (viper-update-syntax-classes)
  ; Use Emacs-style, rather than Ex-style, motions. (Ex-style motions limit line-
  ; oriented motions to the current line... while Emacs-style do not.)
  (hippie/setq-default viper-ex-style-motion  nil
                viper-ex-style-editing nil)
  ; ....................{ TEXT EDITING                       }....................
  ; Do not move the current point when repeating and undoing in ViPER.
  (hippie/setq viper-keep-point-on-repeat t
        viper-keep-point-on-undo t)
  ; Auto-indent newly inserted text in ViPER's insert mode.
  (hippie/setq-default viper-auto-indent t
                viper-electric-mode t)
  ; ....................{ TEXT SEARCHING                     }....................
  ; Search case-insensitively.
  (hippie/setq viper-case-fold-search nil)
  ; Search with regular expressions.
  (hippie/setq viper-re-search t)
  ; Wrap searches around the beginning or end of the current buffer.
  (hippie/setq viper-search-wrap-around t)
  ; ....................{ TEXT REPLACING                     }....................
  ; Query replace with regular expressions.
  (hippie/setq viper-re-query-replace t)
  ; Permit multi-line replacement regions.
  (hippie/setq viper-allow-multiline-replace-regions t)
  ; Insist that pressing <Delete> when using ViPER's replacement command (bound to
  ; <R> by default) deletes characters and moves the cursor backwards.
  (hippie/setq viper-delete-backwards-in-replace t))

; FIXME BWC: Send this revision to Vimpulse author.
; Improves the <o> Vi command in Vimpulse by additionally continuing a comment
; onto the newline, if the previous line was a comment.
;(defun my-viper-open-new-line-below (&optional arg)
; (interactive)
; (viper-goto-eol arg)
; (indent-new-comment-line)
; (viper-change-state-to-insert))

;FIXME: This isn't quite right. Try deleting all initial buffers, for example,
;after opening "e ~/.emacs". You'll rewind to the "*Messages*" buffer, but it
;won't be in ViPER mode. Clearly, killing a buffer and browsing to the next is
;not a call to `switch-to-buffer'.
; Insist that scratch buffers start-up in ViPER mode, as well.
(defadvice switch-to-buffer
  (after hippie-vi/enable-vi-mode-for-scratch-buffers activate)
  "Ensure that switching to a scratch buffer (e.g., the \"*Messages*\",
\"*Help*\", or \"*scratch*\" buffer) enables ViPER mode for that buffer, if not
already enabled."
  (and (not buffer-file-name)
       (member (buffer-name) '("*Messages*" "*Help*" "*scratch*"))
       (viper-change-state-to-vi)))

; ....................{ VERSION CONTROL                    }....................
(defadvice viper-file-checked-in-p (around hippie-vi/fixed activate)
  "Answer this function's FIXME question, \"Should this deal with more than
CVS?\", by doing just that. This advice deals with SVN (Subversion), Bzr
\(Bazaar), Git, Hg \(Mercurial), and Arch."
  (and (featurep 'vc-hooks)
       ;; CVS, SVN, Bzr, Git, Hg, and Arch files are considered not checked in
       ;;FIXME: Is this list feature-complete? `vc-handled-backends' describes
       ;;       which backend systems `vc-mode' supports.
       (not (memq (vc-backend file) '(nil CVS SVN Bzr Git Hg Arch)))
       (ad-do-it)))

; --------------------( COPYRIGHT AND LICENSE              )--------------------
; The information below applies to everything in this distribution,
; except where noted.
; 
; Copyleft 2009 by Cecil Curry <http://hippie.raiazome.com>.
; 
; This program is free software; you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation; either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see L<http://www.gnu.org/licenses/>.
;
; --------------------( LIBRARY                            )--------------------
(provide 'hippie-vi)

;;; hippie-vi.el ends here
