aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTslil Clingman <tslil.clingman@gmail.com>2018-07-05 21:32:37 +0200
committerTslil Clingman <tslil.clingman@gmail.com>2018-07-05 21:32:37 +0200
commitbb9f1fae670eff53f68c47f2f2fcdcad86e7fd01 (patch)
treea732d0907c2b8b8ec15a35f4afde01d7f00e04d9
parent217b6ec5165e803131112c822a33bdf43c14a0de (diff)
Initial version
Updated README.org, included code.
-rw-r--r--README.md25
-rw-r--r--siege-mode.el215
2 files changed, 239 insertions, 1 deletions
diff --git a/README.md b/README.md
index d7c64ec..02803a7 100644
--- a/README.md
+++ b/README.md
@@ -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