From: Christian Heller Date: Sat, 9 Feb 2019 01:33:04 +0000 (+0100) Subject: Initial commit. X-Git-Url: https://plomlompom.com/repos/%7B%7B%20web_path%20%7D%7D/cards/%7B%7Bcard_id%7D%7D/view?a=commitdiff_plain;h=9eb4fc35c9682fb151afa75333c507d52161388f;p=plomvi.el Initial commit. --- 9eb4fc35c9682fb151afa75333c507d52161388f diff --git a/plomvi.el b/plomvi.el new file mode 100644 index 0000000..19b4bab --- /dev/null +++ b/plomvi.el @@ -0,0 +1,332 @@ +;;; plomvi.el --- poor man's vim emulation + +;; Copyright (C) 2019 + +;; Author: Christian Heller + +;; 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 . + +;;; Commentary: + +;; A very imperfect simulation of a small subset of Vim behavior, far +;; from the sophistication of packages like evil; an intentionally +;; thin wrapper around native Emacs behavior instead. To the author, +;; it mostly serves to both avoid muscle memory context switches +;; between Emacs and most basic Vim usages, and at the same time avoid +;; the conceptual distances evil puts between the user and native +;; Emacs logic. It does not care though to keep default Emacs +;; keybindings intact, overwriting some common ones with those from +;; the Vim universe. + +;;; Instructions: + +;; 1. load this script into your init file: (load ...) + +;; 2. to start plomvi by default, put this into your init file: +;; (plomvi-global-mode 1) + +;; 3. define some otherwise unused keybinding to simulate what would +;; be a jump back from Insert mode to Normal mode in Vim (but is de +;; facto just a plomvi mode activation), such as this: +;; (global-set-key (kbd "") 'plomvi-activate) + + + +(defun plomvi-nothing() + "Do nothing. Used to shadow self-insert bindings in `plomvi-editable-mode-map'." + (interactive)) + +(defun plomvi-half-scroll() + "Scroll down half a screen width." + (interactive) + (scroll-up (/ (window-height) 2))) + +(defun plomvi-goto-line (count) + "Jump to line: on nil count, last one, else count." + (interactive "P") + (if (null count) + (goto-char (point-max)) + (goto-char (point-min)) + (forward-line (1- count)))) + +(defun plomvi-prefix-zero-or-line-start (prev-prefix) + "If no prefix so far, jump to start of line, else start new prefix with 0." + (interactive "P") + (if (null prev-prefix) + (beginning-of-line) + (setq prefix-arg 0))) + +(defun plomvi-prompt (prompt-input) + "Provide super-basic : prompt that only accepts: +q +q! +w +w FILENAME +wq +vsplit +split + +If search and replace syntax is detected, it recommends using `query-replace' +instead. +" + (interactive "M:") + (cond + ((string= prompt-input "q!") + (kill-emacs)) + ((string= prompt-input "q") + (save-buffers-kill-emacs)) + ((string= prompt-input "w") + (save-buffer)) + ((string-match "^w [^ ]+" prompt-input) + (let ((file-name (substring prompt-input 2))) (write-file file-name))) + ((string-match "^%?s/" prompt-input) + (message "NOT IMPLEMENTED, consider using query-replace(-regexp)")) + ((string= prompt-input "wq") + ((lambda () (save-some-buffers t) (kill-emacs)))) + ((string= prompt-input "vsplit") + (split-window-horizontally)) + ((string= prompt-input "split") + (split-window-vertically)) + (t (message "NOT IMPLEMENTED")))) + +(defun plomvi-newline-above () + "Open and jump into new line above current line, deactivate `plomvi-mode'." + (interactive) + (beginning-of-line) + (insert "\n") + (previous-line) + (plomvi-deactivate)) + +(defun plomvi-newline-below () + "Open and jump into new line below current line, deactivate `plomvi-mode'." + (interactive) + (end-of-line) + (insert "\n") + (plomvi-deactivate)) + +(defun plomvi-paste-backward () + "Paste last kill leftwards in current line, or (if kill ends in \n) above it. + +Note that this ignores killed rectangles. +" + (interactive) + (if (eq nil (string-match "\n$" (current-kill 0))) + (yank) + (beginning-of-line) + (yank) + (previous-line))) + +(defun plomvi-paste-forward () + "Paste last kill rightwards in current line, or (if kill ends in \n) under it. + +Note that this ignores killed rectangles." + (interactive) + (if (eq nil (string-match "\n$" (current-kill 0))) + (progn + (right-char) + (yank)) + (end-of-line) + (right-char) + (yank) + (previous-line))) + +(defun plomvi-region-kill() + "Kill marked region." + (interactive) + (kill-region (region-beginning) (region-end))) + +(defun plomvi-x() + "If rectangle or region marked, kill those; else, kill char after point." + (interactive) + (cond + ((and (boundp 'rectangle-mark-mode) (eq t rectangle-mark-mode)) + (kill-rectangle (region-beginning) (region-end))) + ((use-region-p) + (plomvi-region-kill)) + (t + (delete-char 1)))) + +(defun plomvi-rectangle-mark() + "Start marked rectangle, move right one char so a single column is visible." + (interactive) + (push-mark (point) nil t) + (rectangle-mark-mode) + (right-char)) + +(defun plomvi-search-forward() + "Find next occurence of search string last targeted by isearch." + (interactive) + (search-forward isearch-string)) + +(defun plomvi-search-backward() + "Find previous occurence of search string last targeted by isearch." + (interactive) + (search-backward isearch-string)) + + +(defun plomvi-copy-line() + "Copy current line into kill buffer." + (interactive) + (let ((keep_pos (point))) ; We sort of cheat: We kill the line, then we + (kill-whole-line) ; paste it back, and return point to its + (plomvi-paste-backward) ; original position. + (goto-char keep_pos))) ; + +(defun plomvi-copy-region() + "Copy marked region." + (interactive) + (copy-region-as-kill (region-beginning) (region-end))) + +(defun plomvi-replace-char (c) + "Replace char after point with c." + (interactive "cplomvi-replace-char") + (delete-char 1) (insert-char c) (left-char)) + +;;; some attempt at a redo feature, not very successful, documented here for +;;; research purposes +; +;(setq plomvi-in-redo nil) ; should be made buffer-local +;(setq plomvi-undo-count 0) ; should be made buffer-local +;(defun plomvi-undo() +; (interactive) +; (undo-only) +; (setq plomvi-in-redo nil) +; (setq plomvi-undo-count (+ plomvi-undo-count 1))) +;(defun plomvi-redo() +; (interactive) +; (if (> plomvi-undo-count 0) +; (progn +; (if (null plomvi-in-redo) +; (progn +; (insert-char ?\s 1) +; (undo) +; (setq plomvi-in-redo t))) +; (progn +; (undo) +; (setq plomvi-undo-count (- plomvi-undo-count 1)))))) + +(defun plomvi-no-redo() + "Tell user what to do, since implementing vim redo was too much of a hassle." + (interactive) + (message "Vim-style redo not available. Try M-x for Emacs' undo-undo.")) + + +(defvar plomvi-basic-mode-map (make-sparse-keymap) + "Keymap for `plomvi-basic-mode', to be used on read-only buffers. + +In contrast to the keymap `plomvi-editable-mode' for editable buffers, +this not only excludes keybindings for editing text, but also does not +shadow keybindings that are bound to `self-insert-command'. + +Thus, it on the whole shadows much fewer keybindings of other keymaps +that can therefore be used for other purposes.") +(define-key plomvi-basic-mode-map (kbd ":") 'plomvi-prompt) +(define-key plomvi-basic-mode-map (kbd "C-w") 'other-window) +(define-key plomvi-basic-mode-map (kbd "k") 'previous-line) +(define-key plomvi-basic-mode-map (kbd "j") 'next-line) +(define-key plomvi-basic-mode-map (kbd "h") 'left-char) +(define-key plomvi-basic-mode-map (kbd "l") 'right-char) +(define-key plomvi-basic-mode-map (kbd "w") 'forward-word) +(define-key plomvi-basic-mode-map (kbd "b") 'backward-word) +(define-key plomvi-basic-mode-map (kbd "/") 'isearch-forward) +(define-key plomvi-basic-mode-map (kbd "N") 'plomvi-search-backward) +(define-key plomvi-basic-mode-map (kbd "n") 'plomvi-search-forward) +(define-key plomvi-basic-mode-map (kbd "v") 'set-mark-command) +(define-key plomvi-basic-mode-map (kbd "C-v") 'plomvi-rectangle-mark) +(define-prefix-command 'plomvi-g-map) +(define-key plomvi-basic-mode-map (kbd "g") 'plomvi-g-map) +(define-key plomvi-g-map (kbd "g") 'beginning-of-buffer) +(define-key plomvi-basic-mode-map (kbd "G") 'plomvi-goto-line) +(define-key plomvi-basic-mode-map (kbd "$") 'end-of-line) +(define-key plomvi-basic-mode-map (kbd "0") 'plomvi-prefix-zero-or-line-start) +(define-key plomvi-basic-mode-map (kbd "1") 'digit-argument) +(define-key plomvi-basic-mode-map (kbd "2") 'digit-argument) +(define-key plomvi-basic-mode-map (kbd "3") 'digit-argument) +(define-key plomvi-basic-mode-map (kbd "4") 'digit-argument) +(define-key plomvi-basic-mode-map (kbd "5") 'digit-argument) +(define-key plomvi-basic-mode-map (kbd "6") 'digit-argument) +(define-key plomvi-basic-mode-map (kbd "7") 'digit-argument) +(define-key plomvi-basic-mode-map (kbd "8") 'digit-argument) +(define-key plomvi-basic-mode-map (kbd "9") 'digit-argument) +(define-key plomvi-basic-mode-map (kbd "C-b") 'scroll-down) +(define-key plomvi-basic-mode-map (kbd "C-f") 'scroll-up) +(define-key plomvi-basic-mode-map (kbd "C-d") 'plomvi-half-scroll) +(define-minor-mode plomvi-basic-mode + "plomvi mode for read-only buffers; uses `plomvi-basic-mode-map' to +implement Vim-Normal-mode-style keybindings." + nil " PV" plomvi-basic-mode-map) + +(defvar plomvi-editable-mode-map (make-sparse-keymap) + "Keymap for `plomvi-editable-mode'. + +Inherits from `plomvi-basic-mode-map', but adds keybindings for text editing +and shadows keybindings bound to `self-insert-command' to avoid accidentally +typing text outside of what would be Vim's Insert mode.") +(set-keymap-parent plomvi-editable-mode-map plomvi-basic-mode-map) +(define-key plomvi-editable-mode-map [remap self-insert-command] 'plomvi-nothing) +(define-key plomvi-editable-mode-map (kbd "i") 'plomvi-deactivate) +(define-key plomvi-editable-mode-map (kbd "x") 'plomvi-x) +(define-key plomvi-editable-mode-map (kbd "o") 'plomvi-newline-below) +(define-key plomvi-editable-mode-map (kbd "O") 'plomvi-newline-above) +(define-key plomvi-editable-mode-map (kbd "r") 'plomvi-replace-char) +(define-key plomvi-editable-mode-map (kbd "u") 'undo-only) +(define-key plomvi-editable-mode-map (kbd "C-r") 'plomvi-no-redo) +;(define-key plomvi-editable-mode-map (kbd "u") 'plomvi-undo) +;(define-key plomvi-editable-mode-map (kbd "C-r") 'plomvi-redo) +(define-key plomvi-editable-mode-map (kbd "I") 'string-insert-rectangle) +(define-key plomvi-editable-mode-map (kbd "p") 'plomvi-paste-forward) +(define-key plomvi-editable-mode-map (kbd "P") 'plomvi-paste-backward) +(define-key plomvi-editable-mode-map (kbd "Y") 'plomvi-copy-line) +(define-key plomvi-editable-mode-map (kbd "y") 'plomvi-copy-region) +(define-key plomvi-editable-mode-map (kbd "D") 'plomvi-region-kill) +(define-prefix-command 'plomvi-d-map) +(define-key plomvi-editable-mode-map (kbd "d") 'plomvi-d-map) +(define-key plomvi-d-map (kbd "w") 'kill-word) +(define-key plomvi-d-map (kbd "$") 'kill-line) +(define-key plomvi-d-map (kbd "d") 'kill-whole-line) +(define-minor-mode plomvi-editable-mode + "plomvi mode for editable buffers; uses `plomvi-editable-mode-map' to +shadow `self-insert-command' keybindings and implement Vim-Normal-mode-style +keybindings." + nil " PVe" plomvi-editable-mode-map) + +(define-minor-mode plomvi-mode + "Imperfectly emulates a subset of Vim normal mode. + +Actually encapsulates either `plomvi-basic-mode' or `plomvi-editable-mode'. +Use `plomvi-activate' and `plomvi-deactivate' to toggle those.") + +(defun plomvi-activate () + "Outside mini-buffer, activate `plomvi-mode'. + +For read only-buffers, activate `plomvi-basic-mode'; else, `plomvi-editable-mode'." + (interactive) + (unless (minibufferp) + ;(universal-argument) + (plomvi-mode 1) + (if buffer-read-only + (plomvi-basic-mode 1) + (plomvi-editable-mode 1)))) + +(defun plomvi-deactivate() + "Outside mini-buffer, deactivate `plomvi-mode'. + +For read only-buffers, deactivate `plomvi-basic-mode'; else, `plomvi-editable-mode'." + (interactive) + (plomvi-mode -1) + (if buffer-read-only + (plomvi-basic-mode -1) + (plomvi-editable-mode -1))) + +(define-globalized-minor-mode plomvi-global-mode plomvi-mode plomvi-activate)