boon/boon-arguments.el

268 lines
12 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-20 20:39:08 +02:00
2014-10-19 14:17:46 +02:00
;;; Code:
(require 'boon-core)
2015-10-16 14:29:28 +02:00
(require 'boon-regs)
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
(?m . ("`" "'"))
(?p . ("(" ")"))
(?q . ("'" "'"))
(?r . ("{" "}"))
(?o . ("" "")) ;; oxford brackets
(?t . ("~" "~")) ;; tilda
2015-03-19 10:50:55 +01:00
)
2015-10-15 23:02:57 +02:00
"Enclosures to use with the boon-enclose command."
2015-03-19 10:50:55 +01:00
:type '(alist :key-type character :value-type (list string))
: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."
2015-10-16 14:31:03 +02:00
(let* ((c (boon-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-thing-at-point (thing)
2014-10-23 09:12:59 +02:00
"Return a region list with a single item pointing to the THING at point."
2016-08-22 22:21:14 +02:00
(lambda ()(boon-regs-from-bounds (bounds-of-thing-at-point thing))))
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-excursion
;; FIXME: deactivate mark
(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-27 07:22:09 +02:00
"Return a region list with a single item: 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-09 20:29:16 +01:00
(defun boon-jump-over-blanks-forward ()
2014-10-23 09:12:59 +02:00
"Jump over blanks, forward."
2014-10-19 14:17:46 +02:00
(interactive)
(skip-chars-forward "\n\t "))
(defun boon-jump-over-blanks-backward ()
2014-10-23 09:12:59 +02:00
"Jump over blanks, backward."
2014-10-19 14:17:46 +02:00
(interactive)
(skip-chars-backward "\n\t "))
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)
"Return a region of COUNT visual lines."
(interactive "p")
(setq temporary-goal-column 0)
(boon-select-n count 'beginning-of-visual-line 'line-move-visual))
(defun boon-select-n (count goto-beginning forward-n)
"Return a region of COUNT objects defined by GOTO-BEGINNING and FORWARD-N."
(lambda()(save-excursion
(funcall goto-beginning)
2016-08-22 22:21:14 +02:00
(boon-regs-from-bounds (cons (point) (progn (funcall forward-n count) (point)))))))
(defun boon-select-paragraph (count) (interactive "p") (boon-select-n count 'start-of-paragraph-text 'forward-paragraph))
2016-08-25 13:35:23 +02:00
(defun boon-select-document () (interactive) (lambda () (boon-regs-from-bounds (cons (point-min) (point-max)))))
2014-10-19 13:59:12 +02:00
(defun boon-select-word () (interactive) (boon-select-thing-at-point 'word))
(defun boon-select-sentence () (interactive) (boon-select-thing-at-point 'sentence))
(defun boon-select-symbol () (interactive) (boon-select-thing-at-point 'symbol))
(defun boon-select-list () (interactive) (boon-select-thing-at-point 'list))
(defun boon-select-sexp () (interactive) (boon-select-thing-at-point 'sexp))
(defun boon-select-outside-pairs () (interactive) (boon-select-from-region 'er/mark-outside-pairs))
2015-08-19 11:30:39 +02:00
(defun boon-select-comment () (interactive) (boon-select-from-region 'er/mark-comment))
2014-10-19 13:59:12 +02:00
(defun boon-select-inside-pairs () (interactive) (boon-select-from-region 'er/mark-inside-pairs))
(defun boon-select-outside-quotes () (interactive) (boon-select-from-region 'er/mark-outside-quotes))
(defun boon-select-whitespace () (interactive) (boon-select-thing-at-point 'whitespace))
(defun boon-select-blanks ()
(interactive)
2016-08-28 19:41:31 +02:00
(lambda ()(boon-regs-from-bounds (cons
2014-10-19 13:59:12 +02:00
(save-excursion
(boon-jump-over-blanks-backward)
(point))
(save-excursion
2015-11-09 20:29:16 +01:00
(boon-jump-over-blanks-forward)
2016-08-28 19:41:31 +02:00
(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.
Intented to be used as an argument to interactive.
Display PROMPT in the echo area."
2016-05-02 15:06:13 +02:00
(let ((head (read-event)))
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))
(let ((regs (boon-spec-region-lazy prompt)))
(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)
"Return the occurences of WHAT as sub-regions of WHERE."
2016-08-25 13:35:23 +02:00
(interactive (list (boon-spec-string-lazy "occurences of what?") (boon-spec-region-lazy "where?")))
(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
the region, in insertion mode. Subregions won't be overlapping."
(interactive (list (boon-spec-region-lazy "what?") (boon-spec-region-lazy "where?")))
(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)
2014-10-23 09:12:59 +02:00
"Return the bordering (of size HOW-MUCH) of a region list REGS.
This function is meant to be called interactively."
(interactive (list (prefix-numeric-value current-prefix-arg) (boon-spec-region-lazy "select contents")))
2016-08-22 22:21:14 +02:00
(lambda ()(apply 'append (mapcar (lambda (reg) (boon-borders reg how-much)) (mapcar 'boon-normalize-reg (funcall regs))))))
2014-10-19 13:59:12 +02:00
2015-11-09 21:01:47 +01:00
(defun boon-select-with-spaces (regs)
"Return the regions REGS, including some surrounding spaces.
This function is meant to be called interactively."
(interactive (list (boon-spec-region-lazy "select with spaces")))
2016-08-22 22:21:14 +02:00
(lambda ()(mapcar (lambda (reg) (boon-include-surround-spaces reg)) (mapcar 'boon-normalize-reg (funcall regs)))))
2015-11-09 21:01:47 +01:00
2014-10-19 13:59:12 +02:00
(defun boon-select-content (regs)
2014-10-23 09:12:59 +02:00
"Return the contents (of size HOW-MUCH) of a region list REGS.
This function is meant to be called interactively."
(interactive (list (boon-spec-region-lazy "select borders")))
2016-08-22 22:21:14 +02:00
(lambda ()(mapcar 'boon-content (mapcar 'boon-normalize-reg (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)
(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
2015-10-16 09:59:31 +02:00
(defun boon-read-char (&optional prompt inherit-input-method seconds)
"Read a character, bypassing multiple cursors defadvice if applicable."
;; do this so that mc's read-char defadvice does not kick in; so we can actually read characters here.
;; a hack for now: as read-event doesn't do the same thing as read char.
(if (boon-bypass-mc)
(read-event prompt inherit-input-method seconds)
(read-char prompt inherit-input-method seconds)))
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
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
(let ((selector (boon-spec-region-lazy 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))))))
(defun boon-spec-region-lazy (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
bounds. This allows to run the function in question multiple
times (describing the region just once with the keyboard). This
can be useful when having multiple cursors, or just using
descriptors referring to several disjoint 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)))
(let ((last-char (boon-read-char (format "%s %s" msg my-prefix-arg))))
(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)
(if (commandp kms)
;; we have a 'selection'. These commands may take prefix
;; args, which they parse right away, and return a
;; continuation constructing the region.
2016-08-22 22:21:14 +02:00
(let ((current-prefix-arg my-prefix-arg))
(call-interactively kms))
;; we have a 'move'. These commands do not take non-universal arguments. So just run it.
(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))))))
(error "Unknown region specifier"))))
2014-10-19 14:17:46 +02:00
(provide 'boon-arguments)
;;; boon-arguments.el ends here