From 0d63977e7251f82960d12f07edd32e16d7e2ae9f Mon Sep 17 00:00:00 2001 From: tianshu Date: Sat, 8 Jan 2022 08:12:48 +0800 Subject: [PATCH] Allow user to define custom states (#158) * Allow user to define custom states (#155) * Generalized macro to produce define-key helpers Defines a macro meow-generate-define-key which produces a helper function meow-STATE-define-key. This macro is then applied to all keymaps. This commit also introduces the variable meow-keymap-alist to keep track of states to keymaps. * Generalize state disabling Ensure that when normal, insert, and beacon modes are entered, they disable all modes that may conflict with them, including custom modes. We also add meow-state-alist to keep track of all modes. * Generalize meow--update-cursor Use a list of conditions instead of a long cond statement. This ensures you can easily add your own conditions and functions. * Generalize meow--switch-state Modify meow--switch-state such that if the state being switched to is not caught by the case statement, it is looked up in an alist of custom modes. * Generalize meow--current-state This uses the previously defined variable meow-state-alist to generalize the meow--current-state function. * Add function to define custom state Please see docstrings. Adds several macros to help the user easily define custom states. * Autoload meow-define-state to prevent errors on startup * Fix entry function macro and add to meow-define-state * Make keymap generator take customization options Suppression option and sparsity options added * Improve variable declarations Fixed docstrings and made indentation look nicer. Also, removes meow-custom-state-alist in favor of two alists that should always mirror each other. Adds meow--register-state to update both lists at once. * Change meow-escape-or-normal-modal to always work Previously, it only switched you to normal if you were either in insert mode or in fundamental mode. * Indenting consistency My indenter got mad * Fix wrong variable usage * Generalize meow--render-indicator Custom states are now added to meow-replace-state-name-list. meow--render-indicator is rewritten to be much shorter. * Documentation * Rewrite meow--switch-state and extract updating to init functions. * Change macros to functions and remove evals * Remove entry function * Make meow-keymap-alist use symbol keys * Rewrite cursor updating Remove code blocks from meow-update-cursor-functions-alist and make defuns for each one. * Make meow--define-state-keymap accept an override keymap * Documentation * Disable normal mode before saving origin commands in motion * Variable docstring tweak Changes variable documentation for meow-keymap-alist and meow-update-cursor-functions-alist. * Make keypad mode remember custom states. * Rewrite meow--motion-init to save origin command properly * setting previous state before keypad is enabled * remove meow-mode-state-alist * improve getting current state & disabling current state * create default helper functions explictly * Update CUSTOMIZATIONS.org * fix error when leaving beacon state * fix init in fundamental-mode * fixes * change define-state syntax * Factor out keymap generation * Documentation for syntax changes * Loop over motion state keymap directly and remove meow--motion-overwrite-keys * Move meow-keymap-alist and make its values keymaps * Add and use meow-define-keys to define keybindings * update docs for meow-define-keys * Inline mode init functions * Docs * Declare indenting on define-keys and define-state * fix indent spec for meow-define-state, update docs * fix unrenamed meow-intern-string * define existing states with meow-define-state * update changelog Co-authored-by: tianshu Co-authored-by: eshrh Co-authored-by: Fredrik Bergroth --- CHANGELOG.md | 1 + CUSTOMIZATIONS.org | 102 ++++++++++++++++++++--- meow-beacon.el | 2 +- meow-command.el | 4 +- meow-core.el | 144 +++++++++++--------------------- meow-helpers.el | 199 +++++++++++++++++++++++++++++++++++++-------- meow-keymap.el | 9 ++ meow-keypad.el | 4 +- meow-shims.el | 2 +- meow-util.el | 152 ++++++++++++++++------------------ meow-var.el | 26 +++++- 11 files changed, 414 insertions(+), 231 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69dd414..4b01307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Master (Unreleased) ### Enhancements +* [#155](https://github.com/meow-edit/meow/pull/155) [#166](https://github.com/meow-edit/meow/pull/166) [#158](https://github.com/meow-edit/meow/pull/158) Add `meow-define-state` and `meow-register-state` to allow user define custom state. * Remap `describe-key` to `meow-describe-key` which handles the dispatched keybinds. * Allow leader in beacon state(still can not switch to keypad). * [#164](https://github.com/meow-edit/meow/issues/164) Add fallback support for meta & control-meta prefix in keypad. diff --git a/CUSTOMIZATIONS.org b/CUSTOMIZATIONS.org index c521962..a9c5cfd 100644 --- a/CUSTOMIZATIONS.org +++ b/CUSTOMIZATIONS.org @@ -2,24 +2,32 @@ * Helper Functions -** meow-normal-define-key +** meow-define-keys -Define key in NORMAL state. +Define key bindings in a state. #+begin_src emacs-lisp - (meow-normal-define-key - ;; bind command + (meow-define-keys + ;; state + 'normal + + ;; bind to a command '("a" . meow-append) - ;; bind keymap + + ;; bind to a keymap (cons "h" help-map) - ;; bind key translation - '("x" . "C-x C-x") - ...) + + ;; bind to a keybinding + '("x" . "C-x C-x")) #+end_src +** meow-normal-define-key + +Similar to ~meow-define-keys~. Define key in NORMAL state. + ** meow-leader-define-key -Like ~meow-normal-define-key~, but for leader keymap. +Similar to ~meow-define-keys~. Define key in leader keymap. The default keybindings in leader keymap: - ~x~, ~c~, ~h~, ~m~ and ~g~ is bound to ~meow-keypad-start~, which can start KEYPAD state. @@ -27,7 +35,8 @@ The default keybindings in leader keymap: ** meow-motion-overwrite-define-key -Use like ~meow-motion-overwrite-define-key~. +Similar to ~meow-define-keys~. Define key in MOTION state. + Meow will remap overwrited command to a keybinding with *HYPER* modifier. For example, if you define ~j~ as ~C-N~ , the original command on ~j~ will be bound to ~H-j~. @@ -58,6 +67,44 @@ Register a thing which can be used for ~meow-beginning/end/inner/bounds-of-thing Check function's documentation for usage examples. +** meow-define-state +Define a custom state. + +Example usage: + +#+begin_src emacs-lisp + (setq meow-paren-keymap (make-keymap)) + (meow-define-state paren + "meow state for interacting with smartparens" + :lighter " [P]" + :keymap meow-paren-keymap) + + ;; meow-define-state creates the variable + (setq meow-cursor-type-paren 'hollow) + + (meow-define-keys 'paren + '("" . meow-normal-mode) + '("l" . sp-forward-sexp) + '("h" . sp-backward-sexp) + '("j" . sp-down-sexp) + '("k" . sp-up-sexp) + '("n" . sp-forward-slurp-sexp) + '("b" . sp-forward-barf-sexp) + '("v" . sp-backward-barf-sexp) + '("c" . sp-backward-slurp-sexp) + '("u" . meow-undo)) +#+end_src + +This function generates several new objects named based on the NAME parameter passed +in. See the function’s docstring for a list of them. + +Similarly to =define-minor-mode=, your last parameter to =meow-define-state= may be +a single lisp form that is run every time the internal minor mode is entered +and exited. + +If you already have a minor mode that you just need to register with meow, then +see the documentation for the internal function =meow-register-state=. + * Variables ** meow-mode-state-list @@ -238,3 +285,38 @@ For examples: "C-x C-v" will remap the occupied j to C-x C-v j. "C-M-" will remap the occupied j to C-M-j. #+end_example + +** meow-state-mode-alist +Association list of symbols of meow states to their corresponding mode functions. + +** meow-update-cursor-functions-alist + +Association list of predicates to functions. + +This list is used to update the cursor type and face. The first value whose +predicate evaluates to true will have its corresponding key run. This key +should use meow--set-cursor-type and meow--set-cursor-color to update the cursor. + +You may customize this list for more complex modifications to the cursor. +For instance, to change the face of the insert cursor to a hollow cursor only +in org-mode, use + +#+BEGIN_SRC emacs-lisp +(defun meow--update-cursor-custom () + (progn + (meow--set-cursor-type 'hollow) + (meow--set-cursor-color 'meow-insert-cursor))) +(add-to-list 'meow-update-cursor-functions-alist + '((lambda () (and (meow-insert-mode-p) + (eq major-mode 'org-mode))) + . meow--update-cursor-custom)) +#+END_SRC +Note that the both the car and cdr must be functions. + +However, for simple changes to the insert cursor it would be sufficient to +change the variable =meow-cursor-type-insert=. + +** meow-keymap-alist + +Association list of symbols to their corresponding keymaps. Used +to generate =meow-*-define-key= helpers. diff --git a/meow-beacon.el b/meow-beacon.el index 8122056..8338662 100644 --- a/meow-beacon.el +++ b/meow-beacon.el @@ -74,7 +74,7 @@ Non-nil BACKWARD means backward direction." inside) (meow--switch-state 'beacon) (meow--beacon-update-overlays)) - ((and (meow-beacon-mode-p)) + ((meow-beacon-mode-p) (if inside (meow--beacon-update-overlays) (meow--beacon-remove-overlays) diff --git a/meow-command.el b/meow-command.el index 736b01f..62d2a20 100644 --- a/meow-command.el +++ b/meow-command.el @@ -1373,7 +1373,7 @@ Argument ARG if not nil, switching in a new window." (when overwrite-mode (overwrite-mode -1)) (meow--switch-state 'normal)) - ((eq major-mode 'fundamental-mode) + (t (meow--switch-state 'normal)))) (defun meow-motion-origin-command () @@ -1381,7 +1381,7 @@ Argument ARG if not nil, switching in a new window." (interactive) (let ((key (meow--parse-input-event last-input-event))) (when-let* ((rebind-key (meow--get-origin-command key))) - (meow--execute-kbd-macro rebind-key)))) + (meow--execute-kbd-macro rebind-key)))) (defun meow-eval-last-exp () "Eval last sexp." diff --git a/meow-core.el b/meow-core.el index 426388a..0fd761d 100644 --- a/meow-core.el +++ b/meow-core.el @@ -24,6 +24,7 @@ ;;; Code: (require 'cl-lib) +(require 'subr-x) (require 'meow-util) (require 'meow-command) @@ -32,48 +33,60 @@ (require 'meow-esc) (require 'meow-shims) (require 'meow-beacon) +(require 'meow-helpers) -;;;###autoload -(define-minor-mode meow-insert-mode - "Meow Insert state." - :init-value nil +(meow-define-state insert + "Meow INSERT state minor mode." :lighter " [I]" :keymap meow-insert-state-keymap - (meow--insert-init)) + :face meow-insert-indicator + (if meow-insert-mode + (run-hooks 'meow-insert-enter-hook) + (when (and meow--insert-pos + meow-select-on-change + (not (= (point) meow--insert-pos))) + (thread-first + (meow--make-selection '(select . transient) meow--insert-pos (point)) + (meow--select))) + (run-hooks 'meow-insert-exit-hook) + (setq-local meow--insert-pos nil))) -;;;###autoload -(define-minor-mode meow-normal-mode - "Meow Normal state." - :init-value nil +(meow-define-state normal + "Meow NORMAL state minor mode." :lighter " [N]" :keymap meow-normal-state-keymap - (meow--normal-init)) - -;;;###autoload -(define-minor-mode meow-keypad-mode - "Meow keypad state." - :init-value nil - :lighter " [K]" - ;; use overriding-local-map for highest keymap priority - ;; so KEYPAD won't be affected by overlays' keymap - :keymap meow-keypad-state-keymap - (meow--keypad-init)) + :face meow-normal-indicator) -;;;###autoload -(define-minor-mode meow-motion-mode - "Meow motion state." - :init-value nil +(meow-define-state motion + "Meow MOTION state minor mode." :lighter " [M]" :keymap meow-motion-state-keymap - (meow--motion-init)) + :face meow-motion-indicator) -;;;###autoload -(define-minor-mode meow-beacon-mode - "Meow cursor state." - :init-value nil - :lighter " [C]" +(meow-define-state keypad + "Meow KEYPAD state minor mode." + :lighter " [K]" + :keymap meow-keypad-state-keymap + :face meow-keypad-indicator + (when meow-keypad-mode + (setq meow--prefix-arg current-prefix-arg + meow--keypad-keys nil + meow--use-literal nil + meow--use-meta nil + meow--use-both nil))) + +(meow-define-state beacon + "Meow BEACON state minor mode." + :lighter " [B]" :keymap meow-beacon-state-keymap - (meow--beacon-init)) + :face meow-beacon-indicator + (if meow-beacon-mode + (progn + (setq meow--beacon-backup-hl-line (bound-and-true-p hl-line-mode)) + (meow--cancel-selection) + (hl-line-mode -1)) + (when meow--beacon-backup-hl-line + (hl-line-mode 1)))) ;;;###autoload (define-minor-mode meow-mode @@ -103,66 +116,6 @@ This minor mode is used by meow-global-mode, should not be enabled directly." (meow--global-enable) (meow--global-disable))) -(defun meow--normal-init () - "Init normal state." - (when meow-normal-mode - (when (bound-and-true-p meow-insert-mode) (meow-insert-mode -1)) - (when (bound-and-true-p meow-motion-mode) (meow-motion-mode -1)) - (when (bound-and-true-p meow-beacon-mode) (meow-beacon-mode -1)))) - -(defun meow--insert-init () - "Init insert state." - (if meow-insert-mode - (progn - (meow-normal-mode -1) - (meow-motion-mode -1) - (run-hooks 'meow-insert-enter-hook)) - (when (and meow--insert-pos - meow-select-on-change - (not (= (point) meow--insert-pos))) - (thread-first - (meow--make-selection '(select . transient) meow--insert-pos (point)) - (meow--select))) - (run-hooks 'meow-insert-exit-hook) - (setq-local meow--insert-pos nil))) - -(defun meow--motion-init () - "Init motion state." - (when meow-motion-mode - (when (bound-and-true-p meow-insert-mode) (meow-insert-mode -1)))) - -(defun meow--keypad-init () - "Init keypad state. - -We have to remember previous state, so that we can restore it." - (when meow-keypad-mode - (cond - ((meow-motion-mode-p) - (setq meow--keypad-previous-state 'motion) - (meow-motion-mode -1)) - ((meow-normal-mode-p) - (setq meow--keypad-previous-state 'normal) - (meow-normal-mode -1))) - (setq meow--prefix-arg current-prefix-arg - ;; meow--keypad-this-command nil - meow--keypad-keys nil - meow--use-literal nil - meow--use-meta nil - meow--use-both nil))) - -(defun meow--beacon-init () - "Init cursor state." - (if meow-beacon-mode - (progn - (setq meow--beacon-backup-hl-line (bound-and-true-p hl-line-mode)) - (meow--cancel-selection) - (meow-normal-mode -1) - (meow-insert-mode -1) - (hl-line-mode -1)) - (meow-normal-mode 1) - (when meow--beacon-backup-hl-line - (hl-line-mode 1)))) - (defun meow--enable () "Enable Meow. @@ -186,6 +139,7 @@ an init function." ;; if MOTION is specified ((apply #'derived-mode-p (mapcar #'car (alist-get 'motion state-to-modes))) (meow-normal-mode -1) + (setq meow--current-state nil) (meow--save-origin-commands) (meow-motion-mode 1)) @@ -197,8 +151,8 @@ an init function." ((progn ;; Disable meow-normal-mode, we need test the command name bound to a single letter key. (meow-normal-mode -1) - (let ((meow-normal-mode nil) - (cmd (key-binding "a"))) + (setq meow--current-state nil) + (let ((cmd (key-binding "a"))) (and (commandp cmd) (symbolp cmd) @@ -212,9 +166,7 @@ an init function." (defun meow--disable () "Disable Meow." - (meow-normal-mode -1) - (meow-insert-mode -1) - (meow-motion-mode -1)) + (mapc (lambda (state-mode) (funcall (cdr state-mode) -1)) meow-state-mode-alist)) (defun meow--global-enable () "Enable meow globally." diff --git a/meow-helpers.el b/meow-helpers.el index 0a01add..fa7ecf1 100644 --- a/meow-helpers.el +++ b/meow-helpers.el @@ -1,4 +1,4 @@ -;;; meow-helpers.el --- Meow Helpers for define keybinding -*- lexical-binding: t; -*- +;;; meow-helpers.el --- Meow helpers for customization -*- lexical-binding: t; -*- ;; This file is not part of GNU Emacs. @@ -19,10 +19,11 @@ ;;; Commentary: ;; +;; Define custom keys in a state with function `meow-define-keys'. ;; Define custom keys in normal map with function `meow-normal-define-key'. ;; Define custom keys in global leader map with function `meow-leader-define-key'. ;; Define custom keys in leader map for specific mode with function `meow-leader-define-mode-key'. - +;; Define a custom state with the macro `meow-define-state' ;;; Code: (require 'cl-lib) @@ -31,41 +32,67 @@ (require 'meow-var) (require 'meow-keymap) -(defun meow-leader-define-key (&rest args) - "Define key for Leader. +(defun meow-intern (name suffix &optional two-dashes prefix) + "Convert a string into a meow symbol. Macro helper. +Concat the string PREFIX or \"meow\" if PREFIX is null, either +one or two hyphens based on TWO-DASHES, the string NAME, and the +string SUFFIX. Then, convert this string into a symbol." + (intern (concat (if prefix prefix "meow") (if two-dashes "--" "-") + name suffix))) + +(defun meow-define-keys (state &rest keybinds) + "Define KEYBINDS in STATE. + +Example usage: + (meow-define-keys + ;; state + 'normal + + ;; bind to a command + '(\"a\" . meow-append) -Usage: - (meow-leader-define-key - '(\"h\" . hs-toggle-hiding)) -Optional argument ARGS key definitions." - (mapcar (lambda (key-def) - (define-key meow-leader-keymap - (kbd (car key-def)) - (meow--parse-def (cdr key-def)))) - args)) + ;; bind to a keymap + (cons \"x\" ctl-x-map) -(defun meow-normal-define-key (&rest args) - "Define key for normal state. + ;; bind to a keybinding which holds a keymap + '(\"c\" . \"C-c\") -Usage: + ;; bind to a keybinding which holds a command + '(\"q\" . \"C-x C-q\"))" + (declare (indent 1)) + (let ((map (alist-get state meow-keymap-alist))) + (pcase-dolist (`(,key . ,def) keybinds) + (define-key map (kbd key) (meow--parse-def def))))) + +(defun meow-normal-define-key (&rest keybinds) + "Define key for NORMAL state with KEYBINDS. + +Example usage: (meow-normal-define-key - '(\"@\" . hs-toggle-hiding)) -Optional argument ARGS key definitions." - (mapcar (lambda (key-def) - (define-key meow-normal-state-keymap - (kbd (car key-def)) - (meow--parse-def (cdr key-def)))) - args)) - -(defun meow-motion-overwrite-define-key (&rest args) - "Define key for motion state." - (mapc (lambda (key-def) - (define-key meow-motion-state-keymap - (kbd (car key-def)) - (meow--parse-def (cdr key-def)))) - args) - (cl-loop for arg in args do - (add-to-list 'meow--motion-overwrite-keys (car arg)))) + ;; bind to a command + '(\"a\" . meow-append) + + ;; bind to a keymap + (cons \"x\" ctl-x-map) + + ;; bind to a keybinding which holds a keymap + '(\"c\" . \"C-c\") + + ;; bind to a keybinding which holds a command + '(\"q\" . \"C-x C-q\"))" + (apply #'meow-define-keys 'normal keybinds)) + +(defun meow-leader-define-key (&rest keybinds) + "Define key in leader keymap with KEYBINDS. + +Check `meow-normal-define-key' for usages." + (apply #'meow-define-keys 'leader keybinds)) + +(defun meow-motion-overwrite-define-key (&rest keybinds) + "Define key for MOTION state. + +Check `meow-normal-define-key' for usages." + (apply #'meow-define-keys 'motion keybinds)) (defun meow-setup-line-number () (add-hook 'display-line-numbers-mode-hook #'meow--toggle-relative-line-number) @@ -84,5 +111,111 @@ and put it anywhere you want." (unless (cl-find '(:eval (meow-indicator)) mode-line-format :test 'equal) (setq-default mode-line-format (append '((:eval (meow-indicator))) mode-line-format)))) +(defun meow--define-state-minor-mode (name + init-value + description + keymap + lighter + form) + "Generate a minor mode definition with name meow-NAME-mode, +DESCRIPTION and LIGHTER." + `(define-minor-mode ,(meow-intern name "-mode") + ,description + :init-value ,init-value + :lighter ,lighter + :keymap ,keymap + (when ,(meow-intern name "-mode") + (meow--disable-current-state) + (setq-local meow--current-state ',(intern name)) + (meow-update-display)) + ,form)) + +(defun meow--define-state-active-p (name) + "Generate a predicate function to check if meow-NAME-mode is +currently active. Function is named meow-NAME-mode-p." + `(defun ,(meow-intern name "-mode-p") () + ,(concat "Whether " name " mode is enabled.\n" + "Generated by meow-define-state-active-p") + (bound-and-true-p ,(meow-intern name "-mode")))) + +(defun meow--define-state-cursor-type (name) + "Generate a cursor type meow-cursor-type-NAME." + `(defvar ,(meow-intern name nil nil "meow-cursor-type") + meow-cursor-type-default)) + +(defun meow--define-state-cursor-function (name &optional face) + `(defun ,(meow-intern name nil nil "meow--update-cursor") () + (meow--set-cursor-type ,(meow-intern name nil nil "meow-cursor-type")) + (meow--set-cursor-color ',(if face face 'meow-unknown-cursor)))) + +(defun meow-register-state (name mode activep cursorf &optional keymap) + "Register a custom state with symbol NAME and symbol MODE +associated with it. ACTIVEP is a function that returns t if the +state is active, nil otherwise. CURSORF is a function that +updates the cursor when the state is entered. For help with +making a working CURSORF, check the variable +meow-update-cursor-functions-alist and the utility functions +meow--set-cursor-type and meow--set-cursor-color." + (add-to-list 'meow-state-mode-alist `(,name . ,mode)) + (add-to-list 'meow-replace-state-name-list + `(,name . ,(upcase (symbol-name name)))) + (add-to-list 'meow-update-cursor-functions-alist + `(,activep . ,cursorf)) + (add-to-list 'meow-keymap-alist `(,name . ,keymap))) + +;;;###autoload +(defmacro meow-define-state (name-sym + description + &rest body) + "Define a custom meow state. + +The state will be called NAME-SYM, and have description +DESCRIPTION. Following these two arguments, pairs of keywords and +values should be passed, similarly to define-minor-mode syntax. + +Recognized keywords: +:keymap - the keymap to use for the state +:lighter - the text to display in the mode line while state is active +:face - custom cursor face +:form - one lisp form that will be run when the minor mode turns on AND off. +If you want to hook into only the turn-on event, check whether +(meow-NAME-SYM-mode) is true. + +Example usage: +(meow-define-state mystate + \"My meow state\" + :lighter \" [M]\" + :keymap 'my-keymap) + +Also see meow-register-state, which is used internally by this +function, if you want more control over defining your state. This +is more helpful if you already have a keymap and defined minor +mode that you only need to integrate with meow. + +This function produces several items: +1. meow-NAME-mode: a minor mode for the state. This is the main entry point. +2. meow-NAME-mode-p: a predicate for whether the state is active. +3. meow-cursor-type-NAME: a variable for the cursor type for the state. +4. meow--update-cursor-NAME: a function that sets the cursor type to 3. + and face FACE or 'meow-unknown cursor if FACE is nil." + (declare (indent 1)) + (let ((name (symbol-name name-sym)) + (init-value (plist-get body :init-value)) + (keymap (plist-get body :keymap)) + (lighter (plist-get body :lighter)) + (face (plist-get body :face)) + (form (unless (cl-evenp (length body)) + (car (last body))))) + `(progn + ,(meow--define-state-active-p name) + ,(meow--define-state-minor-mode name init-value description keymap lighter form) + ,(meow--define-state-cursor-type name) + ,(meow--define-state-cursor-function name face) + (meow-register-state ',(intern name) ',(meow-intern name "-mode") + ',(meow-intern name "-mode-p") + #',(meow-intern name nil nil + "meow--update-cursor") + ,keymap)))) + (provide 'meow-helpers) ;;; meow-helpers.el ends here diff --git a/meow-keymap.el b/meow-keymap.el index 7c8a6d4..53f66f4 100644 --- a/meow-keymap.el +++ b/meow-keymap.el @@ -167,5 +167,14 @@ map) "Keymap for Meow cursor state.") +(defvar meow-keymap-alist + `((insert . ,meow-insert-state-keymap) + (normal . ,meow-normal-state-keymap) + (keypad . ,meow-keypad-state-keymap) + (motion . ,meow-motion-state-keymap) + (beacon . ,meow-beacon-state-keymap) + (leader . ,meow-leader-keymap)) + "Alist of symbols of state names to keymaps.") + (provide 'meow-keymap) ;;; meow-keymap.el ends here diff --git a/meow-keypad.el b/meow-keypad.el index 99eec40..59d9767 100644 --- a/meow-keypad.el +++ b/meow-keypad.el @@ -439,6 +439,7 @@ try replacing the last modifier and try again." (defun meow-keypad-start () "Enter keypad state with current input as initial key sequences." (interactive) + (setq meow--keypad-previous-state (meow--current-state)) (meow--switch-state 'keypad) (setq overriding-local-map meow-keypad-state-keymap) (call-interactively #'meow-keypad-self-insert)) @@ -447,7 +448,8 @@ try replacing the last modifier and try again." "Describe key via KEYPAD input." (interactive) (setq overriding-local-map meow-keypad-state-keymap - meow--keypad-help t) + meow--keypad-help t + meow--keypad-previous-state (meow--current-state)) (meow--switch-state 'keypad) (meow--keypad-show-message) (meow--keypad-display-message)) diff --git a/meow-shims.el b/meow-shims.el index 8f946f8..9548c52 100644 --- a/meow-shims.el +++ b/meow-shims.el @@ -237,7 +237,7 @@ Argument ENABLE non-nil means turn on." Argument ENABLE non-nil means turn on." (setq meow--polymode-setup enable) (when enable - (dolist (v '(meow--selection meow--selection-history)) + (dolist (v '(meow--selection meow--selection-history meow--current-state)) ;; These vars allow us the select through the polymode chunk (add-to-list 'polymode-move-these-vars-from-old-buffer v)))) diff --git a/meow-util.el b/meow-util.el index c272b1b..bed50c4 100644 --- a/meow-util.el +++ b/meow-util.el @@ -68,6 +68,11 @@ "Whether keypad mode is enabled." (bound-and-true-p meow-beacon-mode)) +(defun meow--disable-current-state () + (when meow--current-state + (funcall (alist-get meow--current-state meow-state-mode-alist) -1) + (setq meow--current-state nil))) + (defun meow--read-cursor-face-color (face) "Read cursor color from face." (let ((f (face-attribute face :inherit))) @@ -91,83 +96,74 @@ "Set cursor color by face." (set-cursor-color (meow--read-cursor-face-color face))) +(defun meow--update-cursor-default () + "Set default cursor type and color" + (meow--set-cursor-type meow-cursor-type-default) + (meow--set-cursor-color 'meow-unknown-cursor)) + +(defun meow--update-cursor-insert () + "Set insert cursor type and color" + (meow--set-cursor-type meow-cursor-type-insert) + (meow--set-cursor-color 'meow-insert-cursor)) + +(defun meow--update-cursor-normal () + "Set normal cursor type and color" + (if meow-use-cursor-position-hack + (unless (use-region-p) + (meow--set-cursor-type meow-cursor-type-normal)) + (meow--set-cursor-type meow-cursor-type-normal)) + (meow--set-cursor-color 'meow-normal-cursor)) + +(defun meow--update-cursor-motion () + "Set motion cursor type and color" + (meow--set-cursor-type meow-cursor-type-motion) + (meow--set-cursor-color 'meow-motion-cursor)) + +(defun meow--update-cursor-beacon () + "Set beacon cursor type and color" + (meow--set-cursor-type meow-cursor-type-beacon) + (meow--set-cursor-color 'meow-beacon-cursor)) + +(defun meow--cursor-null-p () + "Check if cursor-type is null" + (null cursor-type)) + (defun meow--update-cursor () "Update cursor type according to the current state. -For performance reasons, we save current cursor type to -`meow--last-cursor-type' to avoid unnecessary updates." - (cond - ;; Don't alter the cursor-type if it's already hidden - ((null cursor-type) - (meow--set-cursor-type meow-cursor-type-default) - (meow--set-cursor-color 'meow-unknown-cursor)) - ((minibufferp) - (meow--set-cursor-type meow-cursor-type-default) - (meow--set-cursor-color 'meow-unknown-cursor)) - ((meow-insert-mode-p) - (meow--set-cursor-type meow-cursor-type-insert) - (meow--set-cursor-color 'meow-insert-cursor)) - ((meow-normal-mode-p) - (if meow-use-cursor-position-hack - ;; Ensure we have correct cursor type after switch state. - (unless (use-region-p) - (meow--set-cursor-type meow-cursor-type-normal)) - (meow--set-cursor-type meow-cursor-type-normal)) - (meow--set-cursor-color 'meow-normal-cursor)) - ((meow-motion-mode-p) - (meow--set-cursor-type meow-cursor-type-motion) - (meow--set-cursor-color 'meow-motion-cursor)) - ((meow-keypad-mode-p) - (meow--set-cursor-type meow-cursor-type-keypad) - (meow--set-cursor-color 'meow-keypad-cursor)) - ((meow-beacon-mode-p) - (meow--set-cursor-type meow-cursor-type-beacon) - (meow--set-cursor-color 'meow-beacon-cursor)) - (t - (meow--set-cursor-type meow-cursor-type-default) - (meow--set-cursor-color 'meow-unknown-cursor)))) +This uses the variable meow-update-cursor-functions-alist, finds the first +item in which the car evaluates to true, and runs the cdr. The last item's car +in the list will always evaluate to true." + (thread-last meow-update-cursor-functions-alist + (cl-remove-if-not (lambda (el) (funcall (car el)))) + (cdar) + (funcall))) (defun meow--get-state-name (state) + "Get the name of the current state. + +Looks up the state in meow-replace-state-name-list" (alist-get state meow-replace-state-name-list)) (defun meow--render-indicator () - "Minimal indicator showing current mode." + "Renders a short indicator based on the current state." (when (bound-and-true-p meow-global-mode) - (cond - ((bound-and-true-p meow-keypad-mode) - (propertize - (format " %s " (meow--get-state-name 'keypad)) - 'face 'meow-keypad-indicator)) - ((bound-and-true-p meow-normal-mode) - (concat - (propertize - (format " %s " (meow--get-state-name 'normal)) - 'face 'meow-normal-indicator))) - ((bound-and-true-p meow-motion-mode) - (propertize - (format " %s " (meow--get-state-name 'motion)) - 'face 'meow-motion-indicator)) - ((bound-and-true-p meow-insert-mode) - (propertize - (format " %s " (meow--get-state-name 'insert)) - 'face 'meow-insert-indicator)) - ((bound-and-true-p meow-beacon-mode) - (propertize - (format " %s " (meow--get-state-name 'beacon)) - 'face 'meow-beacon-indicator)) - (t "")))) + (let ((state-name (meow--get-state-name (meow--current-state)))) + (if state-name + (propertize + (format " %s " state-name) + 'face 'meow-insert-indicator) + "")))) (defun meow--update-indicator () (let ((indicator (meow--render-indicator))) (setq-local meow--indicator indicator))) +(defun meow--state-p (state) + (funcall (intern (concat "meow-" (symbol-name state) "-mode-p")))) + (defun meow--current-state () - (cond - ((bound-and-true-p meow-insert-mode) 'insert) - ((bound-and-true-p meow-normal-mode) 'normal) - ((bound-and-true-p meow-motion-mode) 'motion) - ((bound-and-true-p meow-keypad-mode) 'keypad) - ((bound-and-true-p meow-beacon-mode) 'beacon))) + meow--current-state) (defun meow--should-update-display-p () (cl-case meow-update-display-in-macro @@ -178,28 +174,17 @@ For performance reasons, we save current cursor type to ((nil) (null executing-kbd-macro)))) +(defun meow-update-display () + (when (meow--should-update-display-p) + (meow--update-indicator) + (meow--update-cursor))) + (defun meow--switch-state (state) "Switch to STATE." (unless (eq state (meow--current-state)) - (cl-case state - ('insert - (meow-insert-mode 1)) - ('normal - (meow-normal-mode 1)) - ('motion - ;; We will refresh `meow--origin-commands' when switch from normal to motion. - (when (eq 'normal (meow--current-state)) - (meow-normal-mode -1) - (meow--save-origin-commands)) - (meow-motion-mode 1)) - ('keypad - (meow-keypad-mode 1)) - ('beacon - (meow-beacon-mode 1))) - (run-hook-with-args 'meow-switch-state-hook state) - (when (meow--should-update-display-p) - (meow--update-indicator) - (meow--update-cursor)))) + (let ((mode (alist-get state meow-state-mode-alist))) + (funcall mode 1)) + (run-hook-with-args 'meow-switch-state-hook state))) (defun meow--exit-keypad-state () "Exit keypad state." @@ -450,9 +435,10 @@ For performance reasons, we save current cursor type to (defun meow--save-origin-commands () (setq meow--origin-commands nil) - (cl-loop for key in meow--motion-overwrite-keys do + (cl-loop for key-code being the key-codes of meow-motion-state-keymap do (ignore-errors - (let ((cmd (key-binding (kbd key)))) + (let* ((key (char-to-string key-code)) + (cmd (key-binding (kbd key)))) (when (and (commandp cmd) (not (equal cmd 'undefined))) (let ((rebind-key (concat meow-motion-remap-prefix key))) diff --git a/meow-var.el b/meow-var.el index ab66569..49046e3 100644 --- a/meow-var.el +++ b/meow-var.el @@ -241,6 +241,25 @@ For examples: :group 'meow :type 'string) +(defvar meow-state-mode-alist + '((normal . meow-normal-mode) + (insert . meow-insert-mode) + (keypad . meow-keypad-mode) + (motion . meow-motion-mode) + (beacon . meow-beacon-mode)) + "Alist of meow states -> modes") + +(defvar meow-update-cursor-functions-alist + '((meow--cursor-null-p . meow--update-cursor-default) + (minibufferp . meow--update-cursor-default) + (meow-insert-mode-p . meow--update-cursor-insert) + (meow-normal-mode-p . meow--update-cursor-normal) + (meow-motion-mode-p . meow--update-cursor-motion) + (meow-keypad-mode-p . meow--update-cursor-motion) + (meow-beacon-mode-p . meow--update-cursor-beacon) + ((lambda () t) . meow--update-cursor-default)) + "Alist of predicates to functions that set cursor type and color.") + (defvar meow-keypad-describe-keymap-function 'meow-describe-keymap "The function used to describe (KEYMAP) during keypad execution. @@ -421,6 +440,9 @@ Has a structure of (sel-type point mark).") ;;; Internal variables +(defvar-local meow--current-state 'normal + "A symbol represent current state.") + (defvar-local meow--end-kmacro-on-exit nil "Whether we end kmacro recording when exit insert state.") @@ -454,10 +476,6 @@ Has a structure of (sel-type point mark).") (defvar meow--keypad-help nil "If keypad in help mode.") -(defvar meow--motion-overwrite-keys - '("SPC") - "A list of keybindings to overwrite in MOTION state.") - (defvar meow--beacon-backup-hl-line nil "Whether hl-line is enabled by user.")