diff options
Diffstat (limited to 'configs/shared/emacs/.emacs.d/elpa/general-20180628.1112/general.el')
-rw-r--r-- | configs/shared/emacs/.emacs.d/elpa/general-20180628.1112/general.el | 2469 |
1 files changed, 2469 insertions, 0 deletions
diff --git a/configs/shared/emacs/.emacs.d/elpa/general-20180628.1112/general.el b/configs/shared/emacs/.emacs.d/elpa/general-20180628.1112/general.el new file mode 100644 index 000000000000..0f6ea39a9081 --- /dev/null +++ b/configs/shared/emacs/.emacs.d/elpa/general-20180628.1112/general.el @@ -0,0 +1,2469 @@ +;;; general.el --- Convenience wrappers for keybindings. -*- lexical-binding: t -*- + +;; Author: Fox Kiester <noct@openmailbox.org> +;; URL: https://github.com/noctuid/general.el +;; Package-Version: 20180628.1112 +;; Created: February 17, 2016 +;; Keywords: vim, evil, leader, keybindings, keys +;; Package-Requires: ((emacs "24.4") (cl-lib "0.5")) +;; Version: 0.1 + +;; This file is not part of GNU Emacs. + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; This package provides convenient wrappers for more succinctly defining +;; keybindings. It allows defining multiple keys at once, specifying an +;; arbitrary number of named prefix keys to be used in key definitions, +;; implicitly wrapping key strings with (kbd ...), and more. It provides a +;; single function for standard emacs key definitions as well as evil key +;; definitions for any evil state and any keymap. It also provides a setup +;; function to generate "nmap", "vmap", etc. keybinding functions for evil. + +;; For more information see the README in the online repository. + +;;; Code: +(require 'cl-lib) + +;; * Settings +(defgroup general nil + "Gives convenient wrappers for key definitions." + :group 'convenience + :prefix 'general-) + +(defcustom general-implicit-kbd t + "Whether to implicitly wrap a (kbd) around `general-define-key' keys. +This applies to the prefix key as well. This option is provided to make it easy + to transition from other key definers to `general-define-key'. It does not + apply to other helpers such as `general-key', `general-key-dispatch', and + `general-translate-key'. These will always use `kbd' on keys that are + strings." + :group 'general + :type 'boolean) + +(defcustom general-default-prefix nil + "The default prefix key sequence to use." + :group 'general + :type 'string) +(make-obsolete-variable 'general-default-prefix + "This functionality will be removed in the future." + "2018-01-21") + +(defcustom general-default-non-normal-prefix nil + "The default prefix key sequence to use for the 'emacs and 'insert states. +Note that this setting is only useful for evil-users and will only have an +effect when binding keys in the 'emacs and/or 'insert states or in the +'evil-insert-state-map and/or 'evil-emacs-state-map keymaps. When this is not +specified, `general-default-prefix' will be the default prefix for any states +and keymaps. If this is specified `general-default-prefix' or the arg to :prefix +will not be used when binding keys in the insert and emacs states." + :group 'general + :type 'string) +(make-obsolete-variable 'general-default-non-normal-prefix + "This functionality will be removed in the future." + "2018-01-21") + +(defcustom general-default-global-prefix nil + "The default prefix key sequence to use for all evil states. +This setting is only useful for evil users. Note that like with +`general-default-non-normal-prefix', if this or :global-prefix is specified, +`general-default-prefix' or the arg to :prefix will not be used for binding +keys in the insert and emacs states. If you don't need a different or extra +prefix for one or both state types (insert and emacs vs. the other states), +just use `general-default-prefix'/:prefix by itself." + :group 'general + :type 'string) +(make-obsolete-variable 'general-default-global-prefix + "This functionality will be removed in the future." + "2018-01-21") + +(define-widget 'general-state 'lazy + "General's evil state type." + :type '(choice + (const :tag "Insert state" insert) + (const :tag "Emacs state" emacs) + (const :tag "Normal state" normal) + (const :tag "Visual state" visual) + (const :tag "Motion state" motion) + (const :tag "Operator state" operator) + (const :tag "Replace state" replace) + (const :tag "Use define-key not evil-define-key" nil) + ;; other packages define states + symbol)) + +(defcustom general-default-states nil + "The default evil state(s) to make mappings in. +Non-evil users should keep this nil." + :group 'general + :type '(choice general-state + (set general-state))) +(make-obsolete-variable 'general-default-states + "This functionality will be removed in the future." + "2018-01-21") + +(defcustom general-non-normal-states '(insert emacs hybrid iedit-insert) + "List of \"non-normal\" evil states (used with :non-normal-prefix). When + :states is not specified (only :keymaps), these will automatically be expanded + to their full global evil keymap equivalents." + :group 'general + :type '(repeat general-state)) + +(define-widget 'general-keymap 'lazy + "General's keymap type." + :type '(choice + (const :tag "Global keymap" global) + (const :tag "Buffer local keymap" local) + symbol)) + +(defcustom general-default-keymaps 'global + "The default keymap(s) to bind keys in." + :group 'general + :type '(choice general-keymap + (repeat general-keymap))) +(make-obsolete-variable 'general-default-keymaps + "This functionality will be removed in the future." + "2018-01-21") + +(defcustom general-vim-definer-default nil + "Whether set the states or keymaps in a `general-create-vim-definer' function. +If nil, use the default from when the function was created. If 'keymaps, set the +default keymaps. If 'states, set the default states." + :group 'general + :type '(choice + (const :tag "Default to setting :keymaps" keymaps) + (const :tag "Default to setting :states" states) + (const :tag "Use the initial default" nil))) +(make-obsolete-variable 'general-vim-definer-default + "This functionality is no longer necessary." + "2018-01-20") + +(defvar general-keybindings nil + "Holds all the keybindings created with `general-define-key' (and wrappers). +This is an alist of a keymap to an alist of a state to keybindings.") + +(defvar general-local-keybindings nil + "Holds all the local keybindings created with `general-define-key'. +This is an alist of a state to keybindings.") +(make-variable-buffer-local 'general-local-keybindings) + +(define-widget 'general-alist 'lazy + "General's alist type." + :type '(alist :key-type (or symbol (repeat symbol)) + :value-type symbol)) + +(defcustom general-keymap-aliases + '((override . general-override-mode-map) + ((i insert) . evil-insert-state-map) + ((e emacs) . evil-emacs-state-map) + ((h hybrid) . evil-hybrid-state-map) + ((n normal) . evil-normal-state-map) + ((v visual) . evil-visual-state-map) + ((m motion) . evil-motion-state-map) + ((o operator) . evil-operator-state-map) + ((r replace) . evil-replace-state-map) + ((in inner) . evil-inner-text-objects-map) + ((out outer) . evil-outer-text-objects-map)) + "An alist for mapping short keymap names to their full names. +Earlier entries have higher precedence." + :group 'general + :type 'general-alist) + +(defcustom general-state-aliases + '((i . insert) + (e . emacs) + (h . hybrid) + (n . normal) + (v . visual) + (m . motion) + (o . operator) + (r . replace)) + "An alist for mapping short state names to their full names. +Earlier entries have higher precedence." + :group 'general + :type 'general-alist) + +;; ** `general-describe-keybindings' Settings +(defcustom general-describe-keybinding-sort-function nil + "Function used to sort keybindings for `general-describe-keybindings'." + :group 'general + :type '(choice function (const nil))) + +(defcustom general-describe-state-sort-function + #'general--sort-evil-state-conses + "Function used to sort the states conses for `general-describe-keybindings'." + :group 'general + :type '(choice function (const nil))) + +(defcustom general-describe-keymap-sort-function nil + "Function used to sort the keymap conses`general-keybindings' for +`general-describe-keybindings'." + :group 'general + :type '(choice function (const nil))) + +(defcustom general-describe-priority-keymaps + '(local + global + evil-insert-state-map + evil-emacs-state-map + evil-hybrid-state-map + evil-normal-state-map + evil-visual-state-map + evil-motion-state-map + evil-operator-state-map + evil-replace-state-map + evil-inner-text-objects-map + evil-outer-text-objects-map + evil-ex-search-keymap + evil-ex-completion-map + evil-command-window-mode-map + evil-window-map) + "Keymaps to print first for `general-describe-keybindings'." + :group 'general + :type '(repeat sybmol)) + +(defcustom general-describe-update-previous-definition 'on-change + "Whether to update the previous definition when a key is bound. +When set to 'on-change, the previous definition will only be updated when the +definition changes (e.g. re-evaluating a file with keybindings will not affect +the stored previous definition). When set to nil, it will only be updated when +the key was previously unbound." + :group 'general + ;; can't think of a use case, but add 'always if requested + ;; t is equivalent of on-change + :type '(choice + (const :tag "When definition has changed" on-change) + (const :tag "When the key was previously unbound" nil))) + +;; * Override Minor Modes +(defcustom general-override-auto-enable t + "Whether to automatically enable `general-override-mode'. +If non-nil, enable `general-override-mode' when binding a key in +`general-override-mode-map'." + :group 'general + :type 'boolean) + +(defvar general-override-mode-map (make-sparse-keymap) + "A keymap that will take priority over other minor mode keymaps. +This is only for non-evil keybindings (it won't override keys bound with +`evil-define-key'.") + +(define-minor-mode general-override-mode + "A global minor mode used for key definitions that should override others." + :lighter "" + :global t + :keymap general-override-mode-map) + +(defvar-local general-override-local-mode-map (make-sparse-keymap) + "A keymap that will take priority over other minor mode keymaps. +This keymap is buffer-local and will take precedence over +`general-override-mode-map'. General uses this keymap when creating non-evil +local keybindings.") + +(define-minor-mode general-override-local-mode + "A local minor mode used for key definitions that should override others." + :lighter "" + :keymap general-override-local-mode-map) + +(defvar-local general-maps-alist + `((general-override-mode . ,general-override-mode-map)) + "Holds the (mode . keymap) pairs for general's override modes.") +;; not affected by changing major modes +(put 'general-maps-alist 'permanent-local t) + +(defvar-local general--maps-alist-updated nil + "Whether `general-maps-alist' has been set correctly for the current buffer.") +(put 'general-maps-alist 'permanent-local t) + +(declare-function evil-make-intercept-map "evil-core") +(defun general-override-make-intercept-maps (_sym states) + "Make intercept keymaps for STATES in `general-override-mode-map'. +This means that keys bound in STATES for `general-override-mode-map' will take +precedence over keys bound in other evil auxiliary maps." + ;; can't use `general-with-eval-after-load' here; not available + (with-eval-after-load 'evil + ;; TODO eventually use new evil-make-intercept-map arg + (dolist (state states) + (evil-make-intercept-map + (evil-get-auxiliary-keymap general-override-mode-map state t t) + state)))) + +(defcustom general-override-states + '(insert + emacs + hybrid + normal + visual + motion + operator + replace) + "States to make intercept maps for in `general-override-mode-map'. +Note that this uses :set, meaning that if you want to change the value, you +should either set it using customize (e.g. `general-setq' or +`customize-set-variable') or set it before loading general if using `setq'." + :group 'general + :type '(repeat general-state) + :set #'general-override-make-intercept-maps) + +(defun general--update-maps-alist () + "Update `general-maps-alist' for override modes. +This is necessary to ensure `general-override-local-mode-map' is the buffer's +local version." + (setq general-maps-alist + `((general-override-local-mode . ,general-override-local-mode-map) + (general-override-mode . ,general-override-mode-map)) + general--maps-alist-updated t)) + +(cl-pushnew 'general-maps-alist emulation-mode-map-alists) + +(defun general-local-map () + "Return `general-override-local-mode-map'. +Also turn on `general-override-local-mode' and update `general-maps-alist'." + (or general-override-local-mode (general-override-local-mode)) + (unless general--maps-alist-updated + (general--update-maps-alist)) + general-override-local-mode-map) + +;; * General Helpers +(defmacro general-with-eval-after-load (file &rest body) + "Like `with-eval-after-load' but don't always add to `after-load-alist'. +When FILE has already been loaded, execute BODY immediately without adding it to +`after-load-alist'." + (declare (indent 1)) + `(if (if (stringp ,file) + (load-history-filename-element + (purecopy (load-history-regexp ,file))) + (featurep ,file)) + (progn ,@body) + (eval-after-load ,file (lambda () ,@body)))) + +(defun general--unalias (symbol &optional statep) + "Return the full keymap or state name associated with SYMBOL. +If STATEP is non-nil, check `general-state-aliases' instead of +`general-keymap-aliases'." + (let ((match + (cdr (cl-assoc symbol + (if statep + general-state-aliases + general-keymap-aliases) + ;; test-fn is new to assoc in 26.1 + :test (lambda (symbol key) + (or (eq symbol key) + (ignore-errors (memq symbol key)))))))) + (or match symbol))) + +;; don't want to reuse `general--unalias' since the user can alter +;; `general-keymap-aliases' +(defun general--evil-keymap-for-state (state) + "Return a symbol corresponding to the global evil keymap for STATE." + (intern (concat "evil-" (symbol-name state) "-state-map"))) + +(defun general--kbd (key) + "Use `kbd' on KEY when it is a string." + (if (stringp key) + (kbd key) + key)) + +;; TODO refactor to be more straightforward +(defun general--concat (nokbd &rest keys) + "Concatenate the strings in KEYS. +If `general-implicit-kbd' is non-nil, interleave the strings in KEYS with +spaces; unless NOKBD is non-nil, apply (kbd ...) to the result. If +`general-implicit-kbd' is nil, just concatenate the keys." + (setq keys (remove nil keys)) + (if general-implicit-kbd + (let ((keys (mapconcat (lambda (x) + (if (vectorp x) + (key-description x) + x)) + keys " "))) + (if nokbd + keys + (kbd keys))) + (apply #'concat keys))) + +(defun general--apply-prefix-and-kbd (prefix maps) + "Prepend the PREFIX sequence to all the keys that are strings in MAPS. +Also apply (kbd ...) to key and definition strings if `general-implicit-kbd' is +non-nil." + (setq prefix (or prefix "")) + (cl-loop for (key def) on maps by 'cddr + collect (general--concat nil prefix key) + and collect def)) + +(defun general--lookup-key (state keymap key &optional minor-mode-p) + "Return the current definition for STATE, KEYMAP, and KEY." + (when key + (let ((keymap (general--get-keymap state keymap minor-mode-p))) + (when keymap + (let ((def (lookup-key keymap key))) + (if (and (numberp def) (= def 1)) + nil + def)))))) + +(defun general--record-keybindings (keymap state maps &optional minor-mode-p) + "For KEYMAP and STATE, add MAPS to `general-keybindings'. +If KEYMAP is \"local\", add MAPS to `general-local-keybindings.' For non-evil +keybindings, STATE will be nil. Duplicate keys will be replaced with the new +ones. MINOR-MODE-P should be non-nil when keymap corresponds to a minor-mode +name (as used with `evil-define-minor-mode-key') as opposed to a keymap name." + (if (and state (not (featurep 'evil))) + (general-with-eval-after-load 'evil + (general--record-keybindings keymap state maps minor-mode-p)) + (let* (keys + (maps (cl-loop + for (key new-def _orig-def) on maps by 'cl-cdddr + collect + (list key + new-def + (let* ((current-def (general--lookup-key + state keymap key minor-mode-p)) + ;; none of these will fail if nil + (keymap-cons (assq keymap general-keybindings)) + (state-cons (assq state (cdr keymap-cons))) + (mapping (cl-find key (cdr state-cons) + :test #'equal :key #'car)) + (previous-def (cl-caddr mapping))) + (if (or + (and current-def (not previous-def)) + (and general-describe-update-previous-definition + (not (equal new-def current-def)))) + current-def + previous-def))) + do (push key keys)))) + (cond ((eq keymap 'local) + (unless (assq state general-local-keybindings) + (add-to-list 'general-local-keybindings (list state))) + (let ((state-cons (assq state general-local-keybindings))) + (setcdr state-cons + ;; remove old duplicate keys + (cl-remove-duplicates (append (cdr state-cons) maps) + :key #'car + :test #'equal)))) + (t + (unless (assq keymap general-keybindings) + (add-to-list 'general-keybindings (list keymap))) + (unless (assq state (assq keymap general-keybindings)) + (setcdr (assq keymap general-keybindings) + (append (cdr (assq keymap general-keybindings)) + (list (list state))))) + (let ((state-cons (assq state (assq keymap general-keybindings)))) + (setcdr state-cons + (cl-remove-duplicates (append (cdr state-cons) maps) + :key #'car + :test #'equal)))))))) + +;; don't force non-evil user to require evil for one function +(defun general--delay (condition form hook &optional append local name) + "Execute FORM when CONDITION becomes true, checking with HOOK. +NAME specifies the name of the entry added to HOOK. If APPEND is +non-nil, the entry is appended to the hook. If LOCAL is non-nil, +the buffer-local value of HOOK is modified. + +This is `evil-delay'." + (declare (indent 2)) + (if (and (not (booleanp condition)) (eval condition)) + (eval form) + (let* ((name (or name (format "general-delay-form-in-%s" hook))) + (fun (make-symbol name)) + (condition (or condition t))) + (fset fun `(lambda (&rest args) + (when ,condition + (remove-hook ',hook #',fun ',local) + ,form))) + (put fun 'permanent-local-hook t) + (add-hook hook fun append local)))) + +(defun general--getf (def fallback-plist keyword) + "From DEF or FALLBACK-PLIST get the corresponding value for KEYWORD. +FALLBACK-PLIST will be checked when KEYWORD does not exist in DEF (not in cases +where it is explicitly specified as nil). If DEF isn't a general extended +definition, only check in FALLBACK-PLIST." + (if (general--extended-def-p def) + (cl-getf def keyword + (cl-getf fallback-plist keyword)) + (cl-getf fallback-plist keyword))) + +(defun general--getf2 (plist keyword1 keyword2) + "Check in PLIST for either KEYWORD1 or KEYWORD2." + (or (cl-getf plist keyword1) + (cl-getf plist keyword2))) + +(declare-function evil-get-minor-mode-keymap "evil-core") +(declare-function evil-state-property "evil-common") +(declare-function evil-get-auxiliary-keymap "evil-core") +(cl-defun general--get-keymap (state &optional keymap + minor-mode + ignore-special) + "Transform STATE and the symbol or keymap KEYMAP into the appropriate keymap. +If MINOR-MODE and STATE are non-nil, use `evil-get-minor-mode-keymap'. If +IGNORE-SPECIAL is non-nil, do not try to resolve the \"special\" keymaps 'global +and 'local. In this case, the only thing this function will do is return the +actually keymap if KEYMAP is a symbol besides 'global or 'local. Otherwise the +keymap returned depends on whether STATE is specified. Note that if STATE is +specified, evil needs to be installed and will be required. + +STATE nil: +'local - Run/return `general-local-map' +'global - Run/return `current-global-map' +else - Return keymap or (symbol-value keymap) + +STATE non-nil: +'local - Return the corresponding evil local map +'global - Return the corresponding evil global map +else - Return the corresponding evil auxiliary or minor mode map" + (when (and (symbolp keymap) + (not (memq keymap '(global local)))) + (setq keymap (symbol-value keymap))) + (when ignore-special + (cl-return-from general--get-keymap keymap)) + (if state + (if (require 'evil nil t) + (cond ((or (null keymap) + (eq keymap 'global)) + (evil-state-property state :keymap t)) + (minor-mode + (evil-get-minor-mode-keymap state keymap)) + ((eq keymap 'local) + (evil-state-property state :local-keymap t)) + (t + (evil-get-auxiliary-keymap keymap state t t))) + (error "Evil is required if state is specified")) + (cl-case keymap + (global (current-global-map)) + (local (general-local-map)) + (t keymap)))) +(define-obsolete-function-alias 'general--parse-keymap 'general--get-keymap + "2018-01-14") + +(defun general--remove-keyword-args (rest) + "Remove all keyword arguments from the list REST. +Return a list of the altered REST list and a list of the removed keyword +arguments. The order of arguments will be preserved. Note that the length of +REST does not need to be even (i.e. there can be an odd number of positional +arguments)." + (let (args + kargs) + (while rest + (cond ((keywordp (car rest)) + (push (pop rest) kargs) + (push (pop rest) kargs)) + (t + (push (pop rest) args)))) + (list (nreverse args) (nreverse kargs)))) + +(defmacro general--ensure-lists (&rest vars) + "Ensure that all variables in VARS are lists if they are not already. +If any variable is a lambda, it will not be considered to be a list. If a var is +nil, it will be set to (list nil)." + `(progn + ,@(mapcar (lambda (var) + `(unless (and ,var + (listp ,var) + ;; lambdas are lists + (not (functionp ,var))) + (setq ,var (list ,var)))) + vars))) + +;; * Extended Key Definition Language +;; ** Variables +(defvar general-extended-def-keywords + '(:which-key :wk :properties :repeat :jump) + "Extra keywords that are valid for extended definitions. + +These can work both locally (in extended definitions) and globally (in which +case they apply to all definitions including normal ones). Note that not all +keywords need to make sense/work globally. If the keyword should be ignored when +used globally, add it to `general-extended-def-global-ignore-keywords' as well. + +For each keyword there should be a corresponding function named +general-extended-def-:<keyword> which will be passed state, keymap (the symbol +not actual keymap), key (the internal representation, i.e. `kbd' already called +if necessary), edef (always a plist; normal definitions will automatically be +converted), and kargs (the original `general-define-key' keyword argument plist; +useful when the keyword can be used globally or has helper keywords that can be +used globally). This function is only called for side effects; if you actually +need to alter the definition, you should add the keyword to +`general-rewrite-def-keywords' or `general-rewrite-def-after-keywords' instead. +The order of those lists matters, but the order of this list does not. + +`general--get-keymap' may be useful for getting the actual keymap from the +keymap symbol. `general--getf' may be useful for keywords (helper or main) that +can be specified globally (in kargs) and overridden locally (in def).") + +(defvar general-rewrite-def-keywords + '(:keymap :prefix-command :prefix-keymap) + "Extended definition keywords that alter the definition. + +Each keyword should have a corresponding function named +general-extended-def-:<keyword> and should return a new extended definition +plist (with an altered :def entry). See `general-extended-def-keywords' for +information on the arguments this function should take. These functions will be +run in the order they appear in this list, and each will be passed the most +recent version of the extended definition plist. + +In contrast to the functions for `general-rewrite-def-after-keywords', these +functions will alter the definition before any `general-extended-def-keyword' +functions run. For example, if your function creates a newly named wrapper +command around the user-specified command, you'd want to add the keyword to this +list, so that `general-extended-def-keywords' functions would have access to new +command name (e.g. for :which-key to work properly). On the other hand, if the +keyword, for example, involves putting the definition in an extended menu item +like with :predicate, you should add to `general-rewrite-def-after-keywords' +instead.") + +(defvar general-rewrite-def-after-keywords + '(:predicate) + "Extended definition keywords that alter the definition. +See `general-rewrite-def-keywords' for more information.") + +(defvar general-extended-def-global-ignore-keywords + '(:keymap :prefix-command :prefix-map) + "Extended definitions that should be ignored when used globally. +For example, :prefix-command and :prefix-map are handled differently when used +globally (they have special interaction with other global keywords). :keymap, on +the other hand, doesn't make sense at all globally.") + +;; ** Normal Extended Definition Functions +(defvar which-key-replacement-alist) +(defun general--add-which-key-replacement (mode replacement) + (let* ((mode-match (assq mode which-key-replacement-alist)) + (mode-alist (cdr mode-match))) + (cond (mode + (push replacement mode-alist) + (if mode-match + (setcdr mode-match mode-alist) + (push (cons mode mode-alist) + which-key-replacement-alist))) + (t + (push replacement which-key-replacement-alist))))) + +(defvar which-key--prefix-title-alist) +(defun general--add-which-key-title-prefix (mode keys title-prefix) + (let* ((mode-match (assq mode which-key--prefix-title-alist)) + (title-mode-alist (cdr mode-match)) + (title-cons (cons keys title-prefix))) + (cond (mode + (push title-cons title-mode-alist) + (if mode-match + (setcdr mode-match + title-mode-alist) + (push (cons mode title-mode-alist) + which-key--prefix-title-alist))) + (t + (push title-cons which-key--prefix-title-alist))))) + +(defun general--remove-map (keymap) + "Remove \"-map\" from the symbol KEYMAP." ; + (intern (replace-regexp-in-string "-map$" "" (symbol-name keymap)))) + +;; TODO better documentation +(defun general-extended-def-:which-key (_state keymap key edef kargs) + "Add a which-key description for KEY. +If :major-modes is specified in EDEF, add the description for the corresponding +major mode. KEY should not be in the kbd format (kbd should have already been +run on it)." + (general-with-eval-after-load 'which-key + (let* ((wk (general--getf2 edef :which-key :wk)) + (major-modes (general--getf edef kargs :major-modes)) + (keymaps (plist-get kargs :keymaps)) + ;; index of keymap in :keymaps + (keymap-index (cl-dotimes (ind (length keymaps)) + (when (eq (nth ind keymaps) keymap) + (cl-return ind)))) + (mode (let ((mode (if (and major-modes (listp major-modes)) + (nth keymap-index major-modes) + major-modes))) + (if (eq mode t) + (general--remove-map keymap) + mode))) + (key (key-description key)) + (key-regexp (concat (when (general--getf edef kargs :wk-full-keys) + "\\`") + (regexp-quote key) + "\\'")) + (prefix (plist-get kargs :prefix)) + (binding (or (when (and (plist-get edef :def) + (not (plist-get edef :keymp))) + (plist-get edef :def)) + (when (and prefix + (string= key prefix)) + (plist-get kargs :prefix-command)))) + (replacement (cond ((stringp wk) + (cons nil wk)) + (t + wk))) + (match/replacement + (cons + (cons (when (general--getf edef kargs :wk-match-keys) + key-regexp) + (when (and (general--getf edef kargs :wk-match-binding) + binding + (symbolp binding)) + (symbol-name binding))) + replacement))) + (general--add-which-key-replacement mode match/replacement) + (when (and (consp replacement) + ;; lambda + (not (functionp replacement))) + (general--add-which-key-title-prefix + mode key (cdr replacement)))))) + +(defalias 'general-extended-def-:wk #'general-extended-def-:which-key) + +(declare-function evil-add-command-properties "evil-common") +(defun general-extended-def-:properties (_state _keymap _key edef kargs) + "Use `evil-add-command-properties' to add properties to a command. +The properties should be specified with :properties in either EDEF or KARGS." + (general-with-eval-after-load 'evil + (let ((properties (general--getf edef kargs :properties)) + (command (cl-getf edef :def))) + (apply #'evil-add-command-properties command properties)))) + +(defun general-extended-def-:repeat (_state _keymap _key edef kargs) + "Use `evil-add-command-properties' to set the :repeat property for a command. +The repeat property should be specified with :repeat in either EDEF or KARGS." + (general-with-eval-after-load 'evil + (let ((repeat-property (general--getf edef kargs :repeat)) + (command (cl-getf edef :def))) + (evil-add-command-properties command :repeat repeat-property)))) + +(defun general-extended-def-:jump (_state _keymap _key edef kargs) + "Use `evil-add-command-properties' to set the :jump property for a command. +The jump property should be specified with :jump in either EDEF or KARGS." + (general-with-eval-after-load 'evil + (let ((jump-property (general--getf edef kargs :jump)) + (command (cl-getf edef :def))) + (evil-add-command-properties command :jump jump-property)))) + +;; ** Extended Defintion Functions That Alter the Definition +(defun general-extended-def-:keymap (state keymap _key edef kargs) + "Return an extended definition for a keymap or a \"autoloaded\" keymap. +If the specified keymap does not exist, create a function that binds the keys it +was invoked with in STATE and KEYMAP to the keymap specified in the extended +definition EDEF and then act as if it was originally bound to that +keymap (subsequent keys will be looked up in the keymap). KARGS or EDEF should +contain the package in which the keymap is created (as specified with :package). +If the keymap already exists, it will simply be returned." + (let ((bind-keymap-sym (plist-get edef :def)) + (package (general--getf edef kargs :package)) + (definer (general--getf edef kargs :definer))) + (if (boundp bind-keymap-sym) + (setf (cl-getf edef :def) (symbol-value bind-keymap-sym)) + (if package + (setf (cl-getf edef :def) + ;; relying on lexical binding here + (lambda () + (interactive) + (unless (or (featurep package) + (require package nil t)) + (error (format "Failed to load package: %s" package))) + (unless (and (boundp bind-keymap-sym) + (keymapp (symbol-value bind-keymap-sym))) + (error (format + "A keymap called %s is not defined in the %s package" + bind-keymap-sym package))) + ;; use `this-command-keys' as `key' may not be the full sequence + (let ((keys (this-command-keys)) + (general-implicit-kbd nil)) + (general-define-key + :states state + :keymaps keymap + :definer definer + keys (symbol-value bind-keymap-sym)) + (setq prefix-arg current-prefix-arg + unread-command-events + (mapcar (lambda (ev) (cons t ev)) + (listify-key-sequence keys)))))) + (error "In order to \"autoload\" a keymap, :package must be specified")))) + edef) + +(defun general--define-prefix (command-name &optional map-name menu-name) + "Define a prefix command and/or keymap. +COMMAND-NAME corresponds to the prefix command name. When COMMAND-NAME is +non-nil, `define-prefix-command' will be used and will be passed MAP-NAME and +MENU-NAME. When COMMAND-NAME is nil and MAP-NAME is non-nil, only a prefix +keymap will be created, and its menu name/prompt will be set to MENU-NAME (if +MENU-NAME is non-nil). Existing prefix keymaps/commands will not be +recreated/rebound." + (if (or (and command-name (fboundp command-name)) + (and map-name (boundp map-name))) + (or command-name (symbol-value map-name)) + (cond (command-name + (define-prefix-command command-name map-name menu-name)) + (map-name + (eval `(defvar ,map-name (make-sparse-keymap ,menu-name))))))) + +(defun general-extended-def-:prefix-command (_state _keymap _key edef _kargs) + "Create and return a prefix command or map for the extended definition EDEF. +The :prefix-command, :prefix-map, and :prefix-name properties from EDEF are +passed to `general--define-prefix'." + ;; NOTE will be called twice if both specified, but doesn't matter because + ;; won't recreate prefix-command + (setf (cl-getf edef :def) + (general--define-prefix (plist-get edef :prefix-command) + (plist-get edef :prefix-map) + (plist-get edef :prefix-name))) + edef) + +(defalias 'general-extended-def-:prefix-map + #'general-extended-def-:prefix-command) + +;; http://endlessparentheses.com/define-context-aware-keys-in-emacs.html +(defun general-extended-def-:predicate (_state _keymap _key edef kargs) + "Return an altered extended definition EDEF with a predicate applied. +The predicate is obtained either from EDEF or KARGS." + (let ((def (cl-getf edef :def)) + (predicate (general--getf edef kargs :predicate))) + (setf (cl-getf edef :def) + `(menu-item + "" nil + :filter (lambda (&optional _) + (when ,predicate + ',def)))) + edef)) + +;; ** Parsing Extended Definitions +(defun general--extended-def-p (def) + "Return whether DEF is an extended definition." + (and (listp def) + (not (keymapp def)) + ;; lambda + (not (functionp def)) + (not (eq (car def) 'menu-item)) + ;; will error on cons + (ignore-errors (cl-some #'keywordp def)))) + +(defun general--normalize-extended-def (edef) + "Rewrite the extended definition EDEF to include a :def property. +If EDEF is not an extended defintion, make it into one. + +This handles the allowed shorthand syntax. For example, these are the same: + + (some-func) + (:def some-func) + +Some extended definition keywords can be used instead of :def (mainly for +backwards compatibility). For example, these are the same: + + (some-keymap :keymap t) + (:keymap some-keymap) + (:def some-keymap :keymap t)" + ;; NOTE: This is absolutely necessary for plist functions to work + (if (general--extended-def-p edef) + (unless (keywordp (car edef)) + (setq edef (cons :def edef))) + (setq edef (list :def edef))) + ;; :keymap checks :def always instead of :keymap, and :which-key also checks + ;; :def always (instead of :prefix-command) + ;; note that :keymap and :prefix-map will later rewrite their :def to the + ;; actual keymap value + (unless (plist-get edef :def) + (setf (cl-getf edef :def) + (cl-getf edef :keymap + (cl-getf edef :prefix-command + (plist-get edef :prefix-map))))) + edef) + +(defun general--extract-def (edef) + "Return the bindable definition from the extended definition EDEF." + (if (plist-get edef :ignore) + ;; just for side effects (e.g. which-key description for prefix) + ;; return something that isn't a valid definition + :ignore + (plist-get edef :def))) + +(defun general--run-extended-def-functions (state keymap key edef kargs) + "Run the extended definition functions for the matched keywords. +Pass each extended definition function STATE, KEYMAP, KEY, EDEF, and KARGS. For +each keyword from `general-extended-def-keywords', +`general-rewrite-def-keywords', and `general-rewrite-def-after-keywords' found +in EDEF or KARGS, call the corresponding function named +general-extended-def-:<keyword>. The functions for +`general-rewrite-def-keywords' will rewrite the extended definition plist before +the functions for `general-extended-def-keywords' are called, and the functions +for `general-rewrite-def-after-keywords' are called after that. Functions +are called in the order they appear in each list. Finally, return the +potentially altered extended definition plist." + (cl-flet ((run-edef-functions + (keywords &optional alter-def) + (dolist (keyword keywords) + (when (or (plist-get edef keyword) + (and (not + (memq + keyword + general-extended-def-global-ignore-keywords)) + (plist-get kargs keyword))) + (let ((ret (funcall + (intern (format "general-extended-def-%s" keyword)) + state keymap key edef kargs))) + (when alter-def + (setq edef ret))))))) + (run-edef-functions general-rewrite-def-keywords t) + (run-edef-functions general-extended-def-keywords) + (run-edef-functions general-rewrite-def-after-keywords t)) + edef) + +(defun general--parse-def (state keymap key def kargs) + "Rewrite DEF into a valid/bindable definition. +This function will execute all extended definitions, potentially rewriting the +original definition (e.g. applying a predicate). Pass STATE, KEYMAP, KEY, DEF, and +KARGS to each matched extended definition function. See +`general--run-extended-def-functions' for more information." + (setq def (general--normalize-extended-def def)) + (general--extract-def + (general--run-extended-def-functions state keymap key def kargs))) + +(defun general--parse-maps (state keymap maps kargs) + "Rewrite MAPS so that the definitions are bindable. +This includes possibly calling `kbd' on keys and parsing extended definitions. +Turn key/binding pairs in MAPS into triples in the form of (key parsed-def +original-def) where parsed-def is the bindable form and original-def is the +original definition as an extended definition plist (turn normal definitions +into extended definition plists and implicitly add \":def\" to the beginning of +extended definitions when necessary)." + (let (bindable-def) + (cl-loop for (key def) on maps by 'cddr + do (setq bindable-def + (general--parse-def state keymap key def kargs)) + unless (eq bindable-def :ignore) + collect key + and collect (if general-implicit-kbd + (general--kbd bindable-def) + bindable-def) + and collect (general--normalize-extended-def def)))) + +;; * Helper Key Definers +(declare-function evil-define-minor-mode-key "evil-core") +(defun general-minor-mode-define-key (state mode key def _orig-def _kargs) + "A wrapper for `evil-define-minor-mode-key'." + (general-with-eval-after-load 'evil + (evil-define-minor-mode-key state mode key def))) + +(declare-function lispy-define-key "lispy") +(defun general-lispy-define-key (_state keymap key def orig-def kargs) + "A wrapper for `lispy-define-key'." + (general-with-eval-after-load 'lispy + (let* ((keymap (general--get-keymap nil keymap)) + (key (key-description key)) + (plist (general--getf orig-def kargs :lispy-plist))) + (apply #'lispy-define-key keymap key def plist)))) + +(declare-function worf-define-key "worf") +(defun general-worf-define-key (_state keymap key def orig-def kargs) + "A wrapper for `worf-define-key'." + (general-with-eval-after-load 'worf + (let* ((keymap (general--get-keymap nil keymap)) + (key (key-description key)) + (plist (general--getf orig-def kargs :worf-plist))) + (apply #'worf-define-key keymap key def plist)))) + +(declare-function lpy-define-key "lpy") +(defun general-lpy-define-key (_state keymap key def _orig-def _kargs) + "A wrapper for `lpy-define-key'." + (general-with-eval-after-load 'lpy + (let* ((keymap (general--get-keymap nil keymap)) + (key (key-description key))) + (lpy-define-key keymap key def)))) + +(declare-function evil-define-key* "evil-core") +(defun general--define-key-dispatch (state keymap maps kargs) + "In STATE (if non-nil) and KEYMAP, bind MAPS. +MAPS is composed of triplets of (key parsed-def original-def). This function +determines the appropriate base definer function to use based depending on +whether :definer is present in original-def or KARGS or whether STATE is +non-nil if no custom definer is specified." + (when (and general-override-auto-enable + (eq keymap 'general-override-mode-map) + (not general-override-mode)) + (general-override-mode)) + (while maps + (let* ((key (pop maps)) + (def (pop maps)) + (orig-def (pop maps)) + (definer (general--getf orig-def kargs :definer))) + (if definer + (funcall (intern (format "general-%s-define-key" + (symbol-name definer))) + state keymap key def orig-def kargs) + (cond (state + ;; just to get the symbol-value of the keymap when it is not + ;; global/local + (setq keymap (general--get-keymap nil keymap nil t)) + (general-with-eval-after-load 'evil + (evil-define-key* state keymap key def))) + (t + (setq keymap (general--get-keymap nil keymap)) + (define-key keymap key def))))))) + +(defvar general--definer-p nil + "Whether the current keybinding is being created with `general-define-key'.") + +(defun general--define-key + (states keymap maps non-normal-maps global-maps kargs) + "A helper function for `general-define-key'. +Choose based on STATES and KEYMAP which of MAPS, NON-NORMAL-MAPS, and +GLOBAL-MAPS to use for the keybindings. This function will rewrite extended +definitions, add predicates when applicable, and then choose the base function +to bind the keys with by calling `general--define-key-dispatch'." + (let ((general--definer-p t)) + (dolist (state states) + (let* ((non-normal-p (if state + (memq state general-non-normal-states) + (memq keymap + (mapcar #'general--evil-keymap-for-state + general-non-normal-states)))) + (valid-maps (list (cond ((and non-normal-maps non-normal-p) + non-normal-maps) + ((and global-maps non-normal-p) + nil) + (t + maps)) + global-maps))) + (dolist (maps valid-maps) + (when maps + (setq maps (general--parse-maps state keymap maps kargs)) + ;; NOTE: :definer 'minor-mode cannot be specified locally + (general--record-keybindings keymap state maps + (eq (cl-getf kargs :definer) + 'minor-mode)) + (general--define-key-dispatch state keymap maps kargs))))))) + +;; * Functions With Keyword Arguments +;;;###autoload +(cl-defun general-define-key + (&rest maps &key + definer + (states general-default-states) + (keymaps general-default-keymaps keymaps-specified-p) + (prefix general-default-prefix) + (non-normal-prefix general-default-non-normal-prefix) + (global-prefix general-default-global-prefix) + infix + prefix-command + prefix-map + prefix-name + predicate + ;; related to extended definitions + package + properties + repeat + jump + major-modes + (wk-match-keys t) + (wk-match-binding t) + (wk-full-keys t) + ;; for custom key definers + lispy-plist + worf-plist + &allow-other-keys) + "The primary key definition function provided by general.el. + +Define MAPS, optionally using DEFINER, in the keymap(s) corresponding to STATES +and KEYMAPS. + +MAPS consists of paired keys (vectors or strings; also see +`general-implicit-kbd') and definitions (those mentioned in `define-key''s +docstring and general.el's \"extended\" definitions). All pairs (when not +ignored) will be recorded and can be later displayed with +`general-describe-keybindings'. + +If DEFINER is specified, a custom key definer will be used to bind MAPS. See +general.el's documentation/README for more information. + +Unlike with normal key definitions functions, the keymaps in KEYMAPS should be +quoted (this allows using the keymap name for other purposes, e.g. deferring +keybindings if the keymap symbol is not bound, optionally inferring the +corresponding major mode for a symbol by removing \"-map\" for :which-key, +easily storing the keymap name for use with `general-describe-keybindings', +etc.). Note that general.el provides other key definer macros that do not +require quoting keymaps. + +STATES corresponds to the evil state(s) to bind the keys in. Non-evil users +should not set STATES. When STATES is non-nil, `evil-define-key*' will be +used (the evil auxiliary keymaps corresponding STATES and KEYMAPS will be used); +otherwise `define-key' will be used (unless DEFINER is specified). KEYMAPS +defaults to 'global. There is also 'local, which create buffer-local +keybindings for both evil and non-evil keybindings. There are other special, +user-alterable \"shorthand\" symbols for keymaps and states (see +`general-keymap-aliases' and `general-state-aliases'). + +Note that STATES and KEYMAPS can either be lists or single symbols. If any +keymap does not exist, those keybindings will be deferred until the keymap does +exist, so using `eval-after-load' is not necessary with this function. + +PREFIX corresponds to a key to prefix keys in MAPS with and defaults to none. To +bind/unbind a key specified with PREFIX, \"\" can be specified as a key in +MAPS (e.g. ...:prefix \"SPC\" \"\" nil... will unbind space). + +The keywords in this paragraph are only useful for evil users. If +NON-NORMAL-PREFIX is specified, this prefix will be used instead of PREFIX for +states in `general-non-normal-states' (e.g. the emacs and insert states). This +argument will only have an effect if one of these states is in STATES or if +corresponding global keymap (e.g. `evil-insert-state-map') is in KEYMAPS. +Alternatively, GLOBAL-PREFIX can be used with PREFIX and/or NON-NORMAL-PREFIX to +bind keys in all states under the specified prefix. Like with NON-NORMAL-PREFIX, +GLOBAL-PREFIX will prevent PREFIX from applying to `general-non-normal-states'. +INFIX can be used to append a string to all of the specified prefixes. This is +potentially useful when you are using GLOBAL-PREFIX and/or NON-NORMAL-PREFIX so +that you can sandwich keys in between all the prefixes and the specified keys in +MAPS. This may be particularly useful if you are using default prefixes in a +wrapper function/macro so that you can add to them without needing to re-specify +all of them. If none of the other prefix keyword arguments are specified, INFIX +will have no effect. + +If PREFIX-COMMAND or PREFIX-MAP is specified, a prefix command and/or keymap +will be created. PREFIX-NAME can be additionally specified to set the keymap +menu name/prompt. If PREFIX-COMMAND is specified, `define-prefix-command' will +be used. Otherwise, only a prefix keymap will be created. Previously created +prefix commands/keymaps will never be redefined/cleared. All prefixes (including +the INFIX key, if specified) will then be bound to PREFIX-COMMAND or PREFIX-MAP. +If the user did not specify any PREFIX or manually specify any KEYMAPS, general +will bind all MAPS in the prefix keymap corresponding to either PREFIX-MAP or +PREFIX-COMMAND instead of in the default keymap. + +PREDICATE corresponds to a predicate to check to determine whether a definition +should be active (e.g. \":predicate '(eobp)\"). Definitions created with a +predicate will only be active when the predicate is true. When the predicate is +false, key lookup will continue to search for a match in lower-precedence +keymaps. + +In addition to the normal definitions supported by `define-key', general.el also +provides \"extended\" definitions, which are plists containing the normal +definition as well as other keywords. For example, PREDICATE can be specified +globally or locally in an extended definition. New global (~general-define-key~) +and local (extended definition) keywords can be added by the user. See +`general-extended-def-keywords' and general.el's documentation/README for more +information. + +PACKAGE is the global version of the extended definition keyword that specifies +the package a keymap is defined in (used for \"autoloading\" keymaps) + +PROPERTIES, REPEAT, and JUMP are the global versions of the extended definition +keywords used for adding evil command properties to commands. + +MAJOR-MODES, WK-MATCH-KEYS, WK-MATCH-BINDINGS, and WK-FULL-KEYS are the +corresponding global versions of which-key extended definition keywords. They +will only have an effect for extended definitions that specify :which-key or +:wk. See the section on extended definitions in the general.el +documentation/README for more information. + +LISPY-PLIST and WORF-PLIST are the global versions of extended definition +keywords that are used for each corresponding custom DEFINER." + ;; to silence compiler warning; variables that are later extracted from kargs + (ignore definer + predicate + package + properties + repeat + jump + major-modes + lispy-plist + worf-plist) + (let ((prefix-def (or prefix-command + (when prefix-map + (list :keymap prefix-map)))) + non-normal-prefix-maps + global-prefix-maps + kargs) + ;; don't force the user to wrap a single state or keymap in a list + (general--ensure-lists states keymaps) + ;; unalias states and keymaps + (setq states (mapcar (lambda (state) (general--unalias state t)) + states)) + (setq keymaps (mapcar #'general--unalias keymaps)) + ;; remove keyword arguments from rest var + (let ((split-maps (general--remove-keyword-args maps))) + (setq maps (car split-maps) + ;; order will be preserved; matters for duplicates + kargs (append + (list + ;; should be included even if not manually specified + ;; (because have non-nil defaults) + :wk-match-keys wk-match-keys + :wk-match-binding wk-match-binding + :wk-full-keys wk-full-keys + ;; so :keymaps and :states are always lists in kargs + ;; needed for matching against :major-modes + :keymaps keymaps + ;; for consistency; may be useful in future or for user + :states states) + (cadr split-maps)))) + (general--define-prefix prefix-command prefix-map prefix-name) + (when (and (or prefix-map prefix-command) + (not (or prefix keymaps-specified-p))) + (setq keymaps (list (or prefix-map prefix-command)))) + ;; TODO reduce code duplication here + (when non-normal-prefix + (setq non-normal-prefix-maps + (general--apply-prefix-and-kbd + (general--concat t non-normal-prefix infix) + (append (when (and prefix prefix-def) + (list "" prefix-def)) + maps)))) + (when global-prefix + (setq global-prefix-maps + (general--apply-prefix-and-kbd + (general--concat t global-prefix infix) + (append (when (and prefix prefix-def) + (list "" prefix-def)) + maps)))) + ;; last so not applying prefix twice + (setq maps (general--apply-prefix-and-kbd + (general--concat t prefix infix) + (append (when (and prefix prefix-def) + (list "" prefix-def)) + maps))) + (dolist (keymap keymaps) + (general--delay `(or (memq ',keymap '(local global)) + (boundp ',keymap)) + `(general--define-key ',states + ',keymap + ',maps + ',non-normal-prefix-maps + ',global-prefix-maps + ',kargs) + 'after-load-functions t nil + (symbol-name + (cl-gensym (format "general-define-key-in-%s" keymap))))))) + +;;;###autoload +(defmacro general-emacs-define-key (keymaps &rest args) + "A wrapper for `general-define-key' that is similar to `define-key'. +It has a positional argument for KEYMAPS (that will not be overridden by a later +:keymaps argument). Besides this, it acts the same as `general-define-key', and +ARGS can contain keyword arguments in addition to keybindings. This can +basically act as a drop-in replacement for `define-key', and unlike with +`general-define-key', KEYMAPS does not need to be quoted." + (declare (indent 1)) + `(general-define-key + :keymaps ,(if (and (listp keymaps) + (eq (car keymaps) 'quote)) + `,keymaps + `',keymaps) + ,@args)) + +;;;###autoload +(defmacro general-evil-define-key (states keymaps &rest args) + "A wrapper for `general-define-key' that is similar to `evil-define-key'. +It has positional arguments for STATES and KEYMAPS (that will not be overridden +by a later :keymaps or :states argument). Besides this, it acts the same as +`general-define-key', and ARGS can contain keyword arguments in addition to +keybindings. This can basically act as a drop-in replacement for +`evil-define-key', and unlike with `general-define-key', KEYMAPS does not need +to be quoted." + (declare (indent 2)) + `(general-define-key + :states ,(if (and (listp states) + (eq (car states) 'quote)) + `,states + `',states) + :keymaps ,(if (and (listp keymaps) + (eq (car keymaps) 'quote)) + `,keymaps + `',keymaps) + ,@args)) + +(defun general--positional-arg-p (arg) + "Return whether ARG is a positional argument for a key definer. +Keyword arguments and strings/vectors are not considered positional arguments." + (and arg + (or (symbolp arg) (listp arg)) + (not (keywordp arg)))) + +;;;###autoload +(defmacro general-def (&rest args) + "General definer that takes a variable number of positional arguments in ARGS. +This macro will act as `general-define-key', `general-emacs-define-key', or +`general-evil-define-key' based on how many of the initial arguments do not +correspond to keybindings. All quoted and non-quoted lists and symbols before +the first string, vector, or keyword are considered to be positional arguments. +This means that you cannot use a function or variable for a key that starts +immediately after the positional arguments. If you need to do this, you should +use one of the definers that `general-def' dispatches to or explicitly separate +the positional arguments from the maps with a bogus keyword pair like +\":start-maps t\"" + (declare (indent defun)) + (let ((pos-args 0)) + (while (general--positional-arg-p (nth pos-args args)) + (cl-incf pos-args)) + (cl-case pos-args + (0 + `(general-define-key ,@args)) + (1 + `(general-emacs-define-key ,@args)) + (2 + `(general-evil-define-key ,@args))))) + +;;;###autoload +(cl-defmacro general-create-definer (name &rest defaults &key wrapping + &allow-other-keys) + "A helper macro to create wrappers for `general-def'. +This can be used to create key definers that will use a certain keymap, evil +state, prefix key, etc. by default. NAME is the wrapper name and DEFAULTS are +the default arguments. WRAPPING can also be optionally specified to use a +different definer than `general-def'. It should not be quoted." + (declare (indent defun)) + (let ((defaults (cl-loop for (key val) on defaults by 'cddr + unless (eq key :wrapping) + collect key + and collect val)) + (definer (or wrapping 'general-def))) + `(defmacro ,name (&rest args) + (declare (indent defun)) + ,(let ((print-quoted t)) + (format + "A wrapper for `%s'. + +It has the following defaults: +%s" + definer defaults)) + ;; can still override keywords afterwards (first keyword takes precedence) + `(,',definer + ,@args ,@',defaults)))) + +(defun general--starter-arg-p (arg) + "Return whether ARG is a keyword or positional argument for a key definer." + (or (keywordp arg) + (general--positional-arg-p arg))) + +;;;###autoload +(defmacro general-defs (&rest args) + "A wrapper that splits into multiple `general-def's. +Each consecutive grouping of positional argument followed by keyword/argument +pairs (having only one or the other is fine) marks the start of a new section. +Each section corresponds to one use of `general-def'. This means that settings +only apply to the keybindings that directly follow." + (declare (indent defun) + (debug [&rest sexp])) + (let (arglists + arglist) + (while args + (while (and args (general--starter-arg-p (car args))) + (when (keywordp (car args)) + (push (pop args) arglist)) + (push (pop args) arglist)) + (while (and args (not (general--starter-arg-p (car args)))) + (push (pop args) arglist) + (push (pop args) arglist)) + (push (nreverse arglist) arglists) + (setq arglist nil)) + `(progn + ,@(mapcar (lambda (arglist) + (cons 'general-def arglist)) + (nreverse arglists))))) + +;;;###autoload +(cl-defmacro general-unbind (&rest args) + "A wrapper for `general-def' to unbind multiple keys simultaneously. +Insert after all keys in ARGS before passing ARGS to `general-def.' \":with + #'func\" can optionally specified to use a custom function instead (e.g. + `ignore')." + (declare (indent defun)) + ;; Note: :with can be at an odd position, so must handle internally and not + ;; with &key + (let* (with + (split-args (general--remove-keyword-args args)) + (kargs (cl-loop for (key val) on (cadr split-args) by 'cddr + if (eq key :with) + do (setq with val) + else + collect key + and collect val)) + (positional-args-and-maps + ;; interleave appropriate definitions into maps + (cl-loop for key in (car split-args) + collect key + and + unless (general--positional-arg-p key) + collect (if (eq with t) + nil + with))) + (args (append positional-args-and-maps kargs))) + `(general-def ,@args))) + +;; * Displaying Keybindings +(defun general--to-string (x) + "Convert key vector or symbol X to a string." + (cond ((vectorp x) + (key-description x)) + ((symbolp x) + (symbol-name x)) + (t + x))) + +;; these sorting functions assume x != y (which will hold true for +;; `general-keybindings') +(defun general--< (x y) + "Return t if X is alphabetically less than Y. +Each should be either a string, symbol, or vector. Nil is a special case and is +considered the \"smallest\"." + (cond ((null x) + t) + ((null y) + nil) + (t + (setq x (general--to-string x) + y (general--to-string y)) + (string< x y)))) + +(defun general-sort-by-car (list) + "Sort LIST by comparing the car of each element with `general--<'." + (cl-sort list #'general--< :key #'car)) + +(defun general-sort-by-cadr (list) + "Sort LIST by comparing the cadr of each element with `general--<'." + (cl-sort list #'general--< :key #'cadr)) + +(defvar general-describe-evil-states + '(nil + insert + emacs + hybrid + normal + visual + motion + operator + replace) + "Ordered list of evil states used for `general--evil-state-<'.") + +(defun general--evil-state-< (x y) + "Return t if evil state X should come before state Y. +If X and Y are conses, the first element will be compared. Ordering is based on +`general-describe-evil-states' or the symbol names for states not in the list." + (let ((xind (cl-position x general-describe-evil-states)) + (yind (cl-position y general-describe-evil-states))) + (cond ((and (null xind) + (null yind)) + (general--< x y)) + ((null xind) + nil) + ((null yind) + t) + (t + (< xind yind))))) + +(defun general--sort-evil-state-conses (state-conses) + "Sort STATE-CONSES using `general--evil-state-<'." + (cl-sort state-conses #'general--evil-state-< :key #'car)) + +(defun general--print-map (map) + "Print the keybinding MAP." + (cl-destructuring-bind (key command previous) map + (princ (format "|=%.50s=|~%.50s~|~%.50s~|\n" + (replace-regexp-in-string "|" "ยฆ" (key-description key)) + command + previous)))) + +(defun general--print-maps-table (maps) + "Print an org table for MAPS." + (when general-describe-keybinding-sort-function + (setq maps (funcall general-describe-keybinding-sort-function maps))) + (princ "|key|command|previous|\n|-+-|\n") + (dolist (map maps) + (general--print-map map)) + (princ "\n")) + +(defun general--print-state-heading (state-cons) + "Print a table and possibly a heading for STATE-CONS." + (let ((state (car state-cons)) + (maps (cdr state-cons))) + (unless (null state) + (princ (capitalize (concat "** " (symbol-name state) " State\n")))) + (general--print-maps-table maps))) + +(defun general--print-keymap-heading (keymap-cons) + "Print headings and tables for KEYMAP-CONS." + (let ((keymap (car keymap-cons)) + (state-conses (cdr keymap-cons))) + (princ (capitalize (concat "* " (symbol-name keymap) " Keybindings\n"))) + (when general-describe-state-sort-function + (setq state-conses (funcall general-describe-state-sort-function + state-conses))) + (dolist (state-cons state-conses) + (general--print-state-heading state-cons)))) + +(declare-function org-at-heading-p "org") +(declare-function org-table-align "org-table") +(declare-function outline-next-heading "outline") +(defvar org-startup-folded) +;;;###autoload +(defun general-describe-keybindings (&optional arg) + "Show all keys that have been bound with general in an org buffer. +Any local keybindings will be shown first followed by global keybindings. +With a non-nil prefix ARG only show bindings in active maps." + (interactive "P") + (with-output-to-temp-buffer "*General Keybindings*" + (let* ((keybindings (append + (copy-alist general-keybindings) + (list (cons 'local general-local-keybindings)))) + (active-maps (current-active-maps))) + ;; print prioritized keymaps first (if any) + (dolist (keymap general-describe-priority-keymaps) + (let ((keymap-cons (assq keymap keybindings))) + (when (and keymap-cons + (or (null arg) + (and (boundp (car keymap-cons)) + (memq (symbol-value (car keymap-cons)) + active-maps)))) + (general--print-keymap-heading keymap-cons) + (setq keybindings (assq-delete-all keymap keybindings))))) + ;; sort the remaining and then print them + (when general-describe-keymap-sort-function + (setq keybindings (funcall general-describe-keymap-sort-function + keybindings))) + (dolist (keymap-cons keybindings) + (when (or (null arg) + (and (boundp (car keymap-cons)) + (memq (symbol-value (car keymap-cons)) active-maps))) + (general--print-keymap-heading keymap-cons))))) + (with-current-buffer "*General Keybindings*" + (let ((org-startup-folded 'showall)) + (org-mode)) + (read-only-mode -1) + (while (progn + (while (progn + (forward-line) + (org-at-heading-p))) + (org-table-align) + (outline-next-heading))) + (goto-char (point-min)) + (read-only-mode))) + +;; * Functions/Macros to Aid Key Definition +;; ** Helpers +(cl-defun general--call-interactively + (function &optional (remap t) record-flag keys) + "Like `call-interactively' but use the remapped FUNCTION if it exists. +If REMAP is specified as nil (it is true by default), this is the same as +`call-interactively'. FUNCTION, RECORD-FLAG, and KEYS are passed to +`call-interactively'." + (when remap + (setq function (or (key-binding (kbd (format "<remap> <%s>" function))) + function))) + (call-interactively function record-flag keys)) + +;; ** Key Simulation +;; https://emacs.stackexchange.com/questions/6037/emacs-bind-key-to-prefix/13432#13432 +;; altered to +;; - allow execution in an arbitrary state and keymap +;; - create a named function with a docstring +;; - optionally dynamically lookup the key(s) up in the correct keymap to try to +;; match a command to execute instead +;; - handle more edge cases like correctly working with macros/repeating + +;; TODO +;; - rename keys arguments to key for consistency with builtin functions + +(declare-function evil-change-state "evil-core") +(defvar evil-no-display) +(defvar evil-state) +(defvar evil-previous-state) +(defvar evil-previous-state-alist) +(defvar evil-next-state) +(defmacro general--save-state (&rest body) + "Save the current state; execute BODY; restore the state. +This is a combination of `evil-without-display' and `evil-save-state'. It is +necessary to define this directly in general so that it is available when +general is compiled (as evil is an optional dependency and may not be installed +when general is compiled)." + (declare (indent defun) + (debug t)) + `(let* ((evil-no-display t) + (evil-state evil-state) + (evil-previous-state evil-previous-state) + (evil-previous-state-alist (copy-tree evil-previous-state-alist)) + (evil-next-state evil-next-state) + (old-state evil-state) + (inhibit-quit t) + (buf (current-buffer))) + (unwind-protect + (progn ,@body) + (when (buffer-live-p buf) + (with-current-buffer buf + (evil-change-state old-state)))))) + +;;;###autoload +(cl-defmacro general-key (key &key + state + docstring + accept-default no-remap position) + "Act as KEY's definition in the current context. +This uses an extended menu item's capability of dynamically computing a +definition. It is recommended over `general-simulate-key' wherever possible. KEY +should be a string given in `kbd' notation and should correspond to a single +definition (as opposed to a sequence of commands). When STATE is specified, look +up KEY with STATE as the current evil state. When specified, DOCSTRING will be +the menu item's name/description. ACCEPT-DEFAULT, NO-REMAP, and POSITION are +passed to `key-binding'." + `'(menu-item + ,(or docstring "") + nil + :filter + (lambda (&optional _) + ,(if state + `(general--save-state + (evil-change-state ,state) + (key-binding (general--kbd ,key) ,accept-default ,no-remap + ,position)) + `(key-binding (general--kbd ,key) ,accept-default ,no-remap + ,position))))) + +(defvar general--last-simulated-command nil + "Holds the last simulated command (or nil for incomplete key sequence).") + +(defvar general--simulate-next-as-is nil + "Whether to fake keys unconditionally in the next `general--simulate-keys'. +This is used for testing (but could potentially be useful for a user). Since +`general--simulate-keys' will normally assume it is being run inside a macro +that was manually recorded, this is needed when executing a keyboard macro that +ends up running `general--simulate-keys' for the first time.") + +(defvar general--simulate-as-is nil + "Whether to fake the keys unconditionally in any `general--simulate-keys'.") + +(defun general--key-binding (keys &optional state keymap) + "Look up KEYS in the keymap corresponding to STATE and/or KEYMAP. +Continually check whether subsequences of KEYS are bound to a command or keymap +starting with the full KEYS and ending when a match is found or no subsequences +remain. Unlike `lookup-key' if KEYS is not matched, fall back to checking with +`key-binding'. If STATE is specified and KEYMAP is not, temporarily switch to +STATE to look up the keys (this means that keybindings inherited from a +different evil state can still be detected). Return a list of the match and the +leftover keys (or nil if the full KEYS was matched)." + (let* ((keymap (when keymap + (general--get-keymap state keymap))) + (len (length keys)) + (ind len) + match) + (while (and (> ind 0) (not match)) + (let* ((key (substring keys 0 ind)) + (result (cond (keymap + (or (lookup-key keymap key) + (key-binding key))) + (state + ;; this also works fine when evil-local-mode is off + (general--save-state + (evil-change-state state) + (key-binding key))) + (t + (key-binding key))))) + (if (or (commandp result) + (keymapp result)) + (setq match result) + (cl-decf ind)))) + (list match + (if (= ind len) + nil + (substring keys ind len))))) + +(cl-defun general--simulate-keys (command keys &optional state keymap + (lookup t) + (remap t)) + "Simulate COMMAND followed by KEYS in STATE and/or KEYMAP. +If COMMAND is nil, just simulate KEYS. If STATE and KEYMAP are nil, simulate the +keys in the current context. When COMMAND is non-nil, STATE and KEYMAP will have +no effect. KEYS should be a string that can be passed to `kbd' or nil. If KEYS +is nil, the COMMAND will just be called interactively. If COMMAND is nil and +LOOKUP is non-nil, KEYS will be looked up in the correct context to determine if +any subsequence corresponds to a command or keymap. If a command is matched, +that command will be called followed by the simulation of any leftover keys. To +simulate the keys as-is without any lookup, LOOKUP can be explicitly specified +as nil. When COMMAND has been remapped (i.e. [remap COMMAND] is currently +bound), the remapped version will be used instead of the original command unless +REMAP is specified as nil (it is true by default)." + (let* ((keys (when keys + (general--kbd keys))) + ;; TODO remove when get rid of `general-simulate-keys' + (state (if (eq state t) + 'emacs + state))) + (unless (or command (not lookup)) + (cl-destructuring-bind (match leftover-keys) + (general--key-binding keys state keymap) + (cond ((commandp match) + (setq command match + keys leftover-keys)) + ;; not documented because no current use case + ;; left in because may be useful later + ((and (eq lookup 'always) (keymapp match)) + (setq keymap match + state nil + ;; should be nil + keys leftover-keys))))) + ;; set context for keys + (when (and keymap (not command)) + ;; TODO is it possible to set transient map and then use e.g. + ;; `evil-execute-in-normal-state' (so that commands bound in the motion + ;; state auxiliary map could also be executed)? + (set-transient-map (general--get-keymap state keymap))) + (when keys + ;; only set prefix-arg when only keys + ;; (otherwise will also affect the next command) + (unless command + (setq prefix-arg current-prefix-arg)) + (when (or general--simulate-as-is + general--simulate-next-as-is + (not executing-kbd-macro)) + (setq general--simulate-next-as-is nil) + ;; keys are incorrectly saved as this-command-keys when recording macros + ;; these keys will be played back, so don't simulate them + (setq unread-command-events + (nconc + ;; force keys to be added to this-command-keys + ;; this happens normally already for macros but it needs to be + ;; forced for evil-repeat though, which will only include the + ;; first key otherwise (ideally no keys would ever be added in + ;; either case) + (mapcar (lambda (ev) (cons t ev)) + (listify-key-sequence keys)) + unread-command-events)))) + (when command + (let ((this-command command)) + (general--call-interactively command remap))) + (setq general--last-simulated-command command))) + +;;;###autoload +(cl-defmacro general-simulate-keys (keys &optional state keymap + (lookup t) + docstring name) + "Deprecated. Please use `general-simulate-key' instead." + (let* ((command (when (listp keys) + (car keys))) + (keys (if (listp keys) + (cadr keys) + keys)) + (state (if (eq state t) + ''emacs + state)) + (name (or name + (intern (concat + (format "general-simulate-%s" + (if command + (eval command) + "")) + (when command + "-") + (replace-regexp-in-string " " "_" keys) + (when state + (concat "-in-" + (symbol-name (eval state)) + "-state")) + (when keymap + (concat "-in-" + (symbol-name keymap)))))))) + `(progn + (eval-after-load 'evil + '(evil-set-command-property #',name :repeat 'general--simulate-repeat)) + (defun ,name + () + ,(or docstring + (concat "Simulate " + (when command + (concat "`" + (symbol-name (eval command)) + "' then ")) + "'" + keys + "' in " + (cond ((and state keymap) + (concat (symbol-name (eval state)) + " state in `" + (symbol-name keymap) + "'.")) + (keymap + (concat (symbol-name keymap) + ".")) + (state + (concat (symbol-name (eval state)) + " state.")) + (t + "the current context.")))) + (interactive) + (general--simulate-keys ,command ,keys ,state ,keymap ,lookup))))) +(make-obsolete 'general-simulate-keys 'general-simulate-key "2018-01-14") + +;;;###autoload +(cl-defmacro general-simulate-key (keys + &key + state keymap + name docstring + (lookup t) + which-key + (remap t)) + "Create and return a command that simulates KEYS in STATE and KEYMAP. +KEYS should be a string given in `kbd' notation. It can also be a list of a +single command followed by a string of the key(s) to simulate after calling that +command. STATE should only be specified by evil users and should be a quoted +evil state. KEYMAP should not be quoted. Both STATE and KEYMAP aliases are +supported (but they have to be set when the macro is expanded). When neither +STATE or KEYMAP are specified, the key(s) will be simulated in the current +context. + +If NAME is specified, it will replace the automatically generated function name. +NAME should not be quoted. If DOCSTRING is specified, it will replace the +automatically generated docstring. + +Normally the generated function will look up KEY in the correct context to try +to match a command. To prevent this lookup, LOOKUP can be specified as nil. +Generally, you will want to keep LOOKUP non-nil because this will allow checking +the evil repeat property of matched commands to determine whether or not they +should be recorded. See the docstring for `general--simulate-keys' for more +information about LOOKUP. + +When a WHICH-KEY description is specified, it will replace the command name in +the which-key popup. + +When a command name is specified and that command has been remapped (i.e. [remap +command] is currently bound), the remapped version will be used instead of the +original command unless REMAP is specified as nil (it is true by default). + +The advantages of this over a keyboard macro are as follows: +- Prefix arguments are supported +- The user can control the context in which the keys are simulated +- The user can simulate both a named command and keys +- The user can simulate an incomplete key sequence (e.g. for a keymap)" + (declare (indent defun)) + (let* ((command (when (listp keys) + (car keys))) + (keys (if (listp keys) + (cadr keys) + keys)) + (state (general--unalias (eval state) t)) + (keymap (general--unalias keymap)) + (name (or name + (intern (concat + (format "general-simulate-%s" + (if command + (eval command) + "")) + (when command + "-") + (replace-regexp-in-string " " "_" keys) + (when state + (concat "-in-" + (symbol-name state) + "-state")) + (when keymap + (concat "-in-" + (symbol-name keymap)))))))) + `(progn + (eval-after-load 'evil + '(evil-set-command-property #',name :repeat 'general--simulate-repeat)) + (when ,which-key + (general-with-eval-after-load 'which-key + (push '((nil . ,(symbol-name name)) + nil . ,which-key) + which-key-replacement-alist))) + (defun ,name + () + ,(or docstring + (concat "Simulate " + (when command + (concat "`" + (symbol-name (eval command)) + "' then ")) + "'" + keys + "' in " + (cond ((and state keymap) + (concat (symbol-name state) + " state in `" + (symbol-name keymap) + "'.")) + (keymap + (concat (symbol-name keymap) + ".")) + (state + (concat (symbol-name state) + " state.")) + (t + "the current context.")))) + (interactive) + (general--simulate-keys ,command ,keys ',state ,keymap ,lookup ,remap)) + #',name))) + +(defun general--repeat-abort-p (repeat-prop) + "Return t if repeat recording should be aborted based on REPEAT-PROP." + (or (memq repeat-prop (list nil 'abort 'ignore)) + (and (eq repeat-prop 'motion) + (not (memq evil-state '(insert replace)))))) + +(declare-function evil-repeat-record "evil-repeat") +(declare-function evil-get-command-property "evil-common") +(declare-function evil-repeat-abort "evil-repeat") +(declare-function evil-this-command-keys "evil-repeat") +(declare-function evil-clear-command-keys "evil-repeat") +(defvar evil-this-register) +(defun general--simulate-repeat (flag) + "Modified version of `evil-repeat-keystrokes'. +It behaves as normal but will check the repeat property of a simulated command +to determine whether to abort recording." + (cond ((eq flag 'pre) + (when evil-this-register + (evil-repeat-record + `(set evil-this-register ,evil-this-register)))) + ((eq flag 'post) + (let* ((command general--last-simulated-command) + (repeat-prop (evil-get-command-property command :repeat t))) + (if (and command (general--repeat-abort-p repeat-prop)) + (evil-repeat-abort) + (evil-repeat-record + (evil-this-command-keys t)) + (evil-clear-command-keys)))))) + +;; ** Key Dispatch +(defvar general--last-dispatch-command nil + "Holds the last command run from a `general-key-dispatch' function.") + +(defun general--extend-key-sequence (keys) + "Read a key and append it to KEYS. +KEYS should be a string given in `kbd' notation." + (let ((key (read-event))) + (concat keys + (when keys + " ") + (key-description (if (characterp key) + (char-to-string key) + (vector key)))))) + +;;;###autoload +(cl-defmacro general-key-dispatch + (fallback-command &rest maps + &key + timeout + inherit-keymap + name docstring + which-key + (remap t) + &allow-other-keys) + "Create and return a command that runs FALLBACK-COMMAND or a command in MAPS. +MAPS consists of <key> <command> pairs. If a key in MAPS is matched, the +corresponding command will be run. Otherwise FALLBACK-COMMAND will be run with +the unmatched keys. So, for example, if \"ab\" was pressed, and \"ab\" is not +one of the key sequences from MAPS, the FALLBACK-COMMAND will be run followed by +the simulated keypresses of \"ab\". Prefix arguments will still work regardless +of which command is run. This is useful for binding under non-prefix keys. For +example, this can be used to redefine a sequence like \"cw\" or \"cow\" in evil +but still have \"c\" work as `evil-change'. If TIMEOUT is specified, +FALLBACK-COMMAND will also be run in the case that the user does not press the +next key within the TIMEOUT (e.g. 0.5). + +NAME and DOCSTRING are optional keyword arguments. They can be used to replace +the automatically generated name and docstring for the created function. By +default, `cl-gensym' is used to prevent name clashes (e.g. allows the user to +create multiple different commands using `self-insert-command' as the +FALLBACK-COMMAND without explicitly specifying NAME to manually prevent +clashes). + +When INHERIT-KEYMAP is specified, all the keybindings from that keymap will be +inherited in MAPS. + +When a WHICH-KEY description is specified, it will replace the command name in +the which-key popup. + +When command to be executed has been remapped (i.e. [remap command] is currently +bound), the remapped version will be used instead of the original command unless +REMAP is specified as nil (it is true by default)." + (declare (indent 1)) + (let ((name (or name (cl-gensym (format "general-dispatch-%s-" + (eval fallback-command))))) + ;; remove keyword arguments from maps + (maps (car (general--remove-keyword-args maps)))) + `(progn + (eval-after-load 'evil + '(evil-set-command-property #',name :repeat 'general--dispatch-repeat)) + (when ,which-key + (general-with-eval-after-load 'which-key + (push '((nil . ,(symbol-name name)) + nil . ,which-key) + which-key-replacement-alist))) + ;; TODO list all of the bound keys in the docstring + (defun ,name () + ,(or docstring (format (concat "Run %s or something else based" + "on the next keypresses.") + (eval fallback-command))) + (interactive) + (let ((map (make-sparse-keymap)) + (maps (list ,@maps)) + (invoked-keys (this-command-keys)) + (timeout ,timeout) + (inherit-keymap ,inherit-keymap) + matched-command + fallback + char + timed-out-p) + (when inherit-keymap + (set-keymap-parent map inherit-keymap)) + (while maps + (define-key map (general--kbd (pop maps)) (pop maps))) + (while (progn + (if timeout + (with-timeout (timeout (setq timed-out-p t)) + ;; TODO rename char + (setq char (general--extend-key-sequence char))) + (setq char (general--extend-key-sequence char))) + (and (not timed-out-p) + (keymapp (lookup-key map (kbd char)))))) + (cond + ((and (not timed-out-p) + (setq matched-command (lookup-key map (kbd char)))) + ;; necessary for evil-this-operator checks because + ;; evil-define-operator sets evil-this-operator to this-command + (let ((this-command matched-command)) + (general--call-interactively matched-command ,remap))) + (t + (setq matched-command ,fallback-command) + (general--simulate-keys ,fallback-command char + nil nil t ,remap))) + (setq general--last-dispatch-command matched-command))) + #',name))) + +(defun general--dispatch-repeat (flag) + "Modified version of `evil-repeat-keystrokes'. +It behaves as normal but will check the repeat property of a simulated command +to determine whether to abort recording." + (cond ((eq flag 'pre) + (when evil-this-register + (evil-repeat-record + `(set evil-this-register ,evil-this-register)))) + ((eq flag 'post) + (let ((repeat-prop (evil-get-command-property + general--last-dispatch-command + :repeat t))) + (if (general--repeat-abort-p repeat-prop) + (evil-repeat-abort) + (evil-repeat-record (evil-this-command-keys t)) + (evil-clear-command-keys)))))) + +;; ** Predicate Dispatch +;;;###autoload +(cl-defmacro general-predicate-dispatch + (fallback-def &rest defs + &key docstring + &allow-other-keys) + (declare (indent 1)) + "Create a menu item that will run FALLBACK-DEF or a definition from DEFS. +DEFS consists of <predicate> <definition> pairs. Binding this menu-item to a key +will cause that key to act as the corresponding definition (a command, keymap, +etc) for the first matched predicate. If no predicate is matched FALLBACK-DEF +will be run. When FALLBACK-DEF is nil and no predicates are matched, the keymap +with the next highest precedence for the pressed key will be checked. DOCSTRING +can be specified as a description for the menu item." + ;; remove keyword arguments from defs and sort defs into pairs + (let ((defs (cl-loop for (key value) on defs by 'cddr + unless (keywordp key) + collect (list key value)))) + `'(menu-item + ,(or docstring "") nil + :filter (lambda (&optional _) + (cond ,@(mapcar (lambda (pred-def) + `(,(car pred-def) ,(cadr pred-def))) + defs) + (t ,fallback-def)))))) + +;; ** Key "Translation" +;;;###autoload +(cl-defun general-translate-key (states keymaps + &rest maps + &key destructive + &allow-other-keys) + "Translate keys in the keymap(s) corresponding to STATES and KEYMAPS. +STATES should be the name of an evil state, a list of states, or nil. KEYMAPS +should be a symbol corresponding to the keymap to make the translations in or a +list of keymap names. Keymap and state aliases are supported (as well as 'local +and 'global for KEYMAPS). MAPS corresponds to a list of translations (key +replacement pairs). For example, specifying \"a\" \"b\" will bind \"a\" to +\"b\"'s definition in the keymap. If DESTRUCTIVE is non-nil, the keymap will be +destructively altered without creating a backup. If DESTRUCTIVE is nil, a backup +of the keymap will be stored on the initial invocation, and future invocations +will always look up keys in the backup keymap. On the other hand, if DESTRUCTIVE +is non-nil, calling this function multiple times with \"a\" \"b\" \"b\" \"a\", +for example, would continue to swap and unswap the definitions of these keys. +This means that when DESTRUCTIVE is non-nil, all related swaps/cycles should be +done in the same invocation." + (declare (indent defun)) + (general--ensure-lists states keymaps) + (dolist (keymap-name keymaps) + (dolist (state states) + (setq keymap-name (general--unalias keymap-name) + state (general--unalias state t)) + (let* ((keymap (general--get-keymap state keymap-name)) + (backup-keymap (intern (format "general-%s%s-backup-map" + keymap-name + (if state + (format "-%s-state" state) + "")))) + (lookup-keymap (if (and (not destructive) + (boundp backup-keymap)) + (symbol-value backup-keymap) + (copy-keymap keymap))) + (maps (cl-loop for (key replacement) on maps by 'cddr + ;; :destructive can be in MAPS + unless (keywordp key) + collect (general--kbd key) + and collect (lookup-key + lookup-keymap + (general--kbd replacement))))) + (unless (or destructive + (boundp backup-keymap)) + (set backup-keymap lookup-keymap)) + (apply #'general-define-key :states state :keymaps keymap-name maps))))) + +;;;###autoload +(defmacro general-swap-key (states keymaps &rest args) + "Wrapper around `general-translate-key' for swapping keys. +STATES, KEYMAPS, and ARGS are passed to `general-translate-key'. ARGS should +consist of key swaps (e.g. \"a\" \"b\" is equivalent to \"a\" \"b\" \"b\" \"a\" +with `general-translate-key') and optionally keyword arguments for +`general-translate-key'." + (declare (indent defun)) + (setq args (cl-loop for (key replacement) on args by 'cddr + collect key and collect replacement + and unless (keywordp key) + collect replacement and collect key)) + `(general-translate-key ,states ,keymaps ,@args)) + +;; ** Automatic Key Unbinding +(defun general-unbind-non-prefix-key (define-key keymap key def) + "Use DEFINE-KEY in KEYMAP to unbind an existing non-prefix subsequence of KEY. +When a general key definer is in use and a subsequnece of KEY is already bound +in KEYMAP, unbind it using DEFINE-KEY. Always bind KEY to DEF using DEFINE-KEY." + (when general--definer-p + (let ((key (if (stringp key) + (string-to-vector key) + key))) + (while (numberp (lookup-key keymap key)) + (setq key (cl-subseq key 0 -1))) + (funcall define-key keymap key nil))) + (funcall define-key keymap key def)) + +;;;###autoload +(defun general-auto-unbind-keys (&optional undo) + "Advise `define-key' to automatically unbind keys when necessary. +This will prevent errors when a sub-sequence of a key is already bound (e.g. the +user attempts to bind \"SPC a\" when \"SPC\" is bound, resulting in a \"Key +sequnce starts with non-prefix key\" error). When UNDO is non-nil, remove +advice." + (if undo + ;; using general versions in case start recording advice for later display + (general-advice-remove 'define-key #'general-unbind-non-prefix-key) + (general-advice-add 'define-key :around #'general-unbind-non-prefix-key))) + +;; ** Interactive Lambdas +(defmacro general-lambda (&rest body) + "Wrap BODY in an interactive lamba" + `(lambda () (interactive) + ,@body)) + +(defalias 'general-l #'general-lambda) + +;; * Functions/Macros to Aid Other Configuration +;; ** Settings +(defmacro general-setq (&rest settings) + "A stripped-down `customize-set-variable' with the syntax of `setq'. +Like `setq', multiple variables can be set at once; SETTINGS should consist of +variable value pairs. Some variables have a custom setter (specified with +`defcustom' and :set) that is used to run code necessary for changes to take +effect (e.g. `auto-revert-interval'). If a package has already been loaded, and +the user uses `setq' to set one of these variables, the :set code will not +run (e.g. in the case of `auto-revert-interval', the timer will not be updated). +Like with `customize-set-variable', `general-setq' will use the custom :set +setter when necessary. If the package defining the variable has not yet been +loaded, the custom setter will not be known, but it will still be run upon +loading the package. Unlike `customize-set-variable', `general-setq' does not +attempt to load any dependencies for the variable and does not support giving +variables comments." + `(progn + ,@(cl-loop for (var val) on settings by 'cddr + collect `(funcall (or (get ',var 'custom-set) #'set-default) + ',var ,val)))) + +;; ** Hooks +;;;###autoload +(defun general-add-hook (hooks functions &optional append local) + "A drop-in replacement for `add-hook'. +Unlike `add-hook', HOOKS and FUNCTIONS can be single items or lists. APPEND and +LOCAL are passed directly to `add-hook'." + (general--ensure-lists hooks functions) + (dolist (hook hooks) + (dolist (func functions) + (add-hook hook func append local)))) + +;;;###autoload +(defun general-remove-hook (hooks functions &optional local) + "A drop-in replacement for `remove-hook'. +Unlike `remove-hook', HOOKS and FUNCTIONS can be single items or lists. LOCAL is +passed directly to `remove-hook'." + (general--ensure-lists hooks functions) + (dolist (hook hooks) + (dolist (func functions) + (remove-hook hook func local)))) + +;; ** Advice +;;;###autoload +(defun general-advice-add (symbols where functions &optional props) + "A drop-in replacement for `advice-add'. +SYMBOLS, WHERE, FUNCTIONS, and PROPS correspond to the arguments for +`advice-add'. Unlike `advice-add', SYMBOLS and FUNCTIONS can be single items or +lists." + (general--ensure-lists symbols functions) + (dolist (symbol symbols) + (dolist (func functions) + (advice-add symbol where func props)))) + +;; specify full autoload to prevent function indirection (autoload generation +;; will put a /flipped/ defalias into the autoloads file causing an infinite +;; loop) +;;;###autoload (autoload 'general-add-advice "general") +(defalias 'general-add-advice #'general-advice-add) + +;;;###autoload +(defun general-advice-remove (symbols functions) + "A drop-in replacement for `advice-remove'. +Unlike `advice-remove', SYMBOLS and FUNCTIONS can be single items or lists." + (general--ensure-lists symbols functions) + (dolist (symbol symbols) + (dolist (func functions) + (advice-remove symbol func)))) + +;;;###autoload (autoload 'general-remove-advice "general") +(defalias 'general-remove-advice #'general-advice-remove) + +;; * Optional Setup +;;;###autoload +(defun general-evil-setup (&optional short-names _) + "Set up some basic equivalents for vim mapping functions. +This creates global key definition functions for the evil states. +Specifying SHORT-NAMES as non-nil will create non-prefixed function +aliases such as `nmap' for `general-nmap'." + (general-create-definer general-imap :states 'insert) + (general-create-definer general-emap :states 'emacs) + (general-create-definer general-nmap :states 'normal) + (general-create-definer general-vmap :states 'visual) + (general-create-definer general-mmap :states 'motion) + (general-create-definer general-omap :states 'operator) + (general-create-definer general-rmap :states 'replace) + (general-create-definer general-iemap :states '(insert emacs)) + (general-create-definer general-nvmap :states '(normal visual)) + ;; these two don't have corresponding states + (general-create-definer general-itomap :keymaps 'evil-inner-text-objects-map) + (general-create-definer general-otomap :keymaps 'evil-outer-text-objects-map) + (general-create-definer general-tomap + :keymaps '(evil-outer-text-objects-map + evil-inner-text-objects-map)) + (when short-names + (defalias 'imap #'general-imap) + (defalias 'emap #'general-emap) + (defalias 'nmap #'general-nmap) + (defalias 'vmap #'general-vmap) + (defalias 'mmap #'general-mmap) + (defalias 'omap #'general-omap) + (defalias 'rmap #'general-rmap) + (defalias 'iemap #'general-iemap) + (defalias 'nvmap #'general-nvmap) + (defalias 'itomap #'general-itomap) + (defalias 'otomap #'general-otomap) + (defalias 'tomap #'general-tomap))) + +;; * Use-package Integration +;; maybe useful for something else in future +(defun general--extract-autoloadable-symbol (def) + "Extract an autoloadable symbol from DEF, a normal or extended definition. +This will also correctly extract the definition from a cons of the form (STRING +. DEFN). If the extracted definition is nil, a string, a lambda, a keymap symbol +from an extended definition, or some other definition that cannot be autoloaded, +return nil." + ;; explicit null checks not required because nil return value means no def + (when (general--extended-def-p def) + ;; extract definition + (let ((first (car def))) + (setq def (if (keywordp first) + (plist-get def :def) + first)))) + (cond ((symbolp def) + def) + ((and (consp def) + (symbolp (cdr def))) + (cdr def)))) + +(general-with-eval-after-load 'use-package-core + (declare-function use-package-concat "use-package") + (declare-function use-package-process-keywords "use-package") + (defvar use-package-keywords) + (defvar use-package-deferring-keywords) + ;; ** :general Keyword + (setq use-package-keywords + ;; should go in the same location as :bind + ;; adding to end may not cause problems, but see issue #22 + (cl-loop for item in use-package-keywords + if (eq item :bind-keymap*) + collect :bind-keymap* and collect :general + else + ;; don't add duplicates + unless (eq item :general) + collect item)) + + ;; altered args will be passed to the autoloads and handler functions + (defun use-package-normalize/:general (_name _keyword general-arglists) + "Return a plist containing the original ARGLISTS and autoloadable symbols." + (let* ((sanitized-arglist + ;; combine arglists into one without function names or + ;; positional arguments + (let (result) + (dolist (arglist general-arglists result) + (while (general--positional-arg-p (car arglist)) + (setq arglist (cdr arglist))) + (setq result (append result arglist))))) + (commands + (cl-loop for (key def) on sanitized-arglist by 'cddr + when (and (not (keywordp key)) + (not (null def)) + (ignore-errors + ;; TODO use cdr instead if possible + (setq def (eval def)) + (setq def (general--extract-autoloadable-symbol + def)))) + collect def))) + (list :arglists general-arglists :commands commands))) + + (defun use-package-autoloads/:general (_name _keyword args) + "Return an alist of commands extracted from ARGS. +Return something like '((some-command-to-autoload . command) ...)." + (mapcar (lambda (command) (cons command 'command)) + (plist-get args :commands))) + + (defun use-package-handler/:general (name _keyword args rest state) + "Use-package handler for :general." + (use-package-concat + (use-package-process-keywords name rest state) + `(,@(mapcar (lambda (arglist) + ;; Note: prefix commands are not valid functions + (if (or (functionp (car arglist)) + (macrop (car arglist))) + `(,@arglist :package ',name) + `(general-def + ,@arglist + :package ',name))) + (plist-get args :arglists))))) + + ;; ** :ghook and :gfhook Keyword + (setq use-package-keywords + ;; should go in the same location as :bind + ;; adding to end may not cause problems, but see issue #22 + (cl-loop for item in use-package-keywords + if (eq item :hook) + collect :hook and collect :ghook and collect :gfhook + else + ;; don't add duplicates + unless (memq item '(:ghook :gfhook)) + collect item)) + + (defun general-normalize-hook-arglist (arglist mode-enable mode-hook + &optional symbol-is-function-p) + "Rewrite a :g(f)hook ARGLIST to a `general-add-hook' arglist. +MODE-ENABLE is the inferred command to enable the package's mode, and MODE-HOOK +is the mode inferred hook to enable the package's mode. When ARGLIST is a symbol +instead of a list, it will be considered to be a hook name unless +SYMBOL-IS-FUNCTION-P is non-nil, in which case it will considered to be a +function." + ;; standalone symbols are quoted automatically; unquote + (when (ignore-errors (memq (car arglist) (list 'quote 'function))) + (setq arglist (cadr arglist))) + (cond ((listp arglist) + ;; necessary to extract commands because they could be stored in a + ;; variable or returned by a macro/function + ;; e.g. (list #'func1 #'func2) needs to be evaluated + (setq arglist (mapcar (lambda (arg) (eval arg)) + arglist)) + (if (= (length arglist) 1) + ;; <user specified hook(s)> #'<package>-mode + (append arglist (list mode-enable)) + (let ((hooks (car arglist)) + (functions (cadr arglist))) + (when (or (null hooks) + (not (or (symbolp hooks) + (listp hooks)))) + (setq hooks mode-hook)) + (when (or (null functions) + (not (or (symbolp functions) + (listp functions)))) + (setq functions mode-enable)) + (cons hooks (cons functions (cddr arglist)))))) + (t + (if symbol-is-function-p + ;; '<package>-mode-hook <user specified function> + (list mode-hook arglist) + ;; <user specified hook> #'<package>-mode + (list arglist mode-enable))))) + + ;; altered args will be passed to the autoloads and handler functions + (defun general-normalize-hook (name _keyword args &optional gfhookp) + "Return a plist containing arglists and autoloadable commands. +Transform ARGS into arglists suitable for `general-add-hook'." + (let* ((mode (if (string-match-p "mode\\'" (symbol-name name)) + name + (intern (format "%s-mode" name)))) + (mode-hook (intern (format "%s-hook" mode)))) + (cl-loop for arg in args + collect (general-normalize-hook-arglist + arg mode mode-hook gfhookp)))) + + (defalias 'use-package-normalize/:ghook #'general-normalize-hook) + + (defun use-package-autoloads/:ghook (_name _keyword arglists) + "Return an alist of commands extracted from ARGLISTS. +Return something like '((some-command-to-autoload . command) ...)." + (let ((commands + (cl-loop for (_ functions) in arglists + if (symbolp functions) + collect functions + else + unless (functionp functions) + append (cl-loop for function in functions + when (symbolp function) + collect function)))) + (mapcar (lambda (command) (cons command 'command)) + commands))) + + (defun use-package-handler/:ghook (name _keyword arglists rest state) + "Use-package handler for :ghook and :gfhook." + (use-package-concat + (use-package-process-keywords name rest state) + `(,@(mapcar (lambda (arglist) + arglist + ;; requote (unfortunately need to evaluate in normalizer) + `(general-add-hook ,@(mapcar (lambda (x) `',x) + arglist))) + arglists)))) + + (defun use-package-normalize/:gfhook (name keyword args) + "Use-package normalizer for :gfhook." + (general-normalize-hook name keyword args t)) + + (defalias 'use-package-handler/:gfhook #'use-package-handler/:ghook)) + +;; * Key-chord "Integration" +(defun general-chord (keys) + "Rewrite the string KEYS into a valid key-chord vector." + ;; taken straight from key-chord.el + (if (/= 2 (length keys)) + (error "Key-chord keys must have two elements")) + ;; Exotic chars in a string are >255 but define-key wants 128..255 for those + (let ((key1 (logand 255 (aref keys 0))) + (key2 (logand 255 (aref keys 1)))) + (vector 'key-chord key1 key2))) + +(provide 'general) +;;; general.el ends here |