boon/boon-main.el

541 lines
18 KiB
EmacsLisp
Raw Normal View History

2014-10-19 13:59:12 +02:00
;;; boon --- An Ergonomic Command Mode -*- lexical-binding: t -*-
;;; Commentary:
2014-10-19 14:17:46 +02:00
;;; Code:
2014-10-19 13:59:12 +02:00
(require 'boon-core)
2014-10-19 14:17:46 +02:00
(require 'boon-arguments)
2014-10-19 13:59:12 +02:00
(require 'er-basic-expansions)
2015-10-14 22:55:39 +02:00
(require 'multiple-cursors)
2014-10-19 13:59:12 +02:00
(defmacro boon-with-ordered-region (body)
2014-10-30 20:12:08 +01:00
"Run the BODY, ensuring that the point is before the mark."
2014-10-19 13:59:12 +02:00
`(if (< (point) (mark))
,body
(progn (exchange-point-and-mark) ,body (exchange-point-and-mark))))
(defun boon-drop-mark ()
"Drop a mark; or extend the region to the next full line; or revert to original state."
2014-10-19 13:59:12 +02:00
(interactive)
(if mark-active
(if (and (bolp)
(save-excursion (goto-char (mark)) (bolp))
(not (eq (point) (mark))))
(progn ;; here we have a number of full lines selected, and that number is more than 0
(pop-mark) ;; load the saved position into the mark
(goto-char (mark));; jump there
(deactivate-mark))
(boon-with-ordered-region
2014-10-19 13:59:12 +02:00
(progn ;; here we have at least one non-full line selected. Extend to the full lines.
(beginning-of-line)
(exchange-point-and-mark)
(end-of-line)
(forward-char)
(exchange-point-and-mark))))
(progn
(set-mark (point))
(push-mark) ;; Save the starting position, so we can go back to it.
(call-interactively 'boon-mark-region))))
(defun boon-current-line-indentation ()
"Return the indentation of the curent line."
2014-10-19 13:59:12 +02:00
(save-excursion
(back-to-indentation)
(current-column)))
(defun boon-enclose (enclosure regs)
"Wrap, with the ENCLOSURE the regions given as REGS."
(interactive (list (boon-spec-enclosure) (boon-spec-region "enclose")))
2015-10-15 23:02:57 +02:00
;; (message "boon-enclose regs=%s" regs)
(dolist (reg (mapcar 'boon-reg-to-markers (mapcar 'boon-normalize-reg regs)))
(save-excursion
(goto-char (cdr reg))
(insert (cadr enclosure))
(goto-char (car reg))
(insert (car enclosure)))))
2014-10-19 13:59:12 +02:00
(defun boon-find-char-backward (char)
"Move the cursor backwards, until finding an occurence of the character CHAR."
2014-10-19 13:59:12 +02:00
(interactive "cType the character to find")
(search-backward (make-string 1 char))
(forward-char 1))
(defun boon-find-char-forward (char)
2014-10-19 14:17:46 +02:00
"Find the given character (as CHAR), forwards."
2014-10-19 13:59:12 +02:00
(interactive "cType the character to find")
(search-forward (make-string 1 char))
(backward-char 1))
(defun boon-edge-of-expression (forward)
2014-10-19 14:17:46 +02:00
"Jump to the forward or backward (as FORWARD) limit of the current expression."
2014-10-19 13:59:12 +02:00
(interactive "P")
(let ((orig-point (point)))
(goto-char
(save-excursion
(deactivate-mark)
(if (boon-in-string-p)
(er/mark-inside-quotes) (er/mark-inside-pairs))
(when forward (exchange-point-and-mark))
(point)))
;; make sure we make some progress
(when (eq (point) orig-point)
(forward-char (if forward 1 -1)))))
(defun boon-end-of-expression ()
2014-10-30 20:12:08 +01:00
"Jump to the end of the current expression."
2014-10-19 13:59:12 +02:00
(interactive)
(boon-edge-of-expression 't))
(defun boon-beginning-of-expression ()
2014-10-30 20:12:08 +01:00
"Jump to the beginning of the current expression."
2014-10-19 13:59:12 +02:00
(interactive)
(boon-edge-of-expression nil))
(defun boon-extract-region ()
2014-10-30 20:12:08 +01:00
"Extract (delete) the region if it is active."
2014-10-19 13:59:12 +02:00
(when (use-region-p)
(delete-and-extract-region (region-beginning) (region-end))))
(defun boon-insert-register ()
"Insert register, replacing the region if it is active."
(boon-extract-region)
(call-interactively 'insert-register))
(defun boon-copy-to-register ()
2014-10-30 20:12:08 +01:00
"Copy to register and deactivate mark."
2014-10-19 13:59:12 +02:00
(interactive)
(call-interactively 'copy-to-register)
(deactivate-mark))
(defun boon-splice ()
"Yank, replacing the region if it is active."
(interactive)
(boon-extract-region)
(yank))
2014-10-23 13:37:38 +02:00
(defun boon-line-prefix ()
2014-10-30 20:12:08 +01:00
"Return the text between beginning of line and position."
2014-10-23 13:37:38 +02:00
(buffer-substring-no-properties
(line-beginning-position)
(point)))
2014-10-19 13:59:12 +02:00
(defun boon-at-indent-or-more-p ()
2014-10-30 20:12:08 +01:00
"Return non-nil if the point is at the current line indentation; or to the right."
2014-10-19 13:59:12 +02:00
(or (eolp)
(and (not (boon-at-indent-p))
2015-06-21 22:43:25 +02:00
(string-blank-p (boon-line-prefix)))))
2014-10-19 13:59:12 +02:00
(defun boon-at-indent-p ()
2014-10-30 20:12:08 +01:00
"Return non-nil if the point is at the current line indentation."
2014-10-19 13:59:12 +02:00
(eq (save-excursion (back-to-indentation) (point)) (point)))
2014-10-23 13:50:16 +02:00
(defun boon-smarter-upward ()
2014-10-30 20:12:08 +01:00
"Move upward, to a line with the same level of indentation, or less."
2014-10-19 13:59:12 +02:00
(interactive)
(back-to-indentation)
(previous-logical-line)
(while (boon-at-indent-or-more-p) (previous-logical-line))
(back-to-indentation))
2014-10-23 13:50:16 +02:00
(defun boon-smarter-downward ()
2014-10-30 20:12:08 +01:00
"Move downward, to a line with the same level of indentation, or less."
2014-10-19 13:59:12 +02:00
(interactive)
(back-to-indentation)
(next-logical-line)
(while (boon-at-indent-or-more-p) (next-logical-line))
(back-to-indentation))
2014-10-23 13:50:16 +02:00
(defun boon-smarter-backward ()
2014-10-30 20:12:08 +01:00
"Move backward, over a whole syntactic unit."
2014-10-19 13:59:12 +02:00
(interactive)
(boon-jump-over-blanks-backward)
(cond
((boon-looking-at-comment -1)
(forward-comment -1))
((looking-back "\\s\"")
(backward-char)
(er--move-point-backward-out-of-string))
((looking-back "\\s)")
(backward-list))
((looking-back "\\s_") ;; symbol
(skip-syntax-backward "_"))
((looking-back "\\s(")
(backward-char))
((looking-back "\\s!") ;; generic comment delimiter
(skip-syntax-backward "!"))
((looking-back "\\sw")
(if (not (or (looking-at "\\s-") ;; FIXME: merge regexps with \\|
(looking-at "\\s(")
(looking-at "\\s)")))
(skip-syntax-backward "w")
(skip-syntax-backward "w_")))
(t
(backward-char)))
)
2014-10-23 13:50:16 +02:00
(defun boon-smarter-forward ()
2014-10-30 20:12:08 +01:00
"Move forward, over a whole syntactic unit."
2014-10-19 13:59:12 +02:00
(interactive)
(boon-jump-over-blanks)
(cond
2014-10-23 13:50:16 +02:00
((boon-looking-at-line-comment-start-p)
2014-10-19 13:59:12 +02:00
(end-of-line)
(boon-jump-over-blanks))
((boon-looking-at-comment 1);;
(forward-comment 1))
((looking-at "\\s\"")
(forward-char)
(er--move-point-forward-out-of-string))
((looking-at "\\s(")
;; (message "open paren")
(forward-list))
((looking-at "\\s_") ;; symbol
(skip-syntax-forward "_"))
((looking-at "\\s)")
(forward-char))
((looking-at "\\s!") ;; generic comment delimiter
;; (message "generic")
(skip-syntax-forward "!"))
((looking-at "\\sw")
(if (not (or (looking-back "\\s-")
(looking-back "\\s(")
(looking-back "\\s)")))
(skip-syntax-forward "w")
(skip-syntax-forward "w_")))
(t
(forward-char)))
;; (when (and no-spaces-skipped (not in-middle))
;; (skip-chars-forward "\t\n "))
)
2014-10-19 14:17:46 +02:00
2014-10-19 13:59:12 +02:00
(defun boon-toggle-character-case ()
2014-10-30 20:12:08 +01:00
"Toggle the case of the character at point."
2014-10-19 13:59:12 +02:00
(interactive)
(let ((case-fold-search nil))
(if (looking-at "[[:upper:]]")
(progn
(downcase-region (point) (+ (point) 1)))
(progn
(upcase-region (point) (+ (point) 1))))))
(defun boon-toggle-case ()
2014-10-30 20:12:08 +01:00
"Toggle the case of the character at point, or cycle the case of the region if it is active."
2014-10-19 13:59:12 +02:00
(interactive)
(if (use-region-p)
(call-interactively 'boon-toggle-region-case)
(boon-toggle-character-case)))
2014-10-31 20:56:24 +01:00
(defun boon-toggle-region-case (beg end)
"Cycle the region between BEG and END through 3 capitalizations: UPPER CASE, lower case, Title Case."
2014-10-19 13:59:12 +02:00
(interactive "r")
(let* ((deactivate-mark nil)
(case-fold-search nil)
(cur-state (if (eq last-command this-command)
(get this-command 'state)
(save-excursion
2014-10-31 20:56:24 +01:00
(goto-char beg)
2014-10-19 13:59:12 +02:00
(cond
((looking-at "[[:upper:]][[:upper:]]") 'upcase-region)
((looking-at "[[:upper:]][[:lower:]]") 'capitalize-region)
(t 'downcase-region))))))
(setq cur-state (cdr (assoc cur-state '((downcase-region . capitalize-region)
(capitalize-region . upcase-region)
(upcase-region . downcase-region)
))))
2014-10-31 20:56:24 +01:00
(funcall cur-state beg end)
2014-10-19 13:59:12 +02:00
(put this-command 'state cur-state)))
(defun boon-toggle-mark ()
2014-10-30 20:12:08 +01:00
"Toggle region activation."
2014-10-19 13:59:12 +02:00
(interactive)
(if mark-active
(deactivate-mark)
(when (eq (point) (mark))
(message "mark placed at point"))
(activate-mark)))
(defun boon-beginning-of-line ()
"Move point to the first non-whitespace character on this line.
If point was already at that position, move point to beginning of
line."
(interactive)
(let ((oldpos (point)))
(back-to-indentation)
2014-10-19 13:59:12 +02:00
(when (= oldpos (point))
(beginning-of-line))))
2014-10-19 13:59:12 +02:00
(defun boon-looking-at-comment (how-many)
2014-10-30 20:12:08 +01:00
"Is the current point looking at HOW-MANY comments? (negative for backwards)?"
2014-10-19 13:59:12 +02:00
(save-excursion
(forward-comment how-many)))
(defun boon-in-string-p ()
2014-10-30 20:12:08 +01:00
"Determine if the point is inside a string."
2014-10-19 13:59:12 +02:00
(nth 3 (syntax-ppss)))
2014-10-23 13:50:16 +02:00
(defun boon-looking-at-line-comment-start-p ()
2014-10-30 20:12:08 +01:00
"Are we looking at a comment-start?"
2014-10-19 13:59:12 +02:00
(interactive)
(and (boundp 'comment-start)
comment-start
(looking-at comment-start)
(not (boon-in-string-p))))
(defun boon-end-of-line ()
2014-10-30 20:12:08 +01:00
"Intelligently jump to the end of line.
This function toggles between jumping to 1. the last character of code on the
2014-10-19 13:59:12 +02:00
line 2. the last non-blank char on the line 3. the true end of
line."
(interactive)
(let* ((orig (point))
(orig-eol (eolp))
(progress (lambda () (and (not (bolp)) (or orig-eol (> (point) orig))))))
(beginning-of-line)
2014-10-23 13:50:16 +02:00
(while (not (or (boon-looking-at-line-comment-start-p) (eolp)))
2014-10-19 13:59:12 +02:00
(forward-char))
;; we're now at the last non-comment character of the line
(skip-chars-backward "\n\t " (line-beginning-position))
;; we're now at the last non-blank, non-comment character of the line
(unless (funcall progress)
(end-of-line)
(skip-chars-backward "\n\t " (line-beginning-position))
;; we're now at the last non-blank character of the line
(unless (funcall progress)
(end-of-line)))))
(defun boon-open-line-and-insert ()
"Open a new line, indented as much as the current one, and switch to insert mode."
(interactive)
(let ((indent-lvl (boon-current-line-indentation)))
2014-10-19 13:59:12 +02:00
(beginning-of-line)
(open-line 1)
(insert (make-string indent-lvl 32))
(boon-set-insert-state)))
(defun boon-open-next-line-and-insert ()
2014-10-31 20:56:24 +01:00
"Open the line after the current one."
2014-10-19 13:59:12 +02:00
(interactive)
(next-logical-line)
(boon-open-line-and-insert))
(defun boon-open-line ()
2014-10-31 20:56:24 +01:00
"Open the line before the current one."
2014-10-19 13:59:12 +02:00
(interactive)
(save-excursion
(let ((line-prefix (boon-line-prefix)))
;; (message "next-line-prefix is %S" next-line-prefix)
(open-line 1)
2015-06-21 22:43:25 +02:00
(when (string-blank-p line-prefix)
2014-10-19 13:59:12 +02:00
(progn
(forward-char 1)
(insert line-prefix))))))
(defun boon-switch-mark ()
2014-10-30 20:12:08 +01:00
"If mark active, switch point and mark, otherwise pop mark from mark ring."
2014-10-19 13:59:12 +02:00
(interactive)
(if mark-active
(exchange-point-and-mark)
(progn
(goto-char (mark))
(pop-mark))))
(defun boon-switch-mark-quick ()
2014-10-31 20:56:24 +01:00
"Pop the mark ring until we find ourselves on a different line."
2014-10-19 13:59:12 +02:00
(interactive)
(let ((orig-line (line-number-at-pos)))
(while (> 1 (abs (- orig-line (line-number-at-pos))))
(goto-char (mark))
(pop-mark))))
2014-10-30 20:12:08 +01:00
(defun boon-split-line ()
"Split the current line."
(interactive)
(let ((indent-col (min (boon-current-line-indentation) (current-column))))
;; kill the extra spaces
(save-excursion
(delete-and-extract-region (progn
(skip-chars-forward "\n\t " (line-end-position))
(point))
(progn
(skip-chars-backward "\n\t " (line-beginning-position))
(point))))
(newline)
(insert (make-string indent-col ?\ ))))
2014-10-19 13:59:12 +02:00
(defun boon-newline-dwim ()
2014-10-30 20:12:08 +01:00
"Insert a new line do-what-i-mean style."
2014-10-19 13:59:12 +02:00
(interactive)
2015-06-21 22:43:25 +02:00
(if (and (not (eolp)) (string-blank-p (boon-line-prefix)))
2014-10-19 13:59:12 +02:00
(call-interactively 'boon-open-line)
2014-10-30 20:12:08 +01:00
(boon-split-line)))
2015-10-14 22:55:39 +02:00
(defun boon-lay-multiple-cursors (place-cursor regs)
"Create multiple cursor regions, using REGS.
If there is more than one, use mc/create-fake-cursor-at-point."
(mc/remove-fake-cursors)
(dolist (reg (cdr regs))
(funcall place-cursor reg)
(mc/create-fake-cursor-at-point))
(funcall place-cursor (car regs))
2015-10-15 22:00:16 +02:00
(mc/maybe-multiple-cursors-mode))
2015-10-14 22:55:39 +02:00
(defun boon-mark-region (regs)
2015-10-14 22:55:39 +02:00
"Mark the regions REGS."
(interactive (list (boon-spec-region "mark")))
2015-10-14 22:55:39 +02:00
(boon-lay-multiple-cursors (lambda (reg)
(set-mark (car reg))
(goto-char (cdr reg))) regs)
2014-10-19 13:59:12 +02:00
(activate-mark))
(defun boon-end-of-region (regs)
"Move the point the end region REGS."
(interactive (list (boon-spec-region "go to end")))
2014-10-19 13:59:12 +02:00
(dolist (reg regs)
(goto-char (cdr reg))))
(defun boon-beginning-of-region (regs)
"Move the point to the beginning region REGS."
2015-10-14 22:55:39 +02:00
(interactive (list (boon-spec-region "go to beginning")))
2014-10-19 13:59:12 +02:00
(dolist (reg regs)
(goto-char (car reg))))
(defun boon-take-region (regs)
"Kill the region given as REGS."
(interactive (list (boon-spec-region "take")))
2015-10-15 22:00:16 +02:00
(message "boon-take-region: REGS=%s" regs)
(dolist (reg (mapcar 'boon-reg-to-markers regs))
(message "boon-take-region: killing: %s" reg)
2014-10-19 13:59:12 +02:00
(kill-region (car reg) (cdr reg))))
(defun boon-swap-region (regs)
2014-10-30 20:12:08 +01:00
"Swap the region with the top of the kill ring (BUGGED)."
(interactive (list (boon-spec-region "swap")))
2014-10-19 13:59:12 +02:00
(dolist (reg regs)
(kill-region (car reg) (cdr reg)))
(insert-for-yank (current-kill 1))
(save-excursion
(goto-char (car mark-ring))
(insert-for-yank (current-kill -1)))
)
2015-10-14 22:55:39 +02:00
2014-10-19 13:59:12 +02:00
(defun boon-treasure-region (regs)
2014-10-30 20:12:08 +01:00
"Copy (kill-ring-save) the regions REGS."
(interactive (list (boon-spec-region "treasure")))
2014-10-19 13:59:12 +02:00
(dolist (reg regs)
(kill-ring-save (car reg) (cdr reg))))
(defun boon-substitute-region (regs)
2015-10-14 22:55:39 +02:00
"Kill the regions REGS, and switch to insertion mode."
(interactive (list (boon-spec-region "replace")))
2015-10-15 22:00:16 +02:00
(let ((markers (mapcar 'boon-reg-to-markers regs)))
;; use markers so that deleting things does not mess the positions
(boon-take-region regs)
(deactivate-mark t)
(boon-lay-multiple-cursors (lambda (reg)
(goto-char (cdr reg)))
markers)
(boon-set-insert-state)))
2014-10-19 13:59:12 +02:00
2014-10-28 14:33:55 +01:00
(defun boon-replace-by-character (replacement)
"Replace the character at point, or region if it is active, by the REPLACEMENT character."
2014-10-19 13:59:12 +02:00
(interactive "cType the character to use as a replacement")
(if (use-region-p)
2015-10-14 22:55:39 +02:00
(delete-and-extract-region (region-beginning) (region-end))
(delete-char 1))
2014-10-19 13:59:12 +02:00
(insert replacement))
(defun boon-quote-character (char)
2014-10-28 14:47:17 +01:00
"Execute the command bound to the character CHAR if boon was not enabled."
2014-10-19 13:59:12 +02:00
(interactive "cThe character to insert or command to execute")
(let ((cmd
2014-11-05 13:31:13 +01:00
(or (and (current-local-map) (lookup-key (current-local-map) (vector char)))
(lookup-key (current-global-map) (vector char)))))
2014-10-19 13:59:12 +02:00
(setq last-command-event char)
(message (format "Executing the command bound to %c" char))
(call-interactively cmd nil [char])))
2014-10-28 14:47:17 +01:00
2014-10-30 20:12:08 +01:00
(defun boon-unhighlight ()
"Pop a highlight regexp."
(interactive)
(when (bound-and-true-p hi-lock-interactive-patterns)
(hi-lock-unface-buffer (car (car hi-lock-interactive-patterns)))))
2014-10-19 13:59:12 +02:00
(defun boon-quit ()
2014-10-28 14:47:17 +01:00
"Exit the current modes we're in until no special state is remaining."
2014-10-19 13:59:12 +02:00
(interactive)
(cond
((use-region-p)
(message "Deactivated region (use ' to reactivate)")
(deactivate-mark))
((bound-and-true-p multiple-cursors-mode)
(message "Exitted from multiple cursors")
(multiple-cursors-mode 0))
((bound-and-true-p hi-lock-interactive-patterns)
(message "Removed highlighting")
(boon-unhighlight))
(t
;; (message "Already in command mode; doing keyboard quit")
(keyboard-quit))))
(defun boon-stuff-at-point ()
2014-10-30 20:12:08 +01:00
"Return a meaningful piece of around at point."
2014-10-19 13:59:12 +02:00
(interactive)
(if (use-region-p)
(buffer-substring-no-properties (region-beginning) (region-end))
(or (thing-at-point 'symbol)
(error "Nothing relevant at point; move to a symbol or select a region"))))
2015-05-27 00:03:53 +02:00
;; TODO: remove
(require 'skeleton)
(setq skeleton-pair t)
2014-10-19 13:59:12 +02:00
2014-10-23 13:58:55 +02:00
(defun boon-empty-pair-p ()
2014-10-19 13:59:12 +02:00
"Is the point at the middle of an empty pair of matched parens?"
(interactive)
2015-05-27 00:03:53 +02:00
(declare (obsolete "emacs 24.5 electric pair mode is good enough" "20150527"))
2014-10-19 13:59:12 +02:00
(eq (caddr
(assq (preceding-char)
(or skeleton-pair-alist skeleton-pair-default-alist)))
(following-char)))
2014-10-23 13:58:55 +02:00
(defun boon-empty-quotes-p ()
2014-10-19 13:59:12 +02:00
"Is the point in the middle of an empty pair of quotes?"
(interactive)
2015-05-27 00:03:53 +02:00
(declare (obsolete "emacs 24.5 electric pair mode is good enough" "20150527"))
2014-10-19 13:59:12 +02:00
(and (eq (preceding-char) (following-char))
(member (following-char) '(?\" ?\'))))
2014-10-23 13:58:55 +02:00
(defun boon-smart-insert-backspace2 ()
2014-10-19 13:59:12 +02:00
(interactive)
2015-05-27 00:03:53 +02:00
(declare (obsolete "emacs 24.5 electric pair mode is good enough" "20150527"))
2014-10-23 13:58:55 +02:00
(when (or (boon-empty-pair-p) (boon-empty-quotes-p))
2014-10-19 13:59:12 +02:00
(delete-char 1))
(backward-delete-char-untabify 1))
2014-10-23 13:58:55 +02:00
(defun boon-self-insert-quote ()
"Insert doubled quote.
unless: 1. the previous character is a backslash, in which case a
single quote is inserted or 2. the next character is a quote in
which case the cursor simply jumps over it."
2014-10-19 13:59:12 +02:00
(interactive)
2015-05-27 00:03:53 +02:00
(declare (obsolete "emacs 24.5 electric pair mode is good enough" "20150527"))
2014-10-19 13:59:12 +02:00
(cond
((equal (this-command-keys) (make-string 1 (following-char)))
(forward-char 1))
((eq (preceding-char) ?\\)
(self-insert-command 1))
(t
(self-insert-command 2)
(backward-char 1))))
2014-10-23 13:55:24 +02:00
(defun boon-on-region (f)
2014-10-30 20:12:08 +01:00
"Apply F to the current region."
2014-10-19 13:59:12 +02:00
(funcall f (region-beginning) (region-end)))
2014-10-30 20:12:08 +01:00
(provide 'boon-main)
2014-10-19 13:59:12 +02:00
;;; boon-main ends here