1 ;;; plomvi.el --- poor man's vim emulation
5 ;; Author: Christian Heller <plom+plomvi@plomlompom.com>
7 ;; This program is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation, either version 3 of the License, or
10 ;; (at your option) any later version.
12 ;; This program is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
22 ;; A very imperfect simulation of a small subset of Vim behavior, far
23 ;; from the sophistication of packages like evil; an intentionally
24 ;; thin wrapper around native Emacs behavior instead. To the author,
25 ;; it mostly serves to both avoid muscle memory context switches
26 ;; between Emacs and most basic Vim usages, and at the same time avoid
27 ;; the conceptual distances evil puts between the user and native
28 ;; Emacs logic. It does not care though to keep default Emacs
29 ;; keybindings intact, overwriting some common ones with those from
34 ;; 1. load this script into your init file: (load ...)
36 ;; 2. to start plomvi by default, put this into your init file:
37 ;; (plomvi-global-mode 1)
39 ;; 3. define some otherwise unused keybinding to simulate what would
40 ;; be a jump back from Insert mode to Normal mode in Vim (but is de
41 ;; facto just a plomvi mode activation), such as this:
42 ;; (global-set-key (kbd "<f1>") 'plomvi-activate)
46 (defun plomvi-nothing()
47 "Do nothing. Used to shadow self-insert bindings in `plomvi-editable-mode-map'."
50 (defun plomvi-half-scroll()
51 "Scroll down half a screen width."
53 (scroll-up (/ (window-height) 2)))
55 (defun plomvi-goto-line (count)
56 "Jump to line: on nil count, last one, else count."
59 (goto-char (point-max))
60 (goto-char (point-min))
61 (forward-line (1- count))))
63 (defun plomvi-prefix-zero-or-line-start (prev-prefix)
64 "If no prefix so far, jump to start of line, else start new prefix with 0."
66 (if (null prev-prefix)
70 (defun plomvi-prompt (prompt-input)
71 "Provide super-basic : prompt that only accepts:
80 If search and replace syntax is detected, it recommends using `query-replace'
85 ((string= prompt-input "q!")
87 ((string= prompt-input "q")
88 (save-buffers-kill-emacs))
89 ((string= prompt-input "w")
91 ((string-match "^w [^ ]+" prompt-input)
92 (let ((file-name (substring prompt-input 2))) (write-file file-name)))
93 ((string-match "^%?s/" prompt-input)
94 (message "NOT IMPLEMENTED, consider using query-replace(-regexp)"))
95 ((string= prompt-input "wq")
96 ((lambda () (save-some-buffers t) (kill-emacs))))
97 ((string= prompt-input "vsplit")
98 (split-window-horizontally))
99 ((string= prompt-input "split")
100 (split-window-vertically))
101 (t (message "NOT IMPLEMENTED"))))
103 (defun plomvi-newline-above ()
104 "Open and jump into new line above current line, deactivate `plomvi-mode'."
111 (defun plomvi-newline-below ()
112 "Open and jump into new line below current line, deactivate `plomvi-mode'."
118 (defun plomvi-paste-backward ()
119 "Paste last kill leftwards in current line, or (if kill ends in \n) above it.
121 Note that this ignores killed rectangles.
124 (if (eq nil (string-match "\n$" (current-kill 0)))
130 (defun plomvi-paste-forward ()
131 "Paste last kill rightwards in current line, or (if kill ends in \n) under it.
133 Note that this ignores killed rectangles."
135 (if (eq nil (string-match "\n$" (current-kill 0)))
144 (defun plomvi-region-kill()
145 "Kill marked region."
147 (kill-region (region-beginning) (region-end)))
150 "If rectangle or region marked, kill those; else, kill char after point."
153 ((and (boundp 'rectangle-mark-mode) (eq t rectangle-mark-mode))
154 (kill-rectangle (region-beginning) (region-end)))
156 (plomvi-region-kill))
160 (defun plomvi-rectangle-mark()
161 "Start marked rectangle, move right one char so a single column is visible."
163 (push-mark (point) nil t)
164 (rectangle-mark-mode)
167 (defun plomvi-search-forward()
168 "Find next occurence of search string last targeted by isearch."
170 (search-forward isearch-string))
172 (defun plomvi-search-backward()
173 "Find previous occurence of search string last targeted by isearch."
175 (search-backward isearch-string))
178 (defun plomvi-copy-line()
179 "Copy current line into kill buffer."
181 (let ((keep_pos (point))) ; We sort of cheat: We kill the line, then we
182 (kill-whole-line) ; paste it back, and return point to its
183 (plomvi-paste-backward) ; original position.
184 (goto-char keep_pos))) ;
186 (defun plomvi-copy-region()
187 "Copy marked region."
189 (copy-region-as-kill (region-beginning) (region-end)))
191 (defun plomvi-replace-char (c)
192 "Replace char after point with c."
193 (interactive "cplomvi-replace-char")
194 (delete-char 1) (insert-char c) (left-char))
196 ;;; some attempt at a redo feature, not very successful, documented here for
197 ;;; research purposes
199 ;(setq plomvi-in-redo nil) ; should be made buffer-local
200 ;(setq plomvi-undo-count 0) ; should be made buffer-local
201 ;(defun plomvi-undo()
204 ; (setq plomvi-in-redo nil)
205 ; (setq plomvi-undo-count (+ plomvi-undo-count 1)))
206 ;(defun plomvi-redo()
208 ; (if (> plomvi-undo-count 0)
210 ; (if (null plomvi-in-redo)
212 ; (insert-char ?\s 1)
214 ; (setq plomvi-in-redo t)))
217 ; (setq plomvi-undo-count (- plomvi-undo-count 1))))))
219 (defun plomvi-no-redo()
220 "Tell user what to do, since implementing vim redo was too much of a hassle."
222 (message "Vim-style redo not available. Try M-x for Emacs' undo-undo."))
225 (defvar plomvi-basic-mode-map (make-sparse-keymap)
226 "Keymap for `plomvi-basic-mode', to be used on read-only buffers.
228 In contrast to the keymap `plomvi-editable-mode' for editable buffers,
229 this not only excludes keybindings for editing text, but also does not
230 shadow keybindings that are bound to `self-insert-command'.
232 Thus, it on the whole shadows much fewer keybindings of other keymaps
233 that can therefore be used for other purposes.")
234 (define-key plomvi-basic-mode-map (kbd ":") 'plomvi-prompt)
235 (define-key plomvi-basic-mode-map (kbd "C-w") 'other-window)
236 (define-key plomvi-basic-mode-map (kbd "k") 'previous-line)
237 (define-key plomvi-basic-mode-map (kbd "j") 'next-line)
238 (define-key plomvi-basic-mode-map (kbd "h") 'left-char)
239 (define-key plomvi-basic-mode-map (kbd "l") 'right-char)
240 (define-key plomvi-basic-mode-map (kbd "w") 'forward-word)
241 (define-key plomvi-basic-mode-map (kbd "b") 'backward-word)
242 (define-key plomvi-basic-mode-map (kbd "/") 'isearch-forward)
243 (define-key plomvi-basic-mode-map (kbd "N") 'plomvi-search-backward)
244 (define-key plomvi-basic-mode-map (kbd "n") 'plomvi-search-forward)
245 (define-key plomvi-basic-mode-map (kbd "v") 'set-mark-command)
246 (define-key plomvi-basic-mode-map (kbd "C-v") 'plomvi-rectangle-mark)
247 (define-prefix-command 'plomvi-g-map)
248 (define-key plomvi-basic-mode-map (kbd "g") 'plomvi-g-map)
249 (define-key plomvi-g-map (kbd "g") 'beginning-of-buffer)
250 (define-key plomvi-basic-mode-map (kbd "G") 'plomvi-goto-line)
251 (define-key plomvi-basic-mode-map (kbd "$") 'end-of-line)
252 (define-key plomvi-basic-mode-map (kbd "0") 'plomvi-prefix-zero-or-line-start)
253 (define-key plomvi-basic-mode-map (kbd "1") 'digit-argument)
254 (define-key plomvi-basic-mode-map (kbd "2") 'digit-argument)
255 (define-key plomvi-basic-mode-map (kbd "3") 'digit-argument)
256 (define-key plomvi-basic-mode-map (kbd "4") 'digit-argument)
257 (define-key plomvi-basic-mode-map (kbd "5") 'digit-argument)
258 (define-key plomvi-basic-mode-map (kbd "6") 'digit-argument)
259 (define-key plomvi-basic-mode-map (kbd "7") 'digit-argument)
260 (define-key plomvi-basic-mode-map (kbd "8") 'digit-argument)
261 (define-key plomvi-basic-mode-map (kbd "9") 'digit-argument)
262 (define-key plomvi-basic-mode-map (kbd "C-b") 'scroll-down)
263 (define-key plomvi-basic-mode-map (kbd "C-f") 'scroll-up)
264 (define-key plomvi-basic-mode-map (kbd "C-d") 'plomvi-half-scroll)
265 (define-minor-mode plomvi-basic-mode
266 "plomvi mode for read-only buffers; uses `plomvi-basic-mode-map' to
267 implement Vim-Normal-mode-style keybindings."
268 nil " PV" plomvi-basic-mode-map)
270 (defvar plomvi-editable-mode-map (make-sparse-keymap)
271 "Keymap for `plomvi-editable-mode'.
273 Inherits from `plomvi-basic-mode-map', but adds keybindings for text editing
274 and shadows keybindings bound to `self-insert-command' to avoid accidentally
275 typing text outside of what would be Vim's Insert mode.")
276 (set-keymap-parent plomvi-editable-mode-map plomvi-basic-mode-map)
277 (define-key plomvi-editable-mode-map [remap self-insert-command] 'plomvi-nothing)
278 (define-key plomvi-editable-mode-map (kbd "i") 'plomvi-deactivate)
279 (define-key plomvi-editable-mode-map (kbd "x") 'plomvi-x)
280 (define-key plomvi-editable-mode-map (kbd "o") 'plomvi-newline-below)
281 (define-key plomvi-editable-mode-map (kbd "O") 'plomvi-newline-above)
282 (define-key plomvi-editable-mode-map (kbd "r") 'plomvi-replace-char)
283 (define-key plomvi-editable-mode-map (kbd "u") 'undo-only)
284 (define-key plomvi-editable-mode-map (kbd "C-r") 'plomvi-no-redo)
285 ;(define-key plomvi-editable-mode-map (kbd "u") 'plomvi-undo)
286 ;(define-key plomvi-editable-mode-map (kbd "C-r") 'plomvi-redo)
287 (define-key plomvi-editable-mode-map (kbd "I") 'string-insert-rectangle)
288 (define-key plomvi-editable-mode-map (kbd "p") 'plomvi-paste-forward)
289 (define-key plomvi-editable-mode-map (kbd "P") 'plomvi-paste-backward)
290 (define-key plomvi-editable-mode-map (kbd "Y") 'plomvi-copy-line)
291 (define-key plomvi-editable-mode-map (kbd "y") 'plomvi-copy-region)
292 (define-key plomvi-editable-mode-map (kbd "D") 'plomvi-region-kill)
293 (define-prefix-command 'plomvi-d-map)
294 (define-key plomvi-editable-mode-map (kbd "d") 'plomvi-d-map)
295 (define-key plomvi-d-map (kbd "w") 'kill-word)
296 (define-key plomvi-d-map (kbd "$") 'kill-line)
297 (define-key plomvi-d-map (kbd "d") 'kill-whole-line)
298 (define-minor-mode plomvi-editable-mode
299 "plomvi mode for editable buffers; uses `plomvi-editable-mode-map' to
300 shadow `self-insert-command' keybindings and implement Vim-Normal-mode-style
302 nil " PVe" plomvi-editable-mode-map)
304 (define-minor-mode plomvi-mode
305 "Imperfectly emulates a subset of Vim normal mode.
307 Actually encapsulates either `plomvi-basic-mode' or `plomvi-editable-mode'.
308 Use `plomvi-activate' and `plomvi-deactivate' to toggle those.")
310 (defun plomvi-activate ()
311 "Outside mini-buffer, activate `plomvi-mode'.
313 For read only-buffers, activate `plomvi-basic-mode'; else, `plomvi-editable-mode'."
315 (unless (minibufferp)
316 ;(universal-argument)
319 (plomvi-basic-mode 1)
320 (plomvi-editable-mode 1))))
322 (defun plomvi-deactivate()
323 "Outside mini-buffer, deactivate `plomvi-mode'.
325 For read only-buffers, deactivate `plomvi-basic-mode'; else, `plomvi-editable-mode'."
329 (plomvi-basic-mode -1)
330 (plomvi-editable-mode -1)))
332 (define-globalized-minor-mode plomvi-global-mode plomvi-mode plomvi-activate)