mirror of
https://github.com/vale981/boon
synced 2025-03-04 09:01:39 -05:00
293 lines
11 KiB
EmacsLisp
293 lines
11 KiB
EmacsLisp
|
|
;;; boon-core.el --- An Ergonomic Command Mode -*- lexical-binding: t -*-
|
|
|
|
;;; Commentary:
|
|
|
|
;; This module sets up the emulation keymaps for each boon state.
|
|
;; Functions to switch to each state is also provided.
|
|
|
|
;;; Code:
|
|
|
|
(require 'cl-macs)
|
|
(require 'dash)
|
|
|
|
(defgroup boon nil "Boon" :group 'Editing)
|
|
|
|
;; Maps
|
|
(defvar boon-x-map)
|
|
(define-prefix-command 'boon-x-map)
|
|
(set-keymap-parent boon-x-map ctl-x-map)
|
|
|
|
(defvar boon-command-map (make-sparse-keymap)
|
|
"Keymap used in Boon command mode.
|
|
|
|
\\{boon-command-map}")
|
|
(suppress-keymap boon-command-map 't)
|
|
(defvar boon-moves-map (make-sparse-keymap)
|
|
"Keymap for moves (subset of command mode).
|
|
|
|
\\{boon-moves-map}")
|
|
(set-keymap-parent boon-command-map boon-moves-map)
|
|
(defvar boon-select-map (make-sparse-keymap)
|
|
"Keymap for text regions selectors.
|
|
\\{boon-select-map}
|
|
|
|
Any move is also a valid region selector, see `boon-moves-map'.")
|
|
(defvar boon-insert-map (make-sparse-keymap))
|
|
(defvar boon-special-map (make-sparse-keymap) "Keymap used in special modes.
|
|
See also `boon-special-mode-list'.
|
|
|
|
\\{boon-special-map}")
|
|
|
|
(defvar boon-mode-map-alist (list (cons 'boon-command-state boon-command-map)
|
|
(cons 'boon-special-state boon-special-map)
|
|
(cons 'boon-insert-state boon-insert-map)))
|
|
(push 'boon-mode-map-alist emulation-mode-map-alists)
|
|
|
|
;; States
|
|
(defvar-local boon-off-state nil "Used to disable boon altogether without fiddling with emulation-mode-map-alists")
|
|
(defvar-local boon-command-state nil "Non-nil when boon command mode is activated. (Boon commands can be entered in this mode.)")
|
|
(defvar-local boon-insert-state nil "Non-nil when boon insert mode is activated.")
|
|
(defvar-local boon-special-state nil "Non-nil when special state is
|
|
activated. Special is active when special-mode buffers (see `boon-special-mode-list') are
|
|
activated. This buffers have their own set of commands, so we use
|
|
those. See `boon-special-map' for exceptions.")
|
|
|
|
(defvar boon/insert-command-history nil "History of changes in this insertion round.")
|
|
(defvar boon/insert-command nil "Command which started the insertion.")
|
|
(defvar boon/insert-origin 0 "Point at start of insert mode.")
|
|
|
|
(defcustom boon-command-cursor-type 'box "`cursor-type' for command mode." :group 'boon)
|
|
(defcustom boon-insert-cursor-type 'bar "`cursor-type' for insert mode." :group 'boon)
|
|
|
|
(defun boon-interactive-insert (&rest args)
|
|
"Boon insert commands must call this function after `interactive'.
|
|
The effect of this function is to remember the current command
|
|
and ARGS so that it can be repeated later by
|
|
`boon-set-insert-like-state'. The current command must take an
|
|
optional list of changes as its last argument."
|
|
(unless boon/insert-command
|
|
(setq boon/insert-command (cons this-command (-map (lambda (x) (list 'quote x)) args)))))
|
|
|
|
(defun boon/after-change-hook (begin end old-len)
|
|
"Remember the change defined by BEGIN END OLD-LEN in `boon/insert-command-history'."
|
|
(when (and boon-insert-state (not mc--executing-command-for-fake-cursor))
|
|
;; (message "bach: %s" boon/insert-command-history (list begin end old-len))
|
|
(cond ((and boon/insert-command-history
|
|
(string= "" (nth 2 (car boon/insert-command-history))) ;; no insert
|
|
(eq begin end) ;; no insert
|
|
(eq (+ begin old-len) (+ boon/insert-origin
|
|
(car (car boon/insert-command-history)))))
|
|
;; two consecutive deletes: concat them.
|
|
(setq boon/insert-command-history (cons (list (- begin boon/insert-origin)
|
|
(+ old-len (nth 1 (car boon/insert-command-history)))
|
|
"")
|
|
(cdr boon/insert-command-history))))
|
|
((and boon/insert-command-history
|
|
(eq 0 (nth 1 (car boon/insert-command-history))) ;; no delete
|
|
(eq 0 old-len) ;; no delete
|
|
(eq begin (+ boon/insert-origin
|
|
(car (car boon/insert-command-history))
|
|
(length (nth 2 (car boon/insert-command-history))))))
|
|
;; two consecutive inserts: concat them.
|
|
(setq boon/insert-command-history (cons (list (car (car boon/insert-command-history))
|
|
0
|
|
(concat (nth 2 (car boon/insert-command-history)) (buffer-substring-no-properties begin end)))
|
|
(cdr boon/insert-command-history))))
|
|
(t
|
|
(push (list (- begin boon/insert-origin) old-len (buffer-substring-no-properties begin end))
|
|
boon/insert-command-history)))))
|
|
|
|
(defun boon/replay-changes (changes)
|
|
"Replay the CHANGES at the current point."
|
|
(let ((p0 (point)))
|
|
(setq boon/insert-command nil) ;; did not go to insert mode after all
|
|
(dolist (change changes)
|
|
(goto-char (+ p0 (nth 0 change)))
|
|
(delete-region (+ p0 (nth 0 change)) (+ p0 (nth 0 change) (nth 1 change)))
|
|
(insert (nth 2 change)))))
|
|
|
|
(defvar-local boon-input-method nil
|
|
"The input method to activate
|
|
when going to insert state. (When leaving insert state the
|
|
input-method is reset to nil.)")
|
|
|
|
(defun boon-set-state (state)
|
|
"Set the boon state (as STATE) for this buffer."
|
|
(when boon-insert-state (setq-local boon-input-method current-input-method))
|
|
(setq boon-command-state nil)
|
|
(setq boon-insert-state nil)
|
|
(setq boon-special-state nil)
|
|
(set state t)
|
|
(cond (boon-command-state
|
|
(setq current-input-method nil)
|
|
(when (and boon/insert-command boon/insert-command-history)
|
|
(push `(,@boon/insert-command
|
|
(quote ,@(list (nreverse boon/insert-command-history))))
|
|
command-history))
|
|
(setq boon/insert-command nil)
|
|
(setq boon/insert-command-history nil)
|
|
(setq cursor-type boon-command-cursor-type))
|
|
(boon-special-state)
|
|
(boon-insert-state
|
|
(setq current-input-method boon-input-method)
|
|
(deactivate-mark)
|
|
(save-excursion
|
|
(when (not (bolp))
|
|
(let ((orig (point)))
|
|
(skip-chars-forward " " (line-end-position))
|
|
(when (eolp) (delete-region orig (point))))))
|
|
(setq cursor-type boon-insert-cursor-type)
|
|
(push-mark) ;; remember where the last edition was by pushing a mark
|
|
(setq boon/insert-command-history nil)
|
|
(setq boon/insert-origin (point)))
|
|
(boon-off-state)
|
|
(t (error "Boon: Unknown state!")))
|
|
(force-mode-line-update))
|
|
|
|
(defun boon-set-insert-state ()
|
|
"Switch to insert state."
|
|
(when (and buffer-read-only (not (boon-shell-mode-p)))
|
|
(error "Buffer is read only, can't insert in it"))
|
|
(boon-set-state 'boon-insert-state))
|
|
|
|
(defun boon-set-command-state ()
|
|
"Switch to command state."
|
|
(interactive) (boon-set-state 'boon-command-state))
|
|
|
|
(defun boon-set-special-state ()
|
|
"Switch to special state."
|
|
(boon-set-state 'boon-special-state))
|
|
|
|
(defcustom boon-special-mode-list
|
|
'(Buffer-menu-mode
|
|
debugger-mode
|
|
ediff-mode
|
|
git-rebase-mode
|
|
mu4e-headers-mode
|
|
mu4e-view-mode
|
|
org-agenda-mode
|
|
cfw:calendar-mode)
|
|
"A List of modes which should use `boon-special-state'."
|
|
:group 'boon
|
|
:type '(repeat symbol))
|
|
|
|
(defun boon-shell-mode-p ()
|
|
(derived-mode-p 'comint-mode 'eshell-mode 'term-mode))
|
|
|
|
(defcustom boon-special-conditions
|
|
'((bound-and-true-p magit-blame-mode))
|
|
"A list of sufficient conditions to trigger special state."
|
|
:group 'boon :type '(list sexp))
|
|
|
|
(defcustom boon-insert-conditions '((eq major-mode 'message-mode))
|
|
"A list of sufficient conditions to start in insert state."
|
|
:group 'boon :type '(list sexp))
|
|
|
|
(defun boon-special-mode-p ()
|
|
"Should the mode use `boon-special-state'?"
|
|
(or (and (eq (get major-mode 'mode-class) 'special)
|
|
(not (boon-shell-mode-p)))
|
|
(-some 'eval boon-special-conditions)
|
|
(memq major-mode boon-special-mode-list)))
|
|
|
|
;;; Initialisation and activation
|
|
|
|
(define-minor-mode boon-local-mode
|
|
"Minor mode for setting up command mode in a single buffer."
|
|
:init-value nil
|
|
:lighter (:eval (boon-modeline-string))
|
|
:keymap nil
|
|
(if (not boon-local-mode)
|
|
(boon-set-state 'boon-off-state)
|
|
(unless (memq 'boon/after-change-hook after-change-functions)
|
|
(push 'boon/after-change-hook after-change-functions))
|
|
(cond ((boon-special-mode-p) (boon-set-state 'boon-special-state))
|
|
((-some 'eval boon-insert-conditions) (boon-set-insert-state))
|
|
(t (boon-set-command-state)))))
|
|
|
|
(add-hook 'minibuffer-setup-hook 'boon-minibuf-hook)
|
|
|
|
(defun boon-minibuf-hook ()
|
|
"Set the cursor type to 'bar'.
|
|
This is because no command mode is activated in the minibuffer."
|
|
(setq cursor-type 'bar))
|
|
|
|
;; The function `boon-initialize' should only be used to initialize
|
|
;; `boon-local-mode' from the globalized minor-mode `boon-mode'. It is
|
|
;; called whenever boon is enabled in a buffer for the first time or
|
|
;; when boon is active and the major-mode of the buffer changes.
|
|
(defun boon-initialize ()
|
|
"Enable Boon in the current buffer, if appropriate. To enable Boon globally, do (boon-mode 1)."
|
|
(unless (minibufferp)
|
|
(boon-local-mode 1)))
|
|
|
|
;;;###autoload (autoload 'boon-mode "boon" "Toggle boon in all buffers" t)
|
|
(define-globalized-minor-mode boon-mode boon-local-mode boon-initialize :group 'boon)
|
|
|
|
;;;###autoload
|
|
(defun turn-on-boon-mode ()
|
|
"Turn on Boon in the current buffer."
|
|
(interactive)
|
|
(boon-local-mode 1))
|
|
|
|
;;;###autoload
|
|
(defun turn-off-boon-mode ()
|
|
"Turn off Boon in the current buffer."
|
|
(interactive)
|
|
(boon-local-mode -1))
|
|
|
|
(defun boon-modeline-string ()
|
|
"Return the modeline string appropriate for the current state."
|
|
(concat " Boon:" (boon-state-string)))
|
|
|
|
(defun boon-state-string ()
|
|
"Return a string describing the current state."
|
|
(cond
|
|
(boon-command-state "CMD")
|
|
(boon-insert-state "INS")
|
|
(boon-special-state "SPC")
|
|
(t "???")))
|
|
|
|
(with-eval-after-load 'multiple-cursors
|
|
(defvar mc--default-cmds-to-run-for-all)
|
|
(defvar mc--default-cmds-to-run-once)
|
|
(setq mc--default-cmds-to-run-for-all
|
|
(append '(boon-beginning-of-expression
|
|
boon-beginning-of-line
|
|
boon-end-of-expression
|
|
boon-end-of-line
|
|
boon-end-of-region
|
|
boon-find-char-backward
|
|
boon-find-char-forward
|
|
boon-quote-character
|
|
boon-replace-by-character
|
|
boon-smarter-backward
|
|
boon-smarter-forward
|
|
boon-splice
|
|
boon-split-line
|
|
boon-switch-mark
|
|
boon-toggle-character-case
|
|
boon-toggle-mark)))
|
|
(setq mc--default-cmds-to-run-once
|
|
(append mc--default-cmds-to-run-once
|
|
'(boon-adjust-indent
|
|
boon-navigate-forward
|
|
boon-navigate-backward
|
|
boon-drop-mark
|
|
boon-enclose
|
|
boon-qsearch-next
|
|
boon-qsearch-next-at-point
|
|
boon-qsearch-previous
|
|
boon-qsearch-previous-at-point
|
|
boon-query-replace
|
|
boon-quit
|
|
boon-set-command-state
|
|
boon-set-insert-like-state
|
|
boon-substitute-region
|
|
boon-take-region
|
|
boon-toggle-character-case
|
|
boon-toggle-case))))
|
|
|
|
(provide 'boon-core)
|
|
;;; boon-core ends here
|