;;; mlessvim.el --- (Modeless vim) perform vi-like operations in ;;; emacs using a single "action key" ;; Copyright (C). This is released under the BSD license. ;; Author: Tom Wright ;; Last change: Nov 2008 ;; Version: 0 ;; Keywords: vi, vim, modeless ;;; Motivation ;; Emacs can have some perceived advantages over vi/vim. ;; * You might already know how to use it or customize it ;; * It doesn't have modeful input. ;; * It has the concept of different editing modes with extra key-bindings ;; * It can open multiple windows ;; * It has a terminal that works. ;; * Some people like lisp. ;; However, one possible gripe is that it isn't as good at editing text ;; at vi, you can't press "da(" or "d/end" or "dap". Modeless vim seeks ;; to add these sort of commands to emacs... without having to add ;; a transient normal mode like vim, which you have to continually exit and enter. ;; Instead a "perform some sort of clever text manipulation" ;; prefix key is added. ;; Another theme of modeless-vim is "you don't have to press the control and alt keys" ;; quite as much, but nor do you have to continually press "C-]" - hence the prefix key. ;;; Installation ;; Place mlessvim.el in your load-path. (See "C-h v load-path" for details), and require it ;; on startup. If you want to go into mlessvim mode by default run mlessvim-mode. ;; This can be done with the following line in your .emacs file. ;; (require 'mlessvim) ;; (mlessvim-mode 1) ;; Next you have to find a nice key to use as your mlessvim prefix key. The context menu ;; or super key is good for this (assuming you don't use a thinkpad ;; - hurrah for IBM... or something like that). ;; To find out what emacs calls a key use M-x global-set-key then press the key. ;; Then run "M-x customize-group mlessvim." and change mlv-top-key to the string ;; "" (including both the quotes and the <>), saving this setting for future ;; and current sessions. ;; Next see mlv-help for a list of bindings. ;;; Caps lock ;; If you want to use caps lock for your prefix key life is more fun. One ;; approach is to remap the caps key to a symbol which emacs understands and bind ;; to this symbol. On X systems (windows / mac) see xmodmap, on windows see sharpkeys. ;;; Contact ;; If you have an questions or suggestions feel free to contact me on ;; or would just like to say how much you dislike this mode ;; feel free to contact me at tat.wright@tat.wright.name. ;;; Plausibly Asked Questions ;;Q: Why on earth would you waste your time doing something so ludicrously pointless ;; when you could instead just use an editor that works and actually do something useful. ;; This code is all trivial and slightly broken anyway! More to the point ;; there are already two vi emulation mode in emacs! You lose. ;;A: Yes. (I'm also exceedingly dull and have no social life). ;;Q: Isn't it possible to get exactly the same effects in vim by using the C-o command when in insert mode? ;;A: Yes... in a way. This is still in using vim rather than emacs. Some people might think that ;; emacs offers advatanges over vim *other than* its keybinding model (for example the fact that ;; it attempts to be an entire operating system...) ;;Q: Why couldn't you just have adapted viper mode so it used a prefix key? ;;A: This may have been a good idea. I (very) briefly looked into this, but decided that (a) playing was more fun ;; (b) I wanted whatever I did to play well with emacs help - in particular "C-h k d w" should give ;; you a function name and help for that function. ;;Q: Don't you think it was a bad idea to subtly break the meaning of vim keys? Does this may this hard to learn? ;;A: Maybe. I know that conventional wisdom says that one should make something near identical or complete smash ;; the specification - perhaps I could claim that I'm doing the later. One defence is that I wrote this for ;; myself originally so defined so of the commands to do what I wanted them to do rather than what something ;; more like vim would do. Hopefully having another set of bindings in your head won't destroy your ability ;; to use vim (I still manage to use vim when it is convenient) also hopefully these bindings aren't too hard to learn. ;;Q: Don't you think chaning the default emacs bindings is a bad idea? ;;A: This doesn't really change the default emacs bindings - rather add lots of things do them. (Though I ;; do sometimes think that there is some irresistible force which tells me to do everything using ;; modeless-vim commands.) Hopefully you can just fall back to using the emacs key if you are on an emacs editor; ;; the modeless-vim bindings all start with a prefix command - so are quite easy to recognise. Also ;; one tends to use an editor for large periods of time - this means that it makes sense to have something ;; fairly customised. ;;Q: Why have you flooded my apropos search for "case"/"word"/"line" with matching functions? ;;A: Not quite sure. If it makes you feel happier it means emacs' help facilities work properly. There aren't *that* many matches. ;; Vim-like text commands from a prefixed emacs shortcut (require 'cl) (require 'simple) (require 'edmacro) (defun mlv-defoperator (name key doc-verb doc-adverbial func-name) (add-to-list 'mlv-operations name) (add-to-list 'mlv-operation-keys (cons name key)) (add-to-list 'mlv-operation-doc (cons name (list doc-verb doc-adverbial))) (add-to-list 'mlv-operation-funcs (cons name func-name))) (setq mlv-selectors ()) (defmacro mlv-defselector (symbol key &rest body) "Define a selector with name symbol whose keybindings use key, and where body returns a pair representing the selected region" (let* ((name (symbol-name symbol)) (funsymbol (intern (concat "mlv-" name)))) `(progn (defun ,funsymbol () ,@ (save-excursion body)) (add-to-list (quote mlv-selectors) ,name) (put (quote ,funsymbol) (quote key) ,key)))) (setq mlv-functions ()) (defmacro mlvdefun (name key &rest body) `(progn (defun ,name ,@ body) (add-to-list 'mlv-functions (quote ,name)) (put (quote ,name) (quote key) ,key) (define-key mlv-top-map ,key (quote ,name)))) (defmacro mlv-def-innerselector (symbol &optional doc) "Defines a variant on a selector which selects everything but the first and last characters of a given selection." (let ((variant-symbol (intern (concat "mlv-" (symbol-name symbol))))) `(mlv-defselector ,(intern (concat "in-" (symbol-name symbol))) ,(concat "i" (get variant-symbol 'key)) ,doc (destructuring-bind (a b) (sort (,variant-symbol) '<) (list (+ a 1) (- b 1)))))) (defmacro mlv-tap-selector (thing key &optional doc) "Define a selector from a thing-at-point" `(mlv-defselector ,thing ,key ,doc (let ((tap-bounds (bounds-of-thing-at-point (quote ,thing)))) (if (null tap-bounds) () (list (car tap-bounds) (cdr tap-bounds)))))) (defmacro mlv-tap-end-selector (thing key &optional doc) "Define a selector from the point to the end of thing-at-point" `(mlv-defselector ,(intern (format "end-of-%s" (symbol-name thing))) ,key ,doc (let ((tap-bounds (bounds-of-thing-at-point (quote ,thing)))) (if (null tap-bounds) () (list (point) (cdr tap-bounds)))))) (mlv-tap-selector word "w" "the region between the beginning and end of the word containing the point") (mlv-tap-end-selector word "W" "the region between the point and the end of the word containing the point") (mlv-tap-selector line "l" "the region between the beginning of the line containing the point and the beginning of the next line") (mlv-tap-end-selector line "L" "the region between the point and the beginning of the next line") (mlv-tap-selector symbol "s" "the region between the beginning and end of the symbol in your current mode containing the point") (mlv-tap-end-selector symbol "S" "the region between the point and the end of the symbol containing the point in your current mode") (mlv-tap-selector word "w" "the region between the beginning and end of the word containing the point") (mlv-tap-end-selector word "W" "the region between the point and the end of the word containing the point") (mlv-tap-selector line "l" "the region between the beginning of the line containing the point and the beginning of the next line") (mlv-tap-end-selector line "L" "the region between the point and the beginning of the next line") (mlv-tap-selector symbol "s" "the region between the beginning and end of the symbol in your current mode containing the point") (mlv-tap-end-selector symbol "S" "the region between the point and the end of the symbol containing the point in your current mode") (mlv-tap-selector whitespace "" "the whitespace containing the point upto the end of line (or if there is no whitespace on the line the new line character") (mlv-defselector multiline-whitespace "" "the whitespace containing the point, excluding whitespace at the beginning of lines before characters" (save-excursion (list (progn (search-backward-regexp "[^ \n]") (forward-line 1) (beginning-of-line) (point)) (progn (forward-char 1) (if (search-forward-regexp "[^ \n]" nil t) (beginning-of-line) (goto-char (point-max))) (point))))) (defmacro mlv-selector-from-move (symbol key &rest mover) `(mlv-defselector ,symbol ,key (list (point) (progn ,mover (point))))) (setq mlv-operation-keys ()) (setq mlv-operations ()) (setq mlv-operation-funcs ()) (setq mlv-operation-doc ()) ;; name, letter, (mlv-defoperator "cut" "x" "Delete" "from the current buffer placing a copy on the kill ring" "kill-region") (mlv-defoperator "copy" "c" "Place" "on the kill ring" "kill-ring-save") (mlv-defoperator "del" "d" "Delete" "from the buffer, without placing a copy on the kill-ring" "delete-region") (mlv-defoperator "upcase" "u" "Make" "uppercase" "upcase-region") (mlv-defoperator "downcase" "U" "Make" "lowercase" "downcase-region") (mlv-defoperator "titlecase" "t" "Make" "title case" "upcase-initials-region") (mlv-defoperator "surround" "e" "Surround" "with a character" "mlv-surround") (defun mlv-surround (beg end) "Surround a region with some character" (let* ((surround (char-to-string (read-char "wrap with:"))) open close) (setq open surround) ; ever heard of a switch? (setq close (cond ((equal surround "(") ")") ((equal surround "[") "]") ((equal surround "'") "'") ((equal surround "\"") "\""))) (save-excursion (goto-char (min beg end)) (insert open) (goto-char (+ (max beg end) 1)) (insert close)))) (defun mlv-make-toplevel-maps () "Construct the top level keys for most operations" (dolist (operation-pair mlv-operation-keys) (let ((operation (car operation-pair)) (key (cdr operation-pair))) (let ((symbol (intern (format "mlv-%s-map" operation)))) (message "setting %s to %s" key symbol) (define-prefix-command symbol) (define-key mlv-top-map (read-kbd-macro key) symbol))))) (defvar mlv-top-key "