aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTslil Clingman <tslil.clingman@gmail.com>2018-07-07 23:50:39 +0200
committerTslil Clingman <tslil.clingman@gmail.com>2018-07-07 23:50:39 +0200
commitefa4b2efff0866f9b3ab45e204f283d6399e9733 (patch)
treef37f06ea766d2da8da685251a80aac3750678754
parent94c3cc8829dbe11550ba702a33b947dc9f68b28f (diff)
Complete rewrite of derivation procedure
siege-mode now tokenizes the input, reverses it, and then applies transformations. This way nested delimiters are automatically reversed, and atomic blocks (like latex {equation}) are not affected. Also, GIF.
-rw-r--r--README.md29
-rw-r--r--siege-mode.el279
-rw-r--r--siege-mode.gifbin0 -> 350193 bytes
3 files changed, 208 insertions, 100 deletions
diff --git a/README.md b/README.md
index 24e8534..ad4c1d5 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,26 @@
# siege-mode
-An emacs minor mode to surround the region with smart delimiters
-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 delimiter for the region. By default the input is
-used as the left delimiter 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`).
+When the region is active, all input is redirected to the minibuffer and treated
+as a delimiter for the region. The input is used as the left delimiter from
+which the right one is derived using `siege-transform-regexs` applied to tokens
+generated by `siege-boundary-list` and `siege-block-*`. If such derivations are
+not desired they may be disabled via "C-c a" in the minibuffer or by default
+(`siege-default-derive`).
-All changes are dynamically displayed in the buffer (see
-`siege-preview-face`) and may be committed by "SPC" or "Ret" in the
-minibuffer.
+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.
+By default siege-mode understands the usual delimeter pairs, as well as latex
+LaTeX begin/end pairs and left/right pairs. Moreover, for ease of use it will
+pair both `_{` and `^{` with `}` by default.
## Fancy GIF demo
-Accepting donations.
+
+![siege-mode.gif](siege-mode.gif)
## License
diff --git a/siege-mode.el b/siege-mode.el
index 14119d6..74a33cb 100644
--- a/siege-mode.el
+++ b/siege-mode.el
@@ -1,10 +1,10 @@
;;; Package --- Surround region with smart delimiters interactively
-;; Time-stamp: <2018-07-05 21:19:22 (tslil)>
+;; Time-stamp: <2018-07-07 23:46:55 (tslil)>
;; Copyright (c) 2018 tslil clingman
;; Author: tslil clingman <tslil@posteo.de>
-;; Version: 1.0
+;; Version: 2.0
;; Package-Requires: ((emacs "24.4"))
;; Keywords: region wrap
@@ -26,61 +26,76 @@
;; Lay siege to the region from all sides!
;;
;; When the region is active, all input is redirected to the minibuffer and
-;; treated as a delimiter for the region. By default the input is used as the
-;; left delimiter 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').
+;; treated as a delimiter for the region. The input is used as the left
+;; delimiter from which the right one is derived using `siege-transform-regexs'
+;; applied to tokens generated by `siege-boundary-list' and `siege-block-*'. If
+;; such derivations are not desired they may be disabled via "C-c a" in the
+;; minibuffer or by default (`siege-default-derive').
;;
;; All changes are dynamically displayed in the buffer (see
;; `siege-preview-face') and may be committed by "SPC" or "Ret" in the
;; minibuffer.
+;;
+;; By default siege-mode understands the usual delimeter pairs, as well as latex
+;; LaTeX begin/end pairs and left/right pairs. Moreover, for ease of use it will
+;; pair both _{ and ^{ with } by default.
;;; Code:
(require 'subr-x)
+(require 'cl-lib)
-(defcustom siege-left-to-right-regexs '( ("(" . ")")
- ("\\[" . "]")
- ("{" . "}")
- ("<" . ">")
- ("`" . "'")
- ("``" . "''")
- ("left" . "right")
- ("langle" . "rangle")
- ("begin" . "end") )
- "List of pairs (REGEX . REPLACE) used to generate the RIGHT delimiter from \
-the LEFT one. The list is traversed in order and every substitution is applied."
+(defcustom siege-transform-regexs '(("({" . "})")
+ ("(" . ")")
+ ("\\[" . "]")
+ ("[_^]?{" . "}")
+ ("<" . ">")
+ ("`" . "'")
+ ("``" . "''")
+ ("\\\\left" . "\\\\right")
+ ("\\\\langle" . "\\\\rangle")
+ ("\\\\begin" . "\\\\end"))
+ "List of pairs (REGEX . REPLACE) used to generate the right delimiter 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 delimiter from \
-the RIGHT one. The list is traversed in order and every substitution is applied."
+(defcustom siege-boundary-list '("\\\\left\\\\{"
+ "\\\\left[({<\\[]"
+ "[_^]?{"
+ "[([<`*_/ ]" )
+ "List of regexes giving tokens at which the input delimiter should be split \
+so that the resulting substrings may be transformed. See \
+`siege--tokenize-non-block' for details."
:group 'siege-mode
- :type '(repeat (cons string string)))
+ :type '(list string))
-(defcustom siege-default-left t
- "Whether siege mode should default to deriving the right delimiter \
-from the input."
+(defcustom siege-block-open ?{
+ "Character signifying the start of an atomic block in delimiter input. \
+Such blocks will appear verbatim in the derived delimiter."
:group 'siege-mode
- :type 'bool)
+ :type 'char)
-(defcustom siege-default-apply-regexs t
- "Whether siege mode should default to deriving the matching delimiter \
-from the input using the regexs given in `siege-right-to-left-regexs' \
-and `siege-left-to-right-regexs'."
+(defcustom siege-block-close ?}
+ "Character signifying the end of an atomic block in delimiter input. \
+Such blocks will appear verbatim in the derived delimiter."
:group 'siege-mode
- :type 'bool)
+ :type 'char)
+(defcustom siege-block-prefix "\\\\begin{\\|{"
+ "Regex matching the start of an atomic block in the delimiter input, \
+`siege-block-open', or an optional prefix as well as that character.
+
+Such prefixes will have their relative orientation to the block preserved in \
+the derived delimiter, but will still be subject to the transformations given \
+in `siege-transform-regexs'."
+ :group 'siege-mode
+ :type 'string)
+
+(defcustom siege-default-derive t
+ "Whether siege-mode should default to deriving the matching delimiter \
+from the input or reproduce it on both sides verbatim."
+ :group 'siege-mode
+ :type 'bool)
(defface siege-preview-face '((t :inherit (warning)))
"Face in which to render delimiter previews for Siege mode."
@@ -88,41 +103,137 @@ and `siege-left-to-right-regexs'."
(defvar siege--overlay nil)
(defvar siege--selected-window nil)
-(defvar siege--apply-regexs t)
-(defvar siege--is-left t)
+(defvar siege--derive t)
+(defvar siege--boundary "")
(defvar siege--hist '())
-(defun siege--toggle-end ()
- "Swap the side for which the input is considered a delimiter and \
-from which the matching delimiter is derived, for \
-the currently running instance. See also `siege-default-left'."
+(defun siege--toggle-derive ()
+ "Toggle the automatic derivation of a matching delimiter for \
+the currently running instance. See also `siege-default-derive'."
(interactive)
- (setq siege--is-left (not siege--is-left))
- (minibuffer-message "Currently entering %s delimiter."
- (if siege--is-left "LEFT" "RIGHT")))
+ (setq siege--derive (not siege--derive))
+ (minibuffer-message "%s right delimeter."
+ (if siege--derive "DERIVING" "NOT deriving")))
-(defun siege--toggle-regex ()
- "Toggle the use of regexs in deriving a matching delimiter 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--extract-next-block (string start open-chr close-chr
+ prefix-match)
+ "Given a STRING and a START index, find the next block \
+delimeted by balanced pairs of OPEN-CHR CLOSE-CHR, optionally \
+prefixed by anything matching PREFIX-MATCH.
+
+PREFIX-MATCH is assumed to match against OPEN-CHR. Returns the list\
+ (prefix-start block-start block-end)"
+ (let ((pos start) pre beg end (count 1) curr )
+ (save-match-data
+ (when (setq pre (string-match prefix-match string start))
+ (setq beg (1- (match-end 0))
+ pos (match-end 0))
+ (while (and (not end) (< pos (length string)))
+ (setq curr (aref string pos))
+ (when (char-equal curr open-chr) (incf count))
+ (when (char-equal curr close-chr) (decf count))
+ (incf pos)
+ (when (= count 0) (setq end pos)))))
+ (when end (list pre beg end))))
+
+(defun siege--tokenize-blocks (string open-char close-char
+ prefix-match)
+ "Given a STRING, produce a list of pairs (type . substring). Type is one of \
+'block or 'text. Blocks are substrings delimeted by balanced pairs of \
+OPEN-CHAR and CLOSE-CHAR, text is everything else. The list is in reverse \
+order of occurence, with the exception of block prefixes (see \
+`siege--extract-next-block' and PREFIX-MATCH) whose relative positioning \
+to the blocks the blocks they abut is preserved."
+ (let (result (start 0) block)
+ (while start
+ (if (setq block (siege--extract-next-block
+ string start open-char close-char prefix-match))
+ (progn
+ (when (/= start (nth 0 block))
+ (push (cons 'text
+ (substring-no-properties
+ string start (nth 0 block)))
+ result))
+ (push (cons 'block (substring-no-properties
+ string (nth 1 block) (nth 2 block)))
+ result)
+ (when (/= (nth 0 block) (nth 1 block))
+ (push (cons 'text (substring-no-properties
+ string (nth 0 block) (nth 1 block)))
+ result))
+ (setq start (nth 2 block)))
+ (if (/= start (length string))
+ (push (cons 'text (substring string start)) result))
+ (setq start nil)))
+ result))
+
+(defun siege--tokenize-non-block (string boundary-regex)
+ "Given a STRING, split the string on anything matching \
+BOUNDARY-REGEX and collate the results in reverse order. \
+Splitting tokens appear in output."
+ (let (result (start 0))
+ (save-match-data
+ (while start
+ (if (string-match boundary-regex string start)
+ (progn
+ (when (/= start (match-beginning 0))
+ (push (substring-no-properties
+ string start (match-beginning 0))
+ result))
+ (push (substring-no-properties
+ string (match-beginning 0) (match-end 0))
+ result)
+ (setq start (match-end 0)))
+ (when (/= start (length string))
+ (push (substring-no-properties string start) result))
+ (setq start nil))))
+ result))
+
+(defun siege--derive-delimiter (string
+ block-open block-close
+ block-prefix-match
+ boundary-regex transforms)
+ "Given a STRING, BLOCK-OPEN and BLOCK-CLOSE characters, and a \
+BLOCK-PREFIX-MATCH regex matching against BLOCK-OPEN, this function first \
+tokenizes the string into blocks and text (see `siege--tokenize-blocks'). The \
+text is then further tokenized on boundaries given by BOUNDARY-REGEX (see \
+`siege--tokenize-non-block') and finally all text tokens (not blocks) are \
+transformed as follows and collated to generate the output.
+
+TRANSFORMS is a list of pairs of strings (regex . subst) whics is applied \
+sequentially to every text token."
+ (apply
+ #'concat
+ (mapcar
+ #'(lambda (pair)
+ (if (eq 'block (car pair)) (cdr pair)
+ (apply
+ #'concat
+ (mapcar
+ #'(lambda (str)
+ (let ((res str))
+ (dolist (tr transforms res)
+ (setq
+ res
+ (replace-regexp-in-string (car tr) (cdr tr) res)))))
+ (siege--tokenize-non-block (cdr pair) boundary-regex)))))
+ (siege--tokenize-blocks string block-open block-close
+ block-prefix-match))))
+
+(defun siege--make-boundary-regex-from-list (list)
+ "LIST is a list of strings which are concatenated, separated in the output \
+by regex optional match strings (\\|)"
+ (let ((res "")) (dolist (r list (substring res 0 (- (length res) 2)))
+ (setq res (concat res r "\\|")))))
(defun siege--matching-pair (input)
- "Generate a matching delimiter 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)))
+ "Generate a matching delimiter from INPUT according to all siege-* variables."
+ (if siege--derive
+ (let ((output (siege--derive-delimiter
+ input siege-block-open siege-block-close
+ siege-block-prefix siege--boundary
+ siege-transform-regexs)))
+ (cons input output))
(cons input input)))
(defun siege--preview-input ()
@@ -152,8 +263,7 @@ according to \
(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)
+ (define-key map (kbd "C-c a") 'siege--toggle-derive)
map))
(defun siege--interactive (initial)
@@ -163,36 +273,35 @@ 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)
+ siege--derive siege-default-derive
+ siege--boundary (siege--make-boundary-regex-from-list
+ siege-boundary-list))
(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 delimiter (default %s): " default)
- initial siege-minibuffer-map
- nil 'siege--hist default)))
+ (read-from-minibuffer
+ "Enter left delimiter: "
+ initial siege-minibuffer-map
+ nil 'siege--hist)))
(pair (siege--matching-pair result)))
(siege--preview-end)
(goto-char start)
(insert (car pair))
- (goto-char (+ end (length (cdr pair))))
+ (goto-char (+ end (string-width (car 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 delimiter for the region. By default the input \
-is used as the left delimiter 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.
+When the region is active, all input is redirected to the \
+minibuffer and treated as a delimiter for the region. By default \
+the input is used as the left delimiter from which the right one \
+is derived according to all siege-* variables. See also \
+`siege--derive-delimiter'. ARG is ignored.
If the region is not active then ARG is passed on to `self-insert-command'."
(interactive "p")
diff --git a/siege-mode.gif b/siege-mode.gif
new file mode 100644
index 0000000..4f12136
--- /dev/null
+++ b/siege-mode.gif
Binary files differ