diff options
| author | Tslil Clingman <tslil.clingman@gmail.com> | 2018-07-05 21:32:37 +0200 |
|---|---|---|
| committer | Tslil Clingman <tslil.clingman@gmail.com> | 2018-07-05 21:32:37 +0200 |
| commit | bb9f1fae670eff53f68c47f2f2fcdcad86e7fd01 (patch) | |
| tree | a732d0907c2b8b8ec15a35f4afde01d7f00e04d9 | |
| parent | 217b6ec5165e803131112c822a33bdf43c14a0de (diff) | |
Initial version
Updated README.org, included code.
| -rw-r--r-- | README.md | 25 | ||||
| -rw-r--r-- | siege-mode.el | 215 |
2 files changed, 239 insertions, 1 deletions
@@ -1,2 +1,25 @@ # siege-mode -An emacs minor mode to surround the region with smart delimeters interactively +An emacs minor mode to surround the region with smart delimiters +interactively. + +## Lay siege to the region from all sides! +### (with the power of regular expressions) + +When the region is active, all input is redirected to the minibuffer +and treated as a delimeter for the region. By default the input is +used as the left delimeter from which the right one is derived using +`siege-left-to-right-regexs'. This may be reversed by default +(`siege-default-left') or during usage via "C-c r" in the minibuffer. +If regexes are not desired they may be disabled via "C-c a" in the +minibuffer or by default (`siege-default-apply-regexs'). + +All changes are dynamically displayed in the buffer (see +`siege-preview-face') and may be committed by "SPC" or "Ret" in the +minibuffer. + +The defaults support the obvious pairings, as well as `begin <--> end' +and `left <--> right' of LaTeX fame. + +## Fancy GIF demo +Accepting donations. + diff --git a/siege-mode.el b/siege-mode.el new file mode 100644 index 0000000..c0cf200 --- /dev/null +++ b/siege-mode.el @@ -0,0 +1,215 @@ +;;; Package --- Surround region with smart delimeters interactively +;; Time-stamp: <2018-07-05 21:19:22 (tslil)> + +;; Copyright (c) 2018 tslil clingman + +;; Author: tslil clingman <tslil@posteo.de> +;; Version: 1.0 +;; Package-Requires: ((emacs "24.4")) +;; Keywords: region wrap + +;;; License: + +;; siege-mode 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, or (at your option) any later version. +;; +;; siege-mode 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 +;; siege-mode. If not, see http://www.gnu.org/licenses. + +;;; Commentary: + +;; Lay siege to the region from all sides! +;; +;; When the region is active, all input is redirected to the minibuffer and +;; treated as a delimeter for the region. By default the input is used as the +;; left delimeter from which the right one is derived using +;; `siege-left-to-right-regexs'. This may be reversed by default +;; (`siege-default-left') or during usage via "C-c r" in the minibuffer. If +;; regexes are not desired they may be disabled via "C-c a" in the minibuffer +;; or by default (`siege-default-apply-regexs'). +;; +;; All changes are dynamically displayed in the buffer (see +;; `siege-preview-face') and may be committed by "SPC" or "Ret" in the +;; minibuffer. + +;;; Code: +(require 'subr-x) + +(defcustom siege-left-to-right-regexs '( ("(" . ")") + ("\\[" . "]") + ("{" . "}") + ("<" . ">") + ("`" . "'") + ("``" . "''") + ("left" . "right") + ("langle" . "rangle") + ("begin" . "end") ) + "List of pairs (REGEX . REPLACE) used to generate the RIGHT delimeter from \ +the LEFT one. The list is traversed in order and every substitution is applied." + :group 'siege-mode + :type '(repeat (cons string string))) + +(defcustom siege-right-to-left-regexs '( (")" . "(") + ("\\]" . "[") + ("}" . "{") + (">" . "<") + ("'" . "`") + ("''" . "``") + ("right" . "left") + ("rangle" . "langle") + ("end" . "begin") ) + "List of pairs (REGEX . REPLACE) used to generate the LEFT delimeter from \ +the RIGHT one. The list is traversed in order and every substitution is applied." + :group 'siege-mode + :type '(repeat (cons string string))) + +(defcustom siege-default-left t + "Whether siege mode should default to deriving the right delimeter \ +from the input." + :group 'siege-mode + :type 'bool) + +(defcustom siege-default-apply-regexs t + "Whether siege mode should default to deriving the matching delimeter \ +from the input using the regexs given in `siege-right-to-left-regexs' \ +and `siege-left-to-right-regexs'." + :group 'siege-mode + :type 'bool) + + +(defface siege-preview-face '((t :inherit (warning))) + "Face in which to render delimeter previews for Siege mode." + :group 'siege-mode) + +(defvar siege--overlay nil) +(defvar siege--selected-window nil) +(defvar siege--apply-regexs t) +(defvar siege--is-left t) +(defvar siege--hist '()) + +(defun siege--toggle-end () + "Swap the side for which the input is considered a delimeter and \ +from which the matching delimeter is derived, for \ +the currently running instance. See also `siege-default-left'." + (interactive) + (setq siege--is-left (not siege--is-left)) + (minibuffer-message "Currently entering %s delimeter." + (if siege--is-left "LEFT" "RIGHT"))) + +(defun siege--toggle-regex () + "Toggle the use of regexs in deriving a matching delimeter for \ +the currently running instance. See also `siege-default-apply-regexs'." + (interactive) + (setq siege--apply-regexs (not siege--apply-regexs)) + (minibuffer-message "%s regexs on input." + (if siege--apply-regexs "APPLYING" "NOT applying"))) + +(defun siege--matching-pair (input) + "Generate a matching delimeter from INPUT using \ +`siege-left-to-right-regexs' and `siege-right-to-left-regexs' \ +according to \ +`siege-default-left' and `siege-default-apply-regexs'" + (if siege--apply-regexs + (let ((pairs (if siege--is-left siege-left-to-right-regexs + siege-right-to-left-regexs)) + (output input)) + (cl-dolist (expr pairs) + (setq output + (replace-regexp-in-string (car expr) (cdr expr) output))) + (if siege--is-left (cons input output) + (cons output input))) + (cons input input))) + +(defun siege--preview-input () + "Render the siege delimeters in the buffer using overlays." + (let ((inp (minibuffer-contents))) + (if (string-blank-p inp) + (siege--preview-end) + (with-selected-window siege--selected-window + (let ((beg (region-beginning)) + (end (region-end))) + (if (overlayp siege--overlay) + (move-overlay siege--overlay beg end) + (setq siege--overlay (make-overlay beg end)) + (overlay-put siege--overlay 'sieging t)) + (let ((pair (siege--matching-pair inp))) + (overlay-put siege--overlay 'before-string + (propertize (car pair) + 'face 'siege-preview-face)) + (overlay-put siege--overlay 'after-string + (propertize (cdr pair) + 'face 'siege-preview-face)))))))) + +(defun siege--preview-end () + "Gracefully remove the siege preview overlay." + (with-selected-window siege--selected-window + (remove-overlays (buffer-end -1) (buffer-end 1) 'sieging t))) + +(defvar siege-minibuffer-map + (let ((map minibuffer-local-ns-map)) + (define-key map (kbd "C-c r") 'siege--toggle-end) + (define-key map (kbd "C-c a") 'siege--toggle-regex) + map)) + +(defun siege--interactive (initial) + "Use to enter the interactive delimeter building for region. + +INITIAL is the string present in the minibuffer, which is not necessarily\ +the default value." + (barf-if-buffer-read-only) + (setq siege--selected-window (selected-window) + siege--is-left siege-default-left + siege--apply-regexs siege-default-apply-regexs) + (exchange-point-and-mark) + (let* ((start (region-beginning)) + (end (region-end)) + (default (or (car siege--hist) "")) + (result (minibuffer-with-setup-hook + (lambda () + (add-hook 'post-command-hook #'siege--preview-input nil t) + (add-hook 'minibuffer-exit-hook #'siege--preview-end nil t) + (siege--preview-input)) + (read-from-minibuffer (format "Enter delimeter (default %s): " default) + initial siege-minibuffer-map + nil 'siege--hist default))) + (pair (siege--matching-pair result))) + (siege--preview-end) + (goto-char start) + (insert (car pair)) + (goto-char (+ end (length (cdr pair)))) + (insert (cdr pair)))) + +(defun siege--self-insert (arg) + "This function replaces `self-insert-command'. + +When the region is active, all input is redirected to the minibuffer \ +and treated as a delimeter for the region. By default the input \ +is used as the left delimeter from which the right one is \ +derived using `siege-left-to-right-regexs'. See also \ +`siege-default-left' and `siege-right-to-left-regexs'. ARG is \ +ignored. + +If the region is not active then ARG is passed on to `self-insert-command'." + (interactive "p") + (if (and (region-active-p) (characterp last-command-event)) + (siege--interactive (char-to-string last-command-event)) + (self-insert-command arg))) + +(defvar siege-mode-map + (let ((map (make-sparse-keymap))) + (define-key map [remap self-insert-command] 'siege--self-insert) + map)) + +(define-minor-mode siege-mode + "Siege minor mode." + nil + " siege" siege-mode-map + :global true) + +(provide 'siege-mode) +;;; siege-mode.el ends here |
