;;; boon-moves.el --- An Ergonomic Command Mode -*- lexical-binding: t -*-
;;; Commentary:
;; This file contains boon moves (jumping around somewhere). These
;; commands are typically bound to a key in boon-moves-map. They
;; can be bound to any desired key though (in global-map as well).
;;; Code:
(require 'boon-core)
(require 'er-basic-expansions)
(require 'find-func)
(require 'boon-utils)
(require 'subr-x)
;;;###autoload
(defun boon-find-char-backward (char)
"Move the cursor backwards, until finding an occurence of the character CHAR."
(interactive "cType the character to find")
(search-backward (make-string 1 char))
(forward-char 1))
;;;###autoload
(defun boon-find-char-forward (char)
"Find the given character (as CHAR), forwards."
(interactive "cType the character to find")
(search-forward (make-string 1 char))
(backward-char 1))
;;;###autoload
(defun boon-edge-of-expression (forward)
"Jump to the forward or backward (as FORWARD) limit of the current expression."
(interactive "P")
(let ((orig-point (point)))
(goto-char
(save-mark-and-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)))))
;;;###autoload
(defun boon-end-of-expression ()
"Jump to the end of the current expression."
(interactive)
(boon-edge-of-expression 't))
;;;###autoload
(defun boon-beginning-of-expression ()
"Jump to the beginning of the current expression."
(interactive)
(boon-edge-of-expression nil))
;;;###autoload
(defun boon-smarter-upward (count)
"Move upward, to a line with the same level of indentation or less, COUNT times."
(interactive "p")
(back-to-indentation)
(dotimes (_number count)
(previous-logical-line)
(while (< (boon-col-relative-to-indent) 0) (previous-logical-line)))
(back-to-indentation))
;;;###autoload
(defun boon-smarter-downward (count)
"Move downward, to a line with the same level of indentation or less, COUNT times."
(interactive "p")
(back-to-indentation)
(dotimes (_number count)
(next-logical-line)
(while (< (boon-col-relative-to-indent) 0) (next-logical-line)))
(back-to-indentation))
;;;###autoload
(defun boon-smarter-backward (count)
"Move backward, over COUNT whole syntactic units."
(interactive "p")
(dotimes (_number count)
(boon-jump-over-blanks-backward)
(let ((back-limit (- (point) 5)))
(cond
((boon-looking-at-comment -1)
(forward-comment -1))
((looking-back "\\s\"" back-limit)
(backward-char)
(when-let ((char (nth 8 (syntax-ppss))))
(goto-char char)))
((looking-back "\\s)" back-limit)
(backward-list))
((looking-back "\\s$" back-limit) ;; math and haskell `x`
(condition-case nil
(let ((end-pos (scan-sexps (point) -1)))
(if (>= end-pos (line-beginning-position))
(goto-char end-pos)
(backward-char)))
(scan-error
(message "matching thing not found")
(backward-char))))
((looking-back "\\s." back-limit) ;; punctuation
(skip-syntax-backward "."))
((looking-back "\\s(" back-limit)
(backward-char))
((looking-back "\\s!" back-limit) ;; generic comment delimiter
(skip-syntax-backward "!"))
((and (fboundp 'subword-backward)
(bound-and-true-p subword-mode)
(looking-at "\\sw")
(looking-back "\\sw" (1- (point))))
(subword-backward))
((looking-back "\\sw\\|\\s_" back-limit)
(if (looking-at "\\sw\\|\\s_")
(progn
(skip-syntax-backward "_")
(skip-syntax-backward "w"))
(skip-syntax-backward "w_")))
(t
(backward-char))))))
;;;###autoload
(defun boon-smarter-forward (count)
"Move forward, over COUNT whole syntactic units."
(interactive "p")
(dotimes (_number count)
(boon-jump-over-blanks-forward)
(cond
((boon-looking-at-line-comment-start-p)
(end-of-line)
(boon-jump-over-blanks-forward))
((boon-looking-at-comment 1);;
(forward-comment 1))
((looking-at "\\s\"")
(forward-char)
(when-let ((char (nth 8 (syntax-ppss))))
(goto-char char)
(forward-sexp)))
((looking-at "\\s(")
(forward-list))
((looking-at "\\s)")
(forward-char))
((looking-at "\\s$") ;; math and haskell `x` ;; TODO
(condition-case nil
(let ((end-pos (scan-sexps (point) 1)))
(if (<= end-pos (line-end-position))
(goto-char end-pos)
(forward-char)))
(scan-error
(message "matching thing not found")
(forward-char))))
((looking-at "\\s.") ;; punctuation
(skip-syntax-forward "."))
((looking-at "\\s!") ;; generic comment delimiter
(skip-syntax-forward "!"))
((and (fboundp 'subword-forward)
(bound-and-true-p subword-mode)
(looking-at "\\sw")
(looking-back "\\sw" (1- (point))))
(subword-forward))
((looking-at "\\sw\\|\\s_")
(if (looking-back "\\sw\\|\\s_" (1- (point)))
(progn
(skip-syntax-forward "_")
(skip-syntax-forward "w"))
(skip-syntax-forward "w_")))
(t
(forward-char)))))
;;;###autoload
(defun boon-visible-beginning-of-line ()
"Move point leftwards to the first visible beginning of line."
(interactive)
(beginning-of-line)
(while (bound-and-true-p outline-invisible-p)
(backward-char 1)
(beginning-of-line 1)))
;;;###autoload
(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)
(when (or (and (fboundp 'outline-invisible-p)
(outline-invisible-p))
(= oldpos (point)))
(boon-visible-beginning-of-line))))
;;;###autoload
(defun boon-end-of-line ()
"Intelligently jump to the end of line.
This function toggles between jumping to 1. the last character of code on the
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)
(while (not (or (boon-looking-at-line-comment-start-p) (eolp)))
(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)))))
;;;###autoload
(defun boon-switch-mark ()
"If mark active, switch point and mark, otherwise pop mark from mark ring."
(interactive)
(if mark-active
(exchange-point-and-mark)
(when (mark)
(goto-char (mark))
(pop-mark))))
;;;###autoload
(defun boon-switch-mark-quick ()
"Pop the mark ring until we find ourselves on a different line."
(interactive)
(declare (obsolete "annoying" "20160901"))
(let ((orig-line (line-number-at-pos)))
(while (> 1 (abs (- orig-line (line-number-at-pos))))
(goto-char (mark))
(pop-mark))))
(provide 'boon-moves)
;;; boon-moves.el ends here