boon/boon-arguments.el

287 lines
13 KiB
EmacsLisp
Raw Normal View History

;;; boon-arguments.el --- An Ergonomic Command Mode -*- lexical-binding: t -*-
2014-10-19 14:17:46 +02:00
;;; Commentary:
2016-08-31 21:58:55 +02:00
;; This file defines functions which are intended to be used as
;; 'interactive' specifications: boon-spec-region and
;; boon-spec-enclosure. These are used by boon commands, but can be
;; used by any commands.
;;
;; In this module can also be found functions which are bound in
;; boon-select-map. Those functions return a no-argument lambda which
;; returns a list of boon-regs.
2014-10-19 14:17:46 +02:00
;;; Code:
(require 'boon-core)
2015-10-16 14:29:28 +02:00
(require 'boon-regs)
2016-08-31 21:58:55 +02:00
(require 'boon-utils)
2015-10-24 21:33:38 +02:00
(require 'multiple-cursors)
2016-08-29 20:20:19 +02:00
(require 'dash)
2014-10-19 14:17:46 +02:00
2015-03-19 10:50:55 +01:00
(defcustom boon-enclosures
2014-10-19 14:17:46 +02:00
'(
(?A . ("" ""))
(?a . ("<" ">"))
(?b . ("[" "]"))
(?c . ("{-" "-}"))
2015-03-19 10:50:55 +01:00
(?d . ("\"" "\"")) ;; double quotes
2015-10-16 09:59:31 +02:00
(?D . ("``" "''")) ;; Double quotes
2014-10-19 14:17:46 +02:00
(?f . ("«" "»")) ;; french quotes
(?h . ("#" "#")) ;; hash
2016-09-08 22:06:25 +02:00
(?m . ("`" "'")) ;; mixed
(?o . ("" "")) ;; oxford brackets
2014-10-19 14:17:46 +02:00
(?p . ("(" ")"))
(?q . ("'" "'"))
2016-09-08 22:06:25 +02:00
(?r . ("{" "}")) ;; bRaces
2014-10-19 14:17:46 +02:00
(?t . ("~" "~")) ;; tilda
2015-03-19 10:50:55 +01:00
)
2016-09-08 22:06:25 +02:00
"Enclosures to use with the `boon-enclose' command."
2016-10-30 09:44:58 +01:00
:type '(alist :key-type character :value-type (group (string :tag "Open ") (string :tag "Close")))
:group 'boon)
2014-10-19 14:17:46 +02:00
(defun boon-spec-enclosure ()
2015-10-14 13:59:53 +02:00
"Specify an enclosure style. To be used as an argument to interactive."
2016-09-02 22:25:31 +02:00
(let* ((c (read-char "Specify the enclosure"))
2015-10-15 22:08:59 +02:00
(s (make-string 1 c))
(choice (assoc c boon-enclosures)))
(if choice (cdr choice) (list s s))))
2014-10-19 14:17:46 +02:00
2014-10-19 13:59:12 +02:00
(defun boon-select-from-region (select-fun)
2014-10-23 09:12:59 +02:00
"Return a region list with a single item: the region selected after calling SELECT-FUN (interactively)."
2016-08-27 07:22:09 +02:00
(lambda ()
(save-mark-and-excursion
2016-09-29 09:25:17 +02:00
(call-interactively select-fun)
(boon-regs-from-bounds (cons (region-beginning) (region-end))))))
2014-10-19 13:59:12 +02:00
(defun boon-select-wim () ;; what i mean
2016-08-30 06:40:03 +02:00
"Return a region list with a single item.
This item is either the symbol at point, or, if this fails, the sexp at point."
2014-10-19 13:59:12 +02:00
(interactive)
2016-08-27 07:22:09 +02:00
(lambda () (boon-regs-from-bounds (or (bounds-of-thing-at-point 'symbol)
(bounds-of-thing-at-point 'sexp)))))
2015-10-16 09:59:31 +02:00
2015-11-23 22:44:11 +01:00
(defun boon-select-org-table-cell ()
2016-05-02 15:06:13 +02:00
"Return the region between pipes (|)."
2015-11-23 22:44:11 +01:00
(interactive)
2016-08-22 22:21:14 +02:00
(lambda ()(boon-regs-from-bounds
2015-11-23 22:44:11 +01:00
(cons (save-excursion
(skip-chars-backward "^|") (point))
(save-excursion
(skip-chars-forward "^|") (point))))))
2016-05-02 15:06:13 +02:00
(defun boon-select-justline ()
"Return the region of the current line, without any newline."
2016-08-22 22:21:14 +02:00
(interactive) (boon-regs-from-bounds (cons (line-beginning-position) (line-end-position))))
(defun boon-select-line (count)
2016-08-30 06:40:03 +02:00
"Return a selector of COUNT visual lines."
(interactive "p")
(setq temporary-goal-column 0)
(boon-select-n count 'visible-line))
(defun boon-select-n (count thing)
2016-09-08 21:58:23 +02:00
"Return a region of COUNT THING's."
(lambda() (save-excursion
(let ((bnds (bounds-of-thing-at-point thing)))
(goto-char (cdr bnds))
(forward-thing thing (1- count))
(list (boon-mk-reg (car bnds) (point)))))))
2016-08-25 13:35:23 +02:00
(defun boon-select-document () (interactive) (lambda () (boon-regs-from-bounds (cons (point-min) (point-max)))))
(defun boon-select-paragraph (count) (interactive "p") (boon-select-n count 'paragraph))
(defun boon-select-word (count) (interactive "p") (boon-select-n count 'word))
(defun boon-select-sentence (count) (interactive "p") (boon-select-n count 'sentence))
(defun boon-select-symbol (count) (interactive "p") (boon-select-n count 'symbol))
(defun boon-select-list (count) (interactive "p") (boon-select-n count 'list))
(defun boon-select-sexp (count) (interactive "p") (boon-select-n count 'sexp))
(defun boon-select-whitespace (count) (interactive "p") (boon-select-n count 'whitespace))
(defun boon-select-outside-pairs () (interactive) (boon-select-from-region 'er/mark-outside-pairs))
(defun boon-select-comment () (interactive) (boon-select-from-region 'er/mark-comment))
(defun boon-select-inside-pairs () (interactive) (boon-select-from-region 'er/mark-inside-pairs))
2014-10-19 13:59:12 +02:00
(defun boon-select-outside-quotes () (interactive) (boon-select-from-region 'er/mark-outside-quotes))
(defun boon-select-blanks ()
2016-09-08 21:58:23 +02:00
"Select the blanks around the point, including newlines and tabs."
2014-10-19 13:59:12 +02:00
(interactive)
2016-08-28 19:41:31 +02:00
(lambda ()(boon-regs-from-bounds (cons
2016-09-04 09:16:54 +02:00
(save-excursion
(boon-jump-over-blanks-backward)
(point))
(save-excursion
(boon-jump-over-blanks-forward)
(point))))))
(defun boon-select-block ()
2016-09-08 21:58:23 +02:00
"Select the lines contiguous with the current line and have same indentation or more."
2016-09-04 09:16:54 +02:00
(interactive)
(lambda ()
(boon-regs-from-bounds
(save-excursion
(back-to-indentation)
(setq temporary-goal-column (current-column))
(cons
(save-excursion
(while (and (not (bolp)) (<= (boon-col-relative-to-indent) 0))
(previous-logical-line))
(next-logical-line)
(beginning-of-line)
(point))
(save-excursion
(while (and (not (bolp)) (<= (boon-col-relative-to-indent) 0))
(next-logical-line))
(beginning-of-line)
(point)))))))
2014-10-19 13:59:12 +02:00
2016-08-25 13:35:23 +02:00
(defun boon-spec-string-lazy (prompt)
"Read a string using the region selection functionality.
2016-09-01 20:49:33 +02:00
Intented to be used as an argument to interactive. Returns a
lambda that returns a string. Display PROMPT in the echo
area. Reads a selector and evaluate the selector to fetch a
buffer substring to return. If the character read is a space,
then ask for the string interactively instead."
(let ((head (read-event prompt)))
2016-08-25 13:35:23 +02:00
(if (equal head ? ) (let ((str (read-string (concat prompt ": ")))) (lambda () str))
2016-05-02 15:06:13 +02:00
; if space, read a literal string, otherwise use the region specifier.
2016-08-25 13:35:23 +02:00
(setq unread-command-events (cons head unread-command-events))
2016-09-01 20:49:33 +02:00
(let ((regs (boon-spec-selector prompt)))
2016-08-25 13:35:23 +02:00
(lambda ()
(let ((reg (car (funcall regs))))
(buffer-substring-no-properties (boon-reg-begin reg) (boon-reg-end reg))))))))
2016-08-25 13:35:23 +02:00
(defun boon-select-occurences (what-fun where)
2016-08-30 06:40:03 +02:00
"Return the occurences of WHAT-FUN as sub-regions of WHERE."
2016-09-01 20:49:33 +02:00
(interactive (list (boon-spec-string-lazy "occurences of what?") (boon-spec-selector "where?")))
2016-08-25 13:35:23 +02:00
(lambda ()
(let ((result nil)
(what (funcall what-fun)))
(save-excursion
(dolist (reg (funcall where))
(goto-char (boon-reg-begin reg))
(while (search-forward what (boon-reg-end reg) t)
(setq result (cons (boon-mk-reg (match-beginning 0)
(match-end 0)
(boon-reg-cursor reg))
result))))
result))))
2014-10-19 13:59:12 +02:00
2016-08-29 20:20:19 +02:00
(defun boon-select-all (what where)
"Return a list of empty regions starting at the WHAT subregions of WHERE.
Example: r#<spc>p places a cursor at every begining of line in
2016-08-30 06:40:03 +02:00
the region, in insertion mode. Subregions won't be overlapping."
2016-09-01 20:49:33 +02:00
(interactive (list (boon-spec-selector "what?") (boon-spec-selector "where?")))
2016-08-29 20:20:19 +02:00
(lambda ()
(let ((result nil))
(save-excursion
(dolist (reg (funcall where))
(goto-char (boon-reg-begin reg))
(while (and (< (point) (boon-reg-end reg)))
(let ((subregs (-remove 'boon-reg-nil
(-filter (lambda (r) (> (boon-reg-end r) (point)))
(funcall what)))))
;; some selectors may return nil. (for exmaple sexp on a non-sexp, etc.)
(setq result (append (mapcar (lambda (r) (boon-mk-reg (boon-reg-mark r)
(boon-reg-mark r)))
subregs) result))
(goto-char (apply 'max (+ 1 (point)) (mapcar 'boon-reg-end subregs))))))
result))))
2014-10-19 13:59:12 +02:00
(defun boon-select-borders (how-much regs)
2016-09-08 21:58:23 +02:00
"Return the bordering (of size HOW-MUCH) of a region list REGS."
2016-09-01 20:49:33 +02:00
(interactive (list (prefix-numeric-value current-prefix-arg) (boon-spec-selector "select contents")))
2016-09-08 22:03:25 +02:00
(lambda ()(apply 'append (mapcar (lambda (reg) (boon-borders reg how-much)) (funcall regs)))))
2014-10-19 13:59:12 +02:00
2015-11-09 21:01:47 +01:00
(defun boon-select-with-spaces (regs)
2016-09-08 21:58:23 +02:00
"Return the regions REGS, including some surrounding spaces on one side."
2016-09-01 20:49:33 +02:00
(interactive (list (boon-spec-selector "select with spaces")))
2016-09-08 21:58:23 +02:00
(lambda ()(mapcar (lambda (reg) (boon-include-surround-spaces reg)) (funcall regs))))
2015-11-09 21:01:47 +01:00
2014-10-19 13:59:12 +02:00
(defun boon-select-content (regs)
2016-09-08 21:58:23 +02:00
"Return the contents (of size HOW-MUCH) of a region list REGS."
2016-09-01 20:49:33 +02:00
(interactive (list (boon-spec-selector "select borders")))
2016-09-08 22:03:25 +02:00
(lambda ()(mapcar 'boon-content (funcall regs))))
2014-10-19 13:59:12 +02:00
2015-10-15 23:02:57 +02:00
(defun boon-bypass-mc ()
2015-10-24 21:33:38 +02:00
"Should we bypass multiple cursors when gathering regions?"
2015-10-15 23:02:57 +02:00
(and (bound-and-true-p multiple-cursors-mode)
2016-09-01 21:08:30 +02:00
(or (memq this-command mc--default-cmds-to-run-once)
(memq this-command mc/cmds-to-run-once))))
2015-10-15 22:00:16 +02:00
(defun boon-multiple-cursor-regs ()
2015-10-24 21:33:38 +02:00
"Return all regions defined by multiple-cursors-mode, and outside."
2016-08-20 20:39:08 +02:00
(cons (boon-mk-reg (mark) (point) nil)
2015-10-15 23:02:57 +02:00
(if (boon-bypass-mc)
2015-10-16 14:29:28 +02:00
;; TODO: is marker-position really necessary here?
2016-08-20 20:39:08 +02:00
(mapcar (lambda (o) (boon-mk-reg (marker-position (overlay-get o 'mark)) (marker-position (overlay-get o 'point)) o))
(mc/all-fake-cursors)))))
2015-10-15 22:00:16 +02:00
2014-10-19 13:59:12 +02:00
(defun boon-spec-region (msg)
2014-10-19 14:17:46 +02:00
"Specify a region concisely using the keyboard.
The prompt (as MSG) is displayed. This function returns a list
of regions (See boon-regs.el). If multiple-cursors are enabled
2016-08-23 22:45:42 +02:00
BUT 'this-command' is executed just once (not once per
2016-08-30 06:40:03 +02:00
cursor), you get a region for each cursor."
2015-10-15 23:02:57 +02:00
(let ((orig-regs (boon-multiple-cursor-regs)))
(if (use-region-p) orig-regs
2016-09-01 20:49:33 +02:00
(let ((selector (boon-spec-selector msg)))
(apply 'append
(mapcar (lambda (in-reg)
(save-excursion
(goto-char (boon-reg-point in-reg))
(mapcar (lambda (r) (boon-mk-reg (boon-reg-mark r)
(boon-reg-point r)
(boon-reg-cursor in-reg)))
(funcall selector))))
orig-regs))))))
2016-09-12 20:37:32 +02:00
(defvar boon-selected-by-move nil
2016-09-17 15:57:16 +02:00
"Non nil if the last selection was made by a move, nil otherwise.
2016-09-12 20:37:32 +02:00
When killing, if a selection is made by a move, it make sense to
2016-09-17 15:57:16 +02:00
aggregate the region in the killring, but not so if it was made
2016-09-12 20:37:32 +02:00
by a 'true' selector.")
2016-09-17 15:57:16 +02:00
2016-09-01 20:49:33 +02:00
(defun boon-spec-selector (msg)
"Specify a region selector concisely using the keyboard.
The prompt (as MSG) is displayed. This function returns a
2016-08-23 22:45:42 +02:00
non-interactive function which, when run, will return
2016-08-30 06:40:03 +02:00
bounds. This allows to run the function in question multiple
2016-09-17 15:57:16 +02:00
times, but describe the region just once with the keyboard. This
2016-08-23 22:45:42 +02:00
can be useful when having multiple cursors, or just using
2016-09-17 15:57:16 +02:00
descriptors referring to several subregions. The bounds
that are eventually returned are in the form of a list of regs.
See boon-regs.el."
(let ((my-prefix-arg 0)
(kmv boon-moves-map)
2016-08-23 22:45:42 +02:00
(kms boon-select-map))
;; We read a move or selection, in both keymaps in parallel. First command found wins.
2016-08-23 22:45:42 +02:00
(while (and (or kmv kms) (not (commandp kms)) (not (commandp kmv)))
2016-08-30 06:40:03 +02:00
(let ((last-char (read-event (format "%s %s" msg my-prefix-arg))))
;; read-event, because mc badly advises read-char
(if (and (>= last-char ?0) (<= last-char ?9))
(setq my-prefix-arg (+ (- last-char ?0) (* 10 my-prefix-arg )))
(if kms (setq kms (lookup-key kms (vector last-char))))
(if kmv (setq kmv (lookup-key kmv (vector last-char)))))))
(when (eq my-prefix-arg 0) (setq my-prefix-arg nil))
2016-08-23 22:45:42 +02:00
;; The command is ready; we now execute it (once per cursor if applicable).
(if (or kms kmv)
2016-09-12 20:37:32 +02:00
(prog1
(if (commandp kms)
2016-09-02 22:25:31 +02:00
;; we have a 'selector'. These commands may take prefix
;; args, which they input right away, and return a
;; continuation constructing the region depending on the
;; point/mark.
2016-08-22 22:21:14 +02:00
(let ((current-prefix-arg my-prefix-arg))
(call-interactively kms))
2016-09-02 22:25:31 +02:00
;; we have a 'move'. These commands do not take
;; non-universal arguments. So just run it in the
;; continuation.
(lambda ()
(save-excursion
2016-08-23 22:45:42 +02:00
(let ((orig (point))
(current-prefix-arg my-prefix-arg)) ;; dynamic bindig so env remains clean
(call-interactively kmv)
(list (boon-mk-reg orig (point) nil))))))
2016-09-12 20:37:32 +02:00
(setq boon-selected-by-move (not (commandp kms))))
2016-08-30 06:40:03 +02:00
(error "Unknown selector"))))
2014-10-19 14:17:46 +02:00
(provide 'boon-arguments)
;;; boon-arguments.el ends here