├── .ert-runner ├── .gitignore ├── Cask ├── Makefile ├── .travis.yml ├── test ├── test-helper.el └── mykie-test.el ├── lisp ├── helm-mykie-keywords.el └── mykie.el ├── mykie-example └── README.md /.ert-runner: -------------------------------------------------------------------------------- 1 | -l lisp/mykie.el 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | /.cask 3 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source marmalade) 3 | (source melpa) 4 | 5 | (package-file "lisp/mykie.el") 6 | 7 | (development 8 | ;; Unit test libraries 9 | (depends-on "ert-runner") 10 | (depends-on "cl-lib")) 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CASK ?= cask 2 | EMACS ?= emacs 3 | 4 | all: test 5 | 6 | test: clean-elc 7 | ${MAKE} unit 8 | ${MAKE} compile 9 | ${MAKE} unit 10 | ${MAKE} clean-elc 11 | 12 | compile: 13 | ${CASK} exec ${EMACS} -Q -batch -f batch-byte-compile lisp/mykie.el 14 | 15 | unit: 16 | ${CASK} exec ert-runner 17 | 18 | install: 19 | ${CASK} install 20 | 21 | clean-elc: 22 | rm -f lisp/*.elc 23 | 24 | .PHONY: all test unit install 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: emacs-lisp 2 | before_install: 3 | - curl -fsSkL https://gist.github.com/rejeep/7736123/raw | sh 4 | - export PATH="/home/travis/.cask/bin:$PATH" 5 | - export PATH="/home/travis/.evm/bin:$PATH" 6 | - evm install $EVM_EMACS --use --skip 7 | - cask 8 | env: 9 | # - EVM_EMACS=emacs-24.1-bin 10 | # - EVM_EMACS=emacs-24.2-bin 11 | - EVM_EMACS=emacs-24.3-bin 12 | - EVM_EMACS=emacs-24.4-bin 13 | script: 14 | - emacs --version 15 | - make test -------------------------------------------------------------------------------- /test/test-helper.el: -------------------------------------------------------------------------------- 1 | (eval-when-compile (require 'cl)) 2 | (require 'ert) 3 | (require 'mykie) 4 | 5 | (defvar test-result nil) 6 | (defvar test-region-state nil) 7 | (defvar test-C-u-state nil) 8 | (defvar test-err-state nil) 9 | (defvar flycheck-current-errors nil) 10 | (defvar test-mail "foo@mail.com") 11 | (defvar test-url "https://example.com") 12 | (defvar test-C-u-keywords '()) 13 | 14 | (defun cmd-do (expect map key) 15 | (fset 'mykie:region-p (lambda () test-region-state)) 16 | (with-temp-buffer 17 | (test-init expect) 18 | (when test-region-state (set-mark (point))) 19 | (call-interactively (lookup-key map (mykie:kbd key))) 20 | (case expect 21 | (readonly (read-only-mode 0)) ; <- This is introduced from emacs 24.3 22 | ((url C-u&url) (should (equal test-url mykie:current-thing))) 23 | ((email C-u&email) (should (equal test-mail mykie:current-thing))) 24 | ((file C-u&file) (should (equal "./" mykie:current-file)))) 25 | (should (eq expect test-result)))) 26 | 27 | (defun cmds-do (expects map key) 28 | (loop initially (setq current-prefix-arg nil) 29 | for expect in expects 30 | do (setq current-prefix-arg nil) 31 | do (cmd-do expect map key))) 32 | 33 | (defun test-init (expect) 34 | (lexical-let 35 | ((kw-str (symbol-name expect))) 36 | (insert " \n \n") 37 | (goto-char (1+ (point-min))) 38 | (setq test-result nil 39 | current-prefix-arg 40 | (cond 41 | ((string-match "C-u\\*\\([0-9]\\)" kw-str) 42 | (list (lsh 4 (string-to-number (match-string 1 kw-str))))) 43 | ((string-match "M-\\([0-9]\\)" kw-str) 44 | (string-to-number (match-string 1 kw-str))) 45 | (t (case expect 46 | ((C-u C-u&err C-u&prog region&C-u 47 | C-u&bobp C-u&eobp C-u&bolp C-u&eolp 48 | C-u&email C-u&url C-u&file) '(4))))) 49 | test-region-state (case expect 50 | ((region region&err region&C-u region&prog) t)) 51 | flycheck-current-errors (case expect 52 | ((region&err C-u&err err) t))) 53 | (case expect 54 | ((region&prog C-u&prog prog) (emacs-lisp-mode)) 55 | ((url C-u&url) (insert test-url)) 56 | ((email C-u&email) (insert test-mail)) 57 | ((file C-u&file) (insert "./")) 58 | (readonly (read-only-mode t)) 59 | (comment (emacs-lisp-mode) 60 | (insert ";; Comment") 61 | (backward-char 4)) 62 | ((C-u&bobp bobp) (goto-char (point-min))) 63 | ((C-u&eobp eobp) (call-interactively 'end-of-buffer)) 64 | ((C-u&bolp bolp) (insert "\n ") (goto-char (point-at-bol))) 65 | ((C-u&eolp eolp) (goto-char (point-min)) 66 | (goto-char (point-at-eol)))))) 67 | -------------------------------------------------------------------------------- /lisp/helm-mykie-keywords.el: -------------------------------------------------------------------------------- 1 | ;;; helm-mykie-keywords.el --- Show mykie's keywords by helm.el 2 | 3 | ;; Copyright (C) 2014 by Yuta Yamada 4 | 5 | ;; Author: Yuta Yamada 6 | 7 | ;;; License: 8 | ;; This program is free software: you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | ;; 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | ;; 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | ;;; Commentary: 21 | 22 | ;;; Code: 23 | (require 'cl-lib) 24 | (require 'mykie) 25 | (require 'helm) 26 | 27 | (defvar helm-mykie-keywords-source nil) 28 | (defvar helm-mykie-keywords-action 29 | '(("Insert" . 30 | (lambda (keyword) (insert keyword))))) 31 | 32 | (defun mykie:set-helm-mykie-keyword () 33 | (setq helm-mykie-keywords-source 34 | (cl-loop with base = `((candidates-in-buffer) 35 | (action . ,helm-mykie-keywords-action)) 36 | with get-default = (lambda (cond-sym) 37 | (if (equal 'mykie:normal-conditions cond-sym) 38 | mykie:default-keywords 39 | (car 40 | (assoc-default 41 | cond-sym 42 | mykie:default-condition-keyword-alist)))) 43 | with make = (lambda (cond-sym) 44 | (cl-loop with format = (lambda (kw) 45 | (cl-typecase kw 46 | (symbol (symbol-name kw)) 47 | (string kw))) 48 | with default-kw = (funcall get-default cond-sym) 49 | with keywords 50 | for (kw . c) in (symbol-value cond-sym) 51 | collect (funcall format kw) into keywords 52 | finally return (append keywords default-kw))) 53 | for cond in mykie:group-conditions 54 | for keywords = (funcall make cond) 55 | for name = (list (cons 'name (symbol-name cond))) 56 | for func = `(lambda () 57 | (helm-init-candidates-in-buffer 58 | ,cond 59 | (with-temp-buffer 60 | (cl-loop for kw in (quote ,keywords) 61 | do (insert (format "%s\n" kw))) 62 | (buffer-string)))) 63 | collect (append base name 64 | `((init . ,func)))))) 65 | 66 | ;;;###autoload 67 | (defun helm-show-mykie-keywords () 68 | "Show mykie.el keywords." 69 | (interactive) 70 | (unless helm-mykie-keywords-source 71 | (mykie:set-helm-mykie-keyword)) 72 | (helm 73 | :sources helm-mykie-keywords-source 74 | :buffer "*helm mykie*")) 75 | 76 | (provide 'helm-mykie-keywords) 77 | 78 | ;; Local Variables: 79 | ;; coding: utf-8 80 | ;; mode: emacs-lisp 81 | ;; End: 82 | 83 | ;;; helm-mykie-keywords.el ends here 84 | -------------------------------------------------------------------------------- /mykie-example: -------------------------------------------------------------------------------- 1 | 2 | ;;; Commentary 3 | ;; mykie.el example file 4 | ;;; Code: 5 | (require 'mykie) 6 | 7 | ;; Basic usage 8 | (mykie:global-set-key "C-0" 9 | ;; You can specify lambda form. 10 | :default (lambda () (minibuffer-message "default")) 11 | ;; You can specify list form. 12 | :C-u (minibuffer-message "You pushed C-u") 13 | ;; You can specify symbol form for interactive command or function.. 14 | :region query-replace-regexp) 15 | 16 | ;; Key Definition 17 | ;; There are four patterns to specify `mykie` keybinds. 18 | 19 | ;; `mykie:global-set-key` 20 | (mykie:global-set-key "C-0" 21 | :default (message "hi")) 22 | 23 | ;; `mykie:define-key` 24 | (mykie:define-key emacs-lisp-mode "C-0" 25 | :default (message "hi hello")) 26 | 27 | ;; Below example is common keywords. 28 | ;; You can make sure available full keywords at `mykie:conditions` variable. 29 | 30 | ;; - Normal Keywords Example 31 | (mykie:global-set-key "C-0" 32 | :default (message "this is default function") 33 | :repeat (message "this is executed if pushed same point") 34 | :bolp (message "this is called if pushed at bolp") 35 | :eolp (message "this is called if pushed at eolp") 36 | :C-u&bolp (message "this is called if pushed at bolp after pushed C-u") 37 | :C-u&eolp (message "this is called if pushed at eolp after pushed C-u") 38 | :region (message "this is called if pushed it when selecting region") 39 | :region&C-u (message "this is called if pushed it after pushed C-u when selecting region")) 40 | 41 | ;; C-u*N example 42 | ;; You can utilize C-u's pushed times. 43 | ;; For example: 44 | (mykie:global-set-key "C-0" 45 | :default (message "default func") 46 | :C-u (message "C-u") 47 | :C-u*2 (message "You pushed C-u 2 times aren't you?") 48 | :C-u*3 (message "You pushed C-u 3 times aren't you?") 49 | :C-u*4 (message "You pushed C-u 4 times aren't you?") 50 | :region query-replace-regexp) 51 | 52 | ;; M-N example 53 | ;; You can utilize M-[0-9] pushing times. 54 | 55 | (mykie:global-set-key "C-0" 56 | :default (message "default func") 57 | :C-u (message "C-u") 58 | :M-1 (message "You pushed M-1 aren't you?") 59 | :M-2 (message "You pushed M-2 aren't you?") 60 | :M-3 (message "You pushed M-3 aren't you?") 61 | :M-12 (message "You might pushed M-1 and M-2 aren't you?") 62 | :region query-replace-regexp) 63 | 64 | ;; As you may know, you can do commands 65 | ;; [M-1 M-2 C-0], [M-1 2 C-0], or [C-u 1 2 C-0] to call :M-12's function. 66 | 67 | ;; :email, :url example 68 | ;; You can utilize :email and :url keyword. 69 | ;; And you can use `mykie:current-thing` variable that store thing's 70 | ;; variable. 71 | 72 | (mykie:global-set-key "C-0" 73 | :C-u&url (browse-url-firefox mykie:current-thing) 74 | :email (message mykie:current-thing) 75 | :default (message "default")) 76 | ;; example ↓ try C-0 on below url or email address 77 | ;; http://www.google.com 78 | ;; example@email 79 | 80 | ;; :clone example 81 | ;; Below an example can use comment-dwim function even terminal Emacs by C-; 82 | ;; key. (Because terminal Emacs ignore "C-" key.) 83 | (mykie:set-keys nil 84 | "C-;" 85 | :default doctor 86 | :region comment-dwim 87 | :clone ";") 88 | 89 | ;; :prog example 90 | ;; This is an example using [quickrun.el](https://github.com/syohex/emacs-quickrun). 91 | (mykie:set-keys nil 92 | "M-q" 93 | :default tetris 94 | :prog quickrun 95 | :C-u&prog quickrun-with-arg 96 | :region&prog quickrun-region) 97 | 98 | ;; Local Variables: 99 | ;; coding: utf-8 100 | ;; mode: emacs-lisp 101 | ;; End: 102 | 103 | ;;; basic ends here 104 | -------------------------------------------------------------------------------- /test/mykie-test.el: -------------------------------------------------------------------------------- 1 | 2 | ;;; Code: 3 | 4 | ;; basic keywords 5 | (ert-deftest test-core-keywords () 6 | "should bind :default, :C-u and :region correctly" 7 | (mykie:define-key global-map "C-0" 8 | :default (setq test-result 'default) 9 | :C-u (setq test-result 'C-u) 10 | :region (setq test-result 'region)) 11 | (cmds-do '(default C-u region) global-map "C-0")) 12 | 13 | 14 | (ert-deftest test-bob-eob-bol-eol () 15 | "should bind :bobp, :eobp, :bolp, :eolp, :C-u&bobp, :C-u&eobp, 16 | :C-u&bolp :C-u&eolp correctly" 17 | (mykie:define-key global-map "C-0" 18 | :bobp (setq test-result 'bobp) 19 | :eobp (setq test-result 'eobp) 20 | :bolp (setq test-result 'bolp) 21 | :eolp (setq test-result 'eolp) 22 | :C-u&bobp (setq test-result 'C-u&bobp) 23 | :C-u&eobp (setq test-result 'C-u&eobp) 24 | :C-u&bolp (setq test-result 'C-u&bolp) 25 | :C-u&eolp (setq test-result 'C-u&eolp)) 26 | (cmds-do '(bobp eobp bolp eolp 27 | C-u&bobp C-u&eobp C-u&bolp C-u&eolp) 28 | global-map "C-0")) 29 | 30 | (ert-deftest test-err () 31 | "should bind :err, :C-u&err and :region&err correctly" 32 | (mykie:define-key global-map "C-0" 33 | :err (setq test-result 'err) 34 | :C-u&err (setq test-result 'C-u&err) 35 | :region&err (setq test-result 'region&err)) 36 | (cmds-do '(err C-u&err region&err) 37 | global-map "C-0")) 38 | 39 | (ert-deftest test-commnet () 40 | "should bind :comment correctly" 41 | (mykie:define-key global-map "C-0" :comment (setq test-result 'comment)) 42 | (cmds-do '(comment) global-map "C-0")) 43 | 44 | (ert-deftest test-email () 45 | "should bind :email correctly" 46 | (mykie:define-key global-map "C-0" 47 | :email (setq test-result 'email) 48 | :C-u&email (setq test-result 'C-u&email)) 49 | (cmds-do '(email C-u&email) global-map "C-0")) 50 | 51 | (ert-deftest test-url () 52 | "should bind :url correctly" 53 | (mykie:define-key global-map "C-0" 54 | :url (setq test-result 'url) 55 | :C-u&url (setq test-result 'C-u&url)) 56 | (cmds-do '(url C-u&url) global-map "C-0")) 57 | 58 | (ert-deftest test-prog () 59 | "should bind :prog correctly" 60 | (mykie:define-key global-map "C-0" 61 | :prog (setq test-result 'prog) 62 | :C-u&prog (setq test-result 'C-u&prog) 63 | :region&prog (setq test-result 'region&prog)) 64 | (cmds-do '(prog C-u&prog region&prog) global-map "C-0")) 65 | 66 | (ert-deftest test-readonly () 67 | "should bind :readonly correctly" 68 | (mykie:define-key global-map "C-0" 69 | :readonly (setq test-result 'readonly)) 70 | (cmds-do '(readonly) global-map "C-0")) 71 | 72 | (ert-deftest test-file () 73 | "should bind :file correctly" 74 | (mykie:define-key global-map "C-0" 75 | :file (setq test-result 'file) 76 | :C-u&file (setq test-result 'C-u&file)) 77 | (cmds-do '(file) global-map "C-0")) 78 | 79 | (ert-deftest test-C-u () 80 | (mykie:define-key global-map "C-0" 81 | :C-u*2 (setq test-result 'C-u*2) 82 | :region&C-u (setq test-result 'region&C-u)) 83 | (cmds-do '(C-u*2 region&C-u) global-map "C-0")) 84 | 85 | (ert-deftest test-M-N () 86 | (mykie:define-key global-map "C-0" 87 | :M-1 (setq test-result 'M-1) 88 | :M-2 (setq test-result 'M-2) 89 | :M-3 (setq test-result 'M-3) 90 | :M-4 (setq test-result 'M-4)) 91 | (cmds-do '(M-1 M-2 M-3 M-4) 92 | global-map "C-0")) 93 | 94 | ;; mykie:define-key 95 | (ert-deftest mykie-define-key () 96 | (mykie:define-key global-map "C-0" 97 | :default (setq test-result 'default) 98 | :C-u (setq test-result 'C-u)) 99 | (cmds-do '(default C-u) global-map "C-0")) 100 | 101 | (ert-deftest mykie-define-key-paren () 102 | (mykie:define-key global-map "C-0" 103 | (:default (message "") 104 | (setq test-result 'default)) 105 | (:C-u (message "") 106 | (setq test-result 'C-u))) 107 | (cmds-do '(default C-u) global-map "C-0")) 108 | 109 | ;; mykie:global-set-key 110 | (ert-deftest mykie-global-set-key () 111 | (mykie:global-set-key "C-0" 112 | :default (setq test-result 'default) 113 | :C-u (setq test-result 'C-u)) 114 | (cmds-do '(default C-u) global-map "C-0")) 115 | 116 | (ert-deftest mykie-global-set-key-paren () 117 | (mykie:global-set-key "C-0" 118 | (:default (message "") 119 | (setq test-result 'default)) 120 | (:C-u (message "") 121 | (setq test-result 'C-u))) 122 | (cmds-do '(default C-u) global-map "C-0")) 123 | 124 | ;; mykie:define-key-with-self-key 125 | (ert-deftest mykie-define-key-with-self-key () 126 | (mykie:define-key-with-self-key "a" 127 | :C-u (setq test-result 'C-u)) 128 | (cmds-do '(C-u) global-map "a")) 129 | 130 | (ert-deftest mykie-define-key-with-self-key-paren () 131 | (mykie:define-key-with-self-key "a" 132 | (:C-u (message "") 133 | (setq test-result 'C-u))) 134 | (cmds-do '(C-u) global-map "a")) 135 | 136 | ;; mykie:set-keys 137 | (ert-deftest mykie-set-keys () 138 | (mykie:set-keys global-map 139 | ;; PAREN 140 | "C-0" 141 | (:default (message "") 142 | (setq test-result 'default)) 143 | (:C-u (message "") 144 | (setq test-result 'C-u)) 145 | "C-9" 146 | (:default (message "") 147 | (setq test-result 'default)) 148 | (:C-u (message "") 149 | (setq test-result 'C-u)) 150 | ;; NORMAL 151 | "C-1" 152 | :default (setq test-result 'default) 153 | :C-u (setq test-result 'C-u)) 154 | (cmds-do '(default C-u) global-map "C-0") 155 | (cmds-do '(default C-u) global-map "C-9") 156 | (cmds-do '(default C-u) global-map "C-1")) 157 | 158 | (ert-deftest mykie-set-keys-short-hand () 159 | "`mykie:set-keys' should call function even user doesn't specify 160 | :default keyword." 161 | (mykie:set-keys global-map 162 | "C-e" (setq test-result 'default) 163 | "H-m" (setq test-result 'default) 164 | "S-a" (setq test-result 'default) 165 | "A-c" (setq test-result 'default) 166 | "M-s" (setq test-result 'default)) 167 | (cmds-do '(default) global-map "C-e") 168 | (cmds-do '(default) global-map "H-m") 169 | (cmds-do '(default) global-map "S-a") 170 | (cmds-do '(default) global-map "A-c") 171 | (cmds-do '(default) global-map "M-s")) 172 | 173 | ;; mykie:combined-command 174 | (ert-deftest mykie-combined-command () 175 | (global-set-key (kbd "C-4") 176 | (mykie:combined-command 177 | :default (setq test-result 'default) 178 | :C-u (setq test-result 'C-u))) 179 | (cmds-do '(default C-u) global-map "C-4")) 180 | 181 | (ert-deftest mykie-combined-command-paren () 182 | (global-set-key (kbd "C-5") 183 | (mykie:combined-command 184 | (:default (message "") 185 | (setq test-result 'default)) 186 | (:C-u (message "") 187 | (setq test-result 'C-u)))) 188 | (cmds-do '(default C-u) global-map "C-5")) 189 | 190 | ;; mykie 191 | (ert-deftest mykie () 192 | (global-set-key (kbd "C-3") 193 | (lambda () 194 | (interactive) 195 | (mykie :default (setq test-result 'default) 196 | :C-u (setq test-result 'C-u)))) 197 | (cmds-do '(default C-u) global-map "C-3")) 198 | 199 | (ert-deftest mykie-paren () 200 | (global-set-key (kbd "C-3") 201 | (lambda () 202 | (interactive) 203 | (mykie 204 | (:default (message "") 205 | (setq test-result 'default)) 206 | (:C-u (message "") 207 | (setq test-result 'C-u))))) 208 | (cmds-do '(default C-u) global-map "C-3")) 209 | 210 | ;; Local Variables: 211 | ;; coding: utf-8 212 | ;; mode: emacs-lisp 213 | ;; no-byte-compile: t 214 | ;; End: 215 | 216 | ;;; mykie-test.el ends here 217 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/yuutayamada/mykie-el.png?branch=master)](https://travis-ci.org/yuutayamada/mykie-el) 2 | 3 | # Mykie.el | Command multiplexer 4 | 5 | Do you have enough keybinds in Emacs? 6 | No? Then this program strongly helps you to add other functions to 7 | **a single** keybind. 8 | 9 | ## Requirement 10 | 11 | This package needs Emacs 24.3 or higher. 12 | 13 | ## Installation 14 | 15 | You can install from MELPA by M-x package-install RET mykie. 16 | 17 | ## Configuration 18 | 19 | Below configuration is common setting for mykie.el. 20 | You can see more example in mykie-example file. 21 | Note: below `mykie:use-major-mode-key-override` is a bit complex, 22 | See below section 'Major-mode's keys overriding' for details. 23 | 24 | ```lisp 25 | (require 'mykie) 26 | (setq mykie:use-major-mode-key-override t) 27 | (mykie:initialize) 28 | ;; You can set 'global or global-map instead of nil to specify global-map. 29 | ;; If you want to specify specific keymap, specify the keymap name 30 | ;; like emacs-lisp-mode-map instead of nil. 31 | (mykie:set-keys nil 32 | "C-a" 33 | :default (beginning-of-line) 34 | :C-u mark-whole-buffer 35 | "C-e" 36 | :default (end-of-line) 37 | :C-u (message "Hello") 38 | ;; ... You can add more keybinds 39 | ) 40 | ``` 41 | 42 | Above example is registered two keybinds in global-map. 43 | The C-a binding default behavior is `beginning-of-line`. 44 | But do `mark-whole-buffer` if you pushed C-u key before pushing C-a. 45 | As you can see, you can add more keybinds like above C-e. 46 | 47 | You can specify like this too. 48 | 49 | ```lisp 50 | (mykie:global-set-key "C-j" 51 | :default (progn 52 | (delete-trailing-whitespace) 53 | (case major-mode 54 | (org-mode (org-return-indent)) 55 | (t (newline-and-indent)))) 56 | :C-u&eolp (fill-region (point-at-bol) (point-at-eol)) 57 | :region query-replace-regexp) 58 | ``` 59 | 60 | ## Available Keywords 61 | 62 | You can specify below keywords to mykie's arguments such as :default 63 | or :C-u etc..(You may saw those keywords on above examples) 64 | 65 | Note: below keyword can specify function only. See Available Forms too. 66 | 67 | | KEYWORD | DESCRIPTION | 68 | |:-------------------|:----------- | 69 | | :default or t | default function, call this if conditions aren't matched all conditions 70 | | :C-u | Call this if you pushed C-u key before pushing the key 71 | | :C-u! | like :C-u, but reset `current-prefix-arg` 72 | | :C-u*N | Call this if you pushed N times of C-u(replace N to a number) 73 | | :C-u*N! | like :C-u*N, but reset `current-prefix-arg` 74 | | :M-N | Call this if you pushed such as M-1(replace N to a number) 75 | | :region | Call this if you are selecting region 76 | | :region&C-u | Call this if you satisfied :region & :C-u condition 77 | | :repeat | Call this if you repeat same command at same point 78 | | :bolp | Call this if current point is beginning of line 79 | | :eolp | Call this if current point is end of line 80 | | :bobp | Call this if current point is beginning of buffer 81 | | :eobp | Call this if current point is end of buffer 82 | | :C-u&bolp | Call this if you satisfied :C-u & :bolp 83 | | :C-u&eolp | Call this if you satisfied :C-u & :eolp 84 | | :C-u&bobp | Call this if you satisfied :C-u & :bobp 85 | | :C-u&eobp | Call this if you satisfied :C-u & :eobp 86 | | :email | Call this if current point matched (thing-at-point 'email) 87 | | :C-u&email | Call this if you satisfied :C-u & :email 88 | | :url | Call this if current point matched (thing-at-point 'url) 89 | | :C-u&url | Call this if you satisfied :C-u & :url 90 | | :file | Call this if current point matched `ffap-file-at-point` 91 | | :C-u&file | Call this if you satisfied :C-u & :file 92 | | :MAJOR-MODE | Call this if :MAJOR-MODE matched major-mode. for example you can specify like this: :emacs-lisp-mode (message "hello") 93 | | :C-u&MAJOR-MODE | Call this if you satisfied :C-u & :MAJOR-MODE 94 | | :region&MAJOR-MODE | Call this if you satisfied :region & :MAJOR-MODE 95 | | :prog | Call this if current buffer is related programming see also `prog-mode' from Emacs. Note this function can use from Emacs 24.1. 96 | | :C-u&prog | Call this if you satisfied :C-u & :prog 97 | | :region&prog | Call this if you satisfied :region & :prog 98 | | :err | Call this if flymake-err-info or flycheck-current-errors is non-nil 99 | | :C-u&err | Call this if you satisfied :C-u & :err 100 | | :region&err | Call this if you satisfied :region & :err 101 | | :minibuff | Call this if current point is in minibuffer 102 | | :readonly | Call this if current buffer is read-only 103 | | :comment | Call this if current point is comment 104 | 105 | There are other convenience keywords 106 | Below keywords can't specify function. Instead specify other thing. 107 | See below description. 108 | 109 | | KEYWORD | VALUE | DESCRIPTION | 110 | |:--------------------|:---------------|:-------------------------------| 111 | | :clone | KEY as string | Clone mykie's functions to other KEY. this function is convenient if you use Emacs either situation terminal and GUI. Because terminal Emacs can't use partial keybind such as C-;, this keyword can clone same functions to another key without :default function. For example: :clone ";" (<- if you want to clone origin key to ";") | 112 | | :deactivate-region | symbol | deactivate selecting region after mykie executed command. You can specify this t, 'region, 'region&C-u. | 113 | | :region-handle-flag | symbol | Do copying or killing before command executing. This function is convenience if you want to use kill-ring's variable. But there is mykie:region-str variable that always store region's strings. | 114 | 115 | ## Basic Usage 116 | There are four patterns to specify `mykie` keybinds. 117 | 118 | `mykie:global-set-key` 119 | 120 | ```lisp 121 | (mykie:global-set-key "C-0" 122 | :default (message "hi")) 123 | ``` 124 | 125 | `mykie:define-key` 126 | 127 | ```lisp 128 | (mykie:define-key emacs-lisp-mode "C-0" 129 | :default (message "hi hello")) 130 | ``` 131 | 132 | `mykie:define-key-with-self-key` 133 | 134 | This function binds mykie keybinds to self-insert-key(like [a-zA-Z]). 135 | 136 | ```lisp 137 | (mykie:define-key-with-self-key 138 | "a" :C-u (message "I am C-u")) 139 | ``` 140 | 141 | `mykie:set-keys` 142 | 143 | This function is convenience if you want to set multiple keybinds. 144 | 145 | For global-map 146 | 147 | ```lisp 148 | (mykie:set-keys nil 149 | "C-a" 150 | :default (beginning-of-line) 151 | :C-u mark-whole-buffer 152 | "C-e" 153 | :default (end-of-line) 154 | :C-u (message "Hello")) 155 | ``` 156 | For specific keymap 157 | 158 | ```lisp 159 | ;; Set keybinds to specific keymap: 160 | (mykie:set-keys emacs-lisp-mode-map 161 | "C-1" 162 | :default (message "C-1") 163 | :C-u (message "C-1+C-u") 164 | "C-2" 165 | :default (message "C-2") 166 | :C-u (message "C-2+C-u")) 167 | ``` 168 | 169 | For self-insert-command like "a", "b", "c" etc.. 170 | 171 | ```lisp 172 | ;; Set keybinds for self-insert-key 173 | ;; You don't need to specify :default state, it's specified to 174 | ;; self-insert-command automatically to it. 175 | (mykie:set-keys with-self-key 176 | "a" 177 | :C-u (message "called a") 178 | :region query-replace-regexp 179 | "b" 180 | :C-u (message "called b")) 181 | ``` 182 | 183 | ## Too many keyword... 184 | 185 | If you can use [Helm](https://github.com/emacs-helm/helm), then 186 | `helm-mykie-keywords` shows you available keywords. 187 | 188 | ## Fuzzy Ordering 189 | 190 | This function name was lazy ordering until v0.1.1. 191 | From v0.2.0, the name was renamed to fuzzy ordering and this function is 192 | turn on by default. 193 | 194 | You can change the order of commands by the order of each key-bindings 195 | when you register key-bindings. 196 | Below C-j binding precedes :C-u&url than :C-u&eolp. 197 | 198 | ```lisp 199 | (mykie:set-keys nil 200 | "C-0" 201 | :default (message "hi") 202 | :C-u&url (browse-url-at-point) ; prioritize :C-u&url than :C-u&eolp 203 | :C-u&eolp (fill-region (point-at-bol) (point-at-eol)) 204 | :C-u (message "hello")) 205 | ``` 206 | 207 | ## Shorthand for key binding and unbinding 208 | mykie's :default keywords is redundant to specify just one function. 209 | If you don't specify :default keyword and the function is only one, 210 | you can use shorthand of `mykie:set-keys` function to register and 211 | un-register multiple keybinds. 212 | 213 | To register keybinds, you can write like this: 214 | ```lisp 215 | (mykie:set-keys nil ; <- global-map 216 | "C-0" (message "foo") ; Eval as S-expression 217 | "C-1" emacs-version ; Eval as command 218 | "C-2" 219 | :default (message "You can combine :default keyword as well.") 220 | :C-u (message "You can combine other keyword as well.") 221 | ) 222 | ``` 223 | 224 | To Un-register keybinds, you can write like this: 225 | ```lisp 226 | (mykie:set-keys nil ; <- global-map 227 | "C-0" "C-1" ; <- those keys are unregistered 228 | ;; You can register keybinds in this function 229 | "C-3" :default (message "Hello") 230 | ) 231 | ``` 232 | 233 | ## Nested Mykie keybinds 234 | 235 | You can implement your function with `mykie' function. 236 | 237 | ```lisp 238 | (global-set-key (kbd "C-o") 239 | '(lambda () 240 | (interactive) 241 | (mykie :default (message "default") 242 | :C-u (mykie :emacs-lisp-mode 243 | (message "You can call filter condition"))))) 244 | ``` 245 | 246 | ## Prefix key with mykielized keybinds 247 | 248 | You can make prefix keymap with mykielized keybinds. To be simple, I 249 | omitted :default keywords in below example, but you can add other 250 | keyword that I showed above. Note that this function can take some 251 | parameters and you can handle the exit time by :keep and :exit keyword 252 | with your anonymous function. Also you can specify kind of wrapping 253 | function by using :before and :after with your specified function. 254 | These guys are convenience if you want to change color of modeline, 255 | cursor, or etc. when you do in and out of keymaps. 256 | 257 | ```lisp 258 | (mykie:define-prefix-key global-map "M-j" 259 | (:keep 260 | (lambda () 261 | ;; you can keep this prefix keymap as long as 262 | ;; this function return non-nil. 263 | ;; ... Implement something ... 264 | ) 265 | :exit ; exit this prefix keymap when this function return non-nil 266 | (lambda () 267 | (or (member last-command 268 | '(mc/keyboard-quit 269 | self-insert-command 270 | mc/insert-numbers)) 271 | (eq last-command-event (string-to-char "q")))) 272 | :before 273 | (lambda () ; this function called before you enter this prefix keymap 274 | (set-face-background 'modeline "red")) 275 | :after 276 | (lambda () ; this function called when you exit this prefix keymap 277 | (set-face-background 'modeline "blue"))) 278 | ;; multiple-cursors ;; 279 | ;; You can use other keyword like :region or something, but 280 | ;; please don't forget :default keyword as well. 281 | "a" mc/mark-all-like-this 282 | "q" :default nil 283 | "n" mc/skip-to-next-like-this 284 | "p" mc/skip-to-previous-like-this 285 | "j" mc/mark-next-like-this 286 | "k" mc/mark-previous-like-this 287 | "m" mc/mark-more-like-this-extended 288 | "u" mc/unmark-next-like-this 289 | "U" mc/unmark-previous-like-this 290 | "*" mc/mark-all-like-this 291 | "M-j" mc/mark-all-like-this-dwim 292 | "i" mc/insert-numbers 293 | "o" mc/sort-regions 294 | "O" mc/reverse-regions) 295 | ``` 296 | 297 | ## Overriding of Major-Mode 298 | 299 | I think almost Emacs lisp package do not use the major-mode keybind 300 | with C-u prefix. (of course there are exception like Magit) 301 | This section explains how to attach mykie's function of global-map to 302 | specific keymap. 303 | 304 | Here is an example: 305 | ```lisp 306 | (setq mykie:use-major-mode-key-override 'both) 307 | (mykie:initialize) 308 | (mykie:set-keys nil ; <- nil means registering global-map 309 | "C-w" :default tetris :C-u (message "C-u+C-w")) 310 | (mykie:set-keys with-self-key ; <- this means registering 311 | ;; ↓ You don't need to specify :default and self-insert-command 312 | ;; :default self-insert-command 313 | "1" :region sort-lines 314 | "2" :region align 315 | "3" :region query-replace 316 | "c" :C-u (message "C-u+c")) 317 | ``` 318 | 319 | You can specify 'both or 'global 'self or t to 320 | mykie:use-major-mode-key-override. 321 | 'both means use overriding major-mode keys both case. 322 | 'global means use overriding major-mode keys by global-map's keys 323 | without self-insert-command keys. 324 | 'self means use overriding major-mode keys by self-insert-command keys. 325 | if you set nil, then don't overriding major-mode key. 326 | 327 | Although, I mentioned before, there is a problem, several program are 328 | using :C-u key to change the behavior of keys. 329 | To avoid major-mode key overriding, you can specify specific modes 330 | like this: 331 | 332 | ```lisp 333 | (setq mykie:major-mode-ignore-list '(emacs-lisp-mode) 334 | mykie:minor-mode-ignore-list '(diff-minor-mode)) 335 | ``` 336 | 337 | Also you can use below configuration to avoid overriding major-mode key. 338 | This way ignores overriding major-mode keys. 339 | 340 | ```lisp 341 | (setq mykie:use-major-mode-key-override 'both) 342 | (mykie:initialize) 343 | (mykie:set-keys nil ; <- nil means registering global-map 344 | "C-w" 345 | :default (message ":default will change to major-mode's function of same key") 346 | :C-u tetris 347 | :ignore-major-modes (magit-status-mode) 348 | :ignore-major-modes (diff-minor-mode)) 349 | ``` 350 | 351 | You can specify specific mode. 352 | 353 | ```lisp 354 | (setq mykie:use-major-mode-key-override nil) 355 | (mykie:initialize) 356 | (mykie:attach-mykie-func-to 'emacs-lisp-mode) 357 | ``` 358 | 359 | ## Continuous Command 360 | 361 | You can set keybinds and functions' pair. 362 | Key-bindings can specify [a-zA-Z] only. 363 | (Maybe this function fail if you set existing keybind) 364 | 365 | ```lisp 366 | (defun mykie:vi-faker () 367 | (interactive) 368 | (let 369 | ((scroll (lambda (up-or-down) 370 | (condition-case err 371 | (case up-or-down 372 | (up (scroll-up-command)) 373 | (down (scroll-down-command))) 374 | (error (minibuffer-message err)))))) 375 | (mykie:loop 376 | ;; vi style 377 | "h" backward-char 378 | "j" next-line 379 | "k" previous-line 380 | "l" forward-char 381 | ;; less 382 | "f" (funcall scroll 'up) 383 | "b" (funcall scroll 'down)))) 384 | ``` 385 | 386 | There is a similar command that do first command and then wait user input. 387 | 388 | ```lisp 389 | (mykie:global-set-key 390 | "C-j" 391 | :default (mykie:do-while 392 | "j" newline-and-indent 393 | "u" undo)) 394 | ``` 395 | 396 | Above command do newline-and-indent and then wait user input. 397 | 398 | ## Customizing own condition 399 | 400 | You can change or attach `mykie`s conditions. 401 | 402 | Here is an example to attach your customized conditions. 403 | 404 | ```lisp 405 | (setq mykie:normal-conditions 406 | (append mykie:normal-conditions 407 | '(;; You can set keyword and predicate pair 408 | (:lisp . (case major-mode 409 | ((lisp-mode emacs-lisp-mode) t))) 410 | ;; or regexp and predicate that return keyword pair. 411 | ;; below example is you can use :24.3.1 or :24.5.1 as mykie's args 412 | ("^:24\.\\(3\\|5\\)\..$" . (pcase emacs-version 413 | (`"24.3.1" :24.3.1) 414 | (`"24.5.1" :24.5.1)))))) 415 | ``` 416 | 417 | ## Parenthesized style 418 | 419 | You can use parenthesized style. 420 | But, I don't implement :default short hand feature in this style. 421 | So, please keep in your mind, or use normal style. 422 | 423 | ```lisp 424 | (mykie:global-set-key "C-j" 425 | (:default (delete-trailing-whitespace) 426 | (case major-mode 427 | (org-mode (org-return-indent) 428 | (t (newline-and-indent))))) 429 | (:C-u&url browse-url-at-point) 430 | (:C-u&eolp (fill-region (point-at-bol) (point-at-eol)))) 431 | ``` 432 | 433 | You can use mykie:define-key, mykie:global-set-key, 434 | mykie:define-key-with-self-key, and mykie:set-keys to register 435 | keybind with parenthesized style. 436 | 437 | ## Contributor(s) 438 | Here's a [list](https://github.com/yuutayamada/mykie-el/contributors) 439 | of all the people who have contributed to the development of mykie. 440 | 441 | ## Note 442 | 443 | From v0.2.0, quote is not needed anymore when you register keybinds. 444 | So if you ware already using old mykie.el, please delete needless 445 | quotes before you use new mykie.el. 446 | Also condition's form was changed from v0.2.0. 447 | See Customizing section if you want to add specific condition. 448 | And fuzzy order(lazy order) was turn on by default. 449 | See fuzzy order section too. 450 | 451 | ## License 452 | `mykie.el` is released under GPL v3. 453 | -------------------------------------------------------------------------------- /lisp/mykie.el: -------------------------------------------------------------------------------- 1 | ;;; mykie.el --- Command multiplexer: Register multiple functions to a keybind -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2013 by Yuta Yamada 4 | 5 | ;; Author: Yuta Yamada 6 | ;; URL: https://github.com/yuutayamada/mykie-el 7 | ;; Version: 0.3.1 8 | ;; Package-Requires: ((emacs "24.3") (cl-lib "0.5")) 9 | ;; Keywords: Emacs, configuration, keybind 10 | 11 | ;;; License: 12 | ;; This program is free software: you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | ;; 17 | ;; This program is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | ;; 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with this program. If not, see . 24 | ;;; Commentary: 25 | ;; This program can register multiple functions to a keybind easily. 26 | ;; For example: 27 | ;; (mykie:set-keys global-map ; <- you can specify nil to bind to global-map 28 | ;; "C-j" ; You don't need kbd macro 29 | ;; :default newline-and-indent ; <- normal behavior 30 | ;; :region query-replace-regexp ; <- do query-replace-regexp in region 31 | ;; "C-a" 32 | ;; :default beginning-of-line 33 | ;; :C-u (message "hello") ; <- C-u + C-a, then you can see hello 34 | ;; ;; You can add more keybinds 35 | ;; ;; ... 36 | ;; ) 37 | ;; 38 | ;; Above function call newline-and-indent by default, 39 | ;; But call query-replace-regexp function if you select region. 40 | ;; 41 | ;; There are other way to bind a keybind: 42 | ;; (mykie:global-set-key "C-j" ; You don't need kbd macro 43 | ;; :default newline-and-indent 44 | ;; :region query-replace-regexp) 45 | ;; 46 | ;; You can see more example : https://github.com/yuutayamada/mykie-el 47 | ;;; Code: 48 | (require 'cl-lib) 49 | (autoload 'ido-active "ido") 50 | (autoload 'ffap-file-at-point "ffap") 51 | (when (require 'helm nil t) 52 | (autoload 'helm-show-mykie-keywords "helm-mykie-keywords" nil t)) 53 | 54 | ;; CUSTOMIZE VARIABLE 55 | (defvar mykie:region-conditions 56 | '((:region&repeat . (mykie:repeat-p)) 57 | (:region&C-u . current-prefix-arg) 58 | (:region&prog . mykie:prog-mode-flag) 59 | (:region&err . (mykie:error-occur-p)) 60 | ("^:region&.+-mode$" . (mykie:get-major-mode-state "region&"))) 61 | "This variable is used at `mykie' function. 62 | You don't need to contain region checking function. Mykie will check 63 | whether region is active or not before check this variable.") 64 | 65 | (defvar mykie:prefix-arg-conditions* 66 | '(("^:\\(M-\\|C-u\\*\\)[0-9]+!?$" . (mykie:get-prefix-arg-state))) 67 | "This variable is used at `mykie' function. 68 | Mykie will check whether current-prefix-arg is non-nil 69 | and make sure user pushed multiple time C-u more than one time.") 70 | 71 | (defvar mykie:prefix-arg-conditions 72 | '((:C-u&repeat . (mykie:repeat-p)) 73 | (:C-u&err . (mykie:error-occur-p)) 74 | ("^:C-u&.+-mode$" . (mykie:get-major-mode-state "C-u&")) 75 | (:C-u&prog . mykie:prog-mode-flag) 76 | (:C-u&email . (mykie:thing-exist-p 'email)) 77 | (:C-u&url . (mykie:thing-exist-p 'url)) 78 | (:C-u&file . (mykie:file-at-point-p)) 79 | (:C-u&bobp . (bobp)) 80 | (:C-u&eobp . (eobp)) 81 | (:C-u&bolp . (bolp)) 82 | (:C-u&eolp . (eolp))) 83 | "This variable is used at `mykie' function. 84 | You don't need to contain prefix-arg checking function. Mykie will check 85 | whether current-prefix-arg is non-nil or not before check this variable.") 86 | 87 | (defvar mykie:normal-conditions 88 | '((:repeat . (mykie:repeat-p)) 89 | (:err . (mykie:error-occur-p)) 90 | ("^:.+-mode$" . (mykie:get-major-mode-state)) 91 | (:prog . mykie:prog-mode-flag) 92 | (:comment . (mykie:get-comment-state)) 93 | (:ido . (ido-active)) 94 | (:email . (mykie:thing-exist-p 'email)) 95 | (:url . (mykie:thing-exist-p 'url)) 96 | (:file . (mykie:file-at-point-p)) 97 | (:minibuff . (minibufferp)) 98 | (:bobp . (bobp)) 99 | (:eobp . (eobp)) 100 | (:bolp . (bolp)) 101 | (:eolp . (eolp)) 102 | (:readonly . buffer-read-only)) 103 | "This variable is used at `mykie' function.") 104 | 105 | (defvar mykie:group-conditions '(mykie:region-conditions 106 | mykie:prefix-arg-conditions* 107 | mykie:prefix-arg-conditions 108 | mykie:normal-conditions) 109 | "Mykie will check each condition by this list's order. 110 | You can add another conditions and pre-check by `mykie:precheck-function'.") 111 | 112 | (defvar mykie:use-major-mode-key-override nil 113 | "If this variable is non-nil, attach mykie's same global key function 114 | to major-mode's key function if the function is exists. 115 | You can specify 'both, 'global, 'self or t to this variable. 116 | 'both means do override self-insert-keys and other global-keys. 117 | 'global means do override other global-keys only(such as C-j, not [a-z]). 118 | 'self means do override self-insert-keys only. 119 | t means same as 'self. See also `mykie:attach-mykie-func-to'") 120 | 121 | (defvar mykie:use-fuzzy-order t 122 | "If this variable is non-nil, you can change execution order when 123 | you register key-bindings. This function is convenience if you want to 124 | change keybind order by each keybind. 125 | 126 | For example: 127 | (mykie:global-set-key \"C-j\" 128 | :default new-line-and-indent 129 | :C-u&url browse-url-at-point 130 | :C-u&eolp (fill-region (point-at-bol) (point-at-eol)) 131 | 132 | If you set above setting, then mykie is prior :C-u&url than :C-u&eolp. 133 | If you want change the order, swap order between :C-u&url and 134 | :C-u&eolp. Note: you can change order only same group conditions. 135 | i.e., you can't change order region's function and C-u's function.") 136 | 137 | (defvar mykie:major-mode-ignore-list '() 138 | "major-mode's list that ignore mykie's function if this list 139 | contains current major-mode") 140 | 141 | (defvar mykie:minor-mode-ignore-list '() 142 | "minor-mode's list that ignore mykie's function if this list 143 | contains current minor-mode") 144 | 145 | (defvar mykie:region-before-init-hook '(mykie:region-init)) 146 | (defvar mykie:region-after-init-hook '(mykie:deactivate-mark)) 147 | 148 | (defvar mykie:region-func-predicate 149 | '(lambda () 150 | (and (mykie:region-p) 151 | (cl-case mykie:current-state ((:region :region&C-u) t))))) 152 | 153 | (defvar mykie:ignore-keybinds '("C-c") 154 | "Set this variable to avoid overriding specific key. 155 | Normally use this variable to avoid overriding C-c key to use 156 | `mode-specific-command-prefix'.") 157 | 158 | (defvar mykie:default-keywords '(:default t) 159 | "Some function using :default keyword. So do not delete :default. 160 | To change this variable use `add-to-list'.") 161 | 162 | (defvar mykie:default-condition-keyword-alist 163 | '((mykie:region-conditions (:region)) 164 | (mykie:prefix-arg-conditions (:C-u :C-u!))) 165 | "Alist pair `mykie:group-conditions' element and default keyword(s) list.") 166 | 167 | (defvar mykie:get-default-function 168 | (lambda (args) 169 | (cl-loop for i from 0 to (1- (length args)) by 2 170 | if (member (nth i args) mykie:default-keywords) 171 | do (cl-return (plist-get args (car it)))))) 172 | 173 | (defvar mykie:funcname-style 'dwim 174 | "Style of `mykie:make-funcname-function'. 175 | You can specify 'old to make old style funcname.") 176 | 177 | (defvar mykie:make-funcname-function 178 | (lambda (args keymap key &optional keymap-name) 179 | (let* ((keyname (replace-regexp-in-string " " "_" (key-description key))) 180 | (default-func (funcall mykie:get-default-function args)) 181 | (funcname (if (listp default-func) 182 | "anonymous-func" 183 | default-func)) 184 | (self-or-normal 185 | (if (eq default-func 'self-insert-command) 186 | "self-insert-command" 187 | "key"))) 188 | (intern (format "mykie:%s:%s:%s" 189 | (or keymap-name "") keyname 190 | (cl-case mykie:funcname-style 191 | ;; multiple-cursor refers to the key's function name 192 | ;; So I think probably same function name is best. 193 | (dwim (if (eq global-map keymap) 194 | self-or-normal 195 | funcname)) 196 | (old self-or-normal) 197 | (t funcname))))))) 198 | 199 | (defvar mykie:use-original-key-predicate nil 200 | "Predicate whether you use original keybind before load mykie.el.") 201 | 202 | (defconst mykie:get-fallback-function 203 | (lambda (args) 204 | (cond ((mykie:ignore-mode-p) 205 | ;; Return default function 206 | (funcall mykie:get-default-function args)) 207 | ((and mykie:use-original-key-predicate 208 | (funcall mykie:use-original-key-predicate)) 209 | (let ((func (lookup-key 210 | (bound-and-true-p mykie:original-map) 211 | (car (plist-get args :key-info))))) 212 | `(call-interactively ,func))))) 213 | "Fallback function that returning fallback function's symbol.") 214 | 215 | (defvar mykie:precheck-function 216 | (lambda (condition-name) 217 | (pcase condition-name 218 | (`mykie:region-conditions (mykie:region-p)) 219 | (`mykie:prefix-arg-conditions* 220 | (and current-prefix-arg 221 | (not (equal '(4) current-prefix-arg)))) 222 | (`mykie:prefix-arg-conditions current-prefix-arg) 223 | (_ t))) 224 | "Pre-check condition depending on CONDITION-NAME before check the 225 | CONDITION-NAME's condition. If you add conditions to 226 | `mykie:group-conditions', then you can add your precheck condition by 227 | change this variable. 228 | By default, this function check whether region is active or prefix-arg 229 | is exists.") 230 | 231 | ;; INTERNAL VARIABLES 232 | (defvar mykie:current-state nil) 233 | (defvar mykie:current-args '()) 234 | (defvar mykie:current-point "") 235 | (defvar mykie:current-thing nil) 236 | (defvar mykie:current-file nil) 237 | (defvar mykie:current-keyword nil) 238 | (defvar mykie:region-str "") 239 | (defvar mykie:C-u-num nil) 240 | (defconst mykie:original-map (copy-keymap global-map) 241 | "Keymap before load mykie.el.") 242 | 243 | (defvar mykie:global-keys '() 244 | "This variable will store global-map's key and mykie's args pair 245 | list without self insert key such as \"a\" etc.. 246 | See also `mykie:attach-mykie-func-to' or `mykie:use-major-mode-key-override'") 247 | 248 | (defvar mykie:self-insert-keys '() 249 | "This variable will store self-insert-key's key and mykie's args pair list. 250 | To use this variable, you need to use function `mykie:define-key-with-self-key' 251 | or `mykie:set-keys' with 'with-self-key argument. 252 | See also `mykie:attach-mykie-func-to' or `mykie:use-major-mode-key-override'") 253 | 254 | (defvar mykie:attached-mode-list '() 255 | "list that `mykie:attach-mykie-func-to' function attached mode name list.") 256 | 257 | (defvar mykie:prog-mode-flag nil 258 | "Buffer local variable, t means this buffer is related programing mode. 259 | Otherwise nil.") 260 | 261 | (add-hook 'prog-mode-hook 262 | (lambda () 263 | (set (make-local-variable 'mykie:prog-mode-flag) t))) 264 | 265 | ;;;###autoload 266 | (defmacro mykie:loop (&rest args) 267 | `(mykie:loop-core (quote ,args))) 268 | 269 | (defun mykie:loop-core (keybinds) 270 | (let* 271 | (keynum 272 | (exist-p 273 | (lambda () 274 | (cl-loop with input = (read-key) 275 | with format = (lambda (input) 276 | (condition-case _err 277 | (char-to-string input) 278 | (error (vector input)))) 279 | for i from 0 to (- (length keybinds) 2) by 2 280 | if (equal (nth i keybinds) 281 | (funcall format input)) 282 | do (cl-return (setq keynum i)))))) 283 | (cl-loop while (funcall exist-p) 284 | for func = (nth (1+ keynum) keybinds) 285 | if func 286 | do (mykie:execute func)))) 287 | 288 | ;;;###autoload 289 | (defmacro mykie:do-while (&rest args) 290 | "Firstly do 1th function of ARGS and then do `mykie:loop' with ARGS." 291 | `(progn (mykie:execute (nth 1 (quote ,args))) 292 | (mykie:loop-core (quote ,args)))) 293 | 294 | (defun mykie:execute (func) 295 | "Execute FUNC. 296 | You can specify like following forms to the FUNC: 297 | Example 298 | (mykie:loop 299 | \"j\" (lambda () 300 | (newline-and-indent)) 301 | \"m\" newline 302 | \"g\" (if current-prefix-arg 303 | (keyboard-quit)))" 304 | (mykie:run-hook 'before) 305 | (unless (ido-active) 306 | (run-hooks 'pre-command-hook)) 307 | (let ((current-prefix-arg 308 | (if (mykie:contain-exclamation-p mykie:current-state) 309 | nil 310 | current-prefix-arg))) 311 | (cl-typecase func 312 | (command 313 | ;; Do not use `setq' because the command loop overrides 314 | ;; `last-command', so use timer here. 315 | (run-with-timer 0 nil 'set 'last-command func) 316 | (funcall 'call-interactively func)) 317 | (function (funcall func)) 318 | (list (funcall 'eval `(progn ,func))))) 319 | (unless (or (ido-active) (bound-and-true-p multiple-cursors-mode)) 320 | (run-hooks 'post-command-hook)) 321 | (mykie:run-hook 'after)) 322 | 323 | ;;;###autoload 324 | (defun mykie:attach-mykie-func-to (&optional mode-symbol) 325 | "Attach mykie's functions to the MODE's same key function without :default. 326 | Use the MODE's function as :default function. 327 | If you didn't specify the MODE, then use current major-mode by default. 328 | The MODE is mode name's symbol such as 'emacs-lisp-mode." 329 | (interactive) 330 | (let* 331 | ((mode (or mode-symbol major-mode)) 332 | (ignore (or (member mode mykie:major-mode-ignore-list) 333 | (member mode mykie:minor-mode-ignore-list))) 334 | (attach-func 335 | (lambda (mykie-global-keys) 336 | (cl-loop with keymap-name = (format "%s-map" mode) 337 | with keymap = (symbol-value (intern keymap-name)) 338 | for key in mykie-global-keys 339 | for args = (funcall (lookup-key global-map key) t) 340 | for mode-func = (lookup-key keymap key) 341 | if (and (keymapp keymap) 342 | (functionp mode-func) 343 | (not (string-match "^mykie:" (symbol-name mode-func)))) 344 | do (mykie:clone-key 345 | key args `(:default ,mode-func) `(,keymap-name . ,keymap)))))) 346 | (if ignore 347 | (message (format "mykie overriding key: Ignore %S" mode)) 348 | (condition-case err 349 | (if (member mode mykie:attached-mode-list) 350 | (error (format "Mykie: already attached %s" (symbol-name mode))) 351 | (cl-case mykie:use-major-mode-key-override 352 | (both (funcall attach-func mykie:global-keys) 353 | (funcall attach-func mykie:self-insert-keys)) 354 | (global (funcall attach-func mykie:global-keys)) 355 | (self (funcall attach-func mykie:self-insert-keys)) 356 | (t (funcall attach-func mykie:self-insert-keys))) 357 | (add-to-list 'mykie:attached-mode-list mode)) 358 | (error err))))) 359 | 360 | (defun mykie:repeat-p () 361 | (equal this-command last-command)) 362 | 363 | (defun mykie:region-p () 364 | (or (region-active-p) 365 | (bound-and-true-p evil-visual-region-expanded))) 366 | 367 | (defun mykie:get-C-u-times () 368 | "Return times that your pushed C-u's times. And you can use mykie:C-u-num 369 | variable to get the times after do this function if you want." 370 | (setq mykie:C-u-num (truncate (log (or (car current-prefix-arg) 1) 4))) 371 | mykie:C-u-num) 372 | 373 | (defalias 'mykie:C-u-num 'mykie:get-C-u-times) 374 | 375 | (defmacro mykie:aif (test-form then-form &rest else-forms) 376 | "Like `if' but set the result of TEST-FORM in a temprary variable called `it'. 377 | THEN-FORM and ELSE-FORMS are then excuted just like in `if'." 378 | `(let ((it ,test-form)) 379 | (if it ,then-form ,@else-forms))) 380 | (put 'mykie:aif 'lisp-indent-function 2) 381 | 382 | (defun mykie:thing-exist-p (thing) 383 | "Return thing-at-point's return value of THING. 384 | If return value is non-nil, then save the value to `mykie:current-thing'." 385 | (mykie:aif (thing-at-point thing) 386 | (setq mykie:current-thing it))) 387 | 388 | (defun mykie:get-comment-state () 389 | "Return :comment if current point is comment." 390 | (when (nth 4 (syntax-ppss)) :comment)) 391 | 392 | (defun mykie:file-at-point-p () 393 | "Return non-nil if current point is file related path. 394 | And then save the path to `mykie:current-file' variable." 395 | (mykie:aif (ffap-file-at-point) 396 | (setq mykie:current-file it))) 397 | 398 | (defun mykie:error-occur-p () 399 | "Return non-nil if `flycheck-current-errors' or `flymake-err-info' is non-nil." 400 | (or (bound-and-true-p flymake-err-info) 401 | (bound-and-true-p flycheck-current-errors))) 402 | 403 | (defun mykie:get-major-mode-state (&optional prefix) 404 | "Return :major-mode, :C-u&major-mode or :region&major-mode if 405 | you specified same state name to `mykie's args and matched 406 | condition if you specified prefix(whether current-prefix-arg or region 407 | active). 408 | The major-mode replaced to `major-mode' name. 409 | You can specify \"C-u&\" or \"region&\" to the PREFIX." 410 | (let ((keyword (intern (format ":%s%s" (or prefix "") major-mode)))) 411 | (when (plist-get mykie:current-args keyword) 412 | keyword))) 413 | 414 | (defun mykie:get-prefix-arg-state () 415 | "Return keyword like :C-u, :C-*N or :M-N. 416 | If current-prefix-arg is list, return :C-u or :C-u*N(N is replaced to 417 | C-u's pushed times). 418 | If current-prefix-arg is number, return :M-N(the N is replaced number 419 | like M-1.). You can change the M-N's number by pushing M-[0-9] before 420 | call `mykie' function." 421 | (let ((exclamation (if (mykie:contain-exclamation-p) "!" ""))) 422 | (cl-typecase current-prefix-arg 423 | (list (let ((times (mykie:get-C-u-times))) 424 | (if (= 1 times) 425 | :C-u 426 | (intern (format ":C-u*%i%s" times exclamation))))) 427 | (number (intern (format ":M-%i%s" current-prefix-arg exclamation)))))) 428 | 429 | (defun mykie:contain-exclamation-p (&optional keyword) 430 | (string-match ":.+!$" (symbol-name (or keyword mykie:current-keyword)))) 431 | 432 | ;; Backward compatibility 433 | (defalias 'mykie:get-C-u-keyword 'mykie:get-prefix-arg-state) 434 | 435 | (defun mykie:get-skk-state () 436 | "Return SKK(simple kana kanji)'s state. 437 | If `on'(▽) mode then return :skk-on. 438 | If `active'(▼) mode then return :skk-active." 439 | (when (bound-and-true-p skk-mode) 440 | (cl-case (bound-and-true-p skk-henkan-mode) 441 | (active :skk-active) 442 | (on :skk-on)))) 443 | 444 | (defun mykie:ignore-mode-p () 445 | "Return non-nil if matched specified modes. 446 | If you specified :ignore-major-modes with major-mode's list to mykie's args, 447 | then check whether major-mode list match current major-mode. 448 | If you specified :ignore-minor-modes with minor-mode's list to mykie's args, 449 | then check whether minor-mode list match current `minor-mode-list'." 450 | (or (member major-mode 451 | (plist-get mykie:current-args :ignore-major-modes)) 452 | (cl-loop for mode in (plist-get mykie:current-args :ignore-minor-modes) 453 | if (member mode minor-mode-list) 454 | do (cl-return t)))) 455 | 456 | (defun mykie:initialize () 457 | (if mykie:use-major-mode-key-override 458 | (add-hook 'change-major-mode-after-body-hook 'mykie:attach-mykie-func-to) 459 | (remove-hook 'change-major-mode-after-body-hook 'mykie:attach-mykie-func-to))) 460 | 461 | (defun mykie:init (args) 462 | "Initialize mykie's global-variable before do mykie's command." 463 | (when (plist-get args :use-C-u-num) 464 | (mykie:get-C-u-times)) 465 | (setq mykie:current-args args) 466 | (let 467 | ((fallback (funcall mykie:get-fallback-function args))) 468 | (when fallback 469 | (pcase fallback 470 | (`(call-interactively ,func) (call-interactively func)) 471 | (fallback (mykie:execute fallback))) 472 | 'exit))) 473 | 474 | (defun mykie:region-init () 475 | "Initialize mykie's global-variable for region. 476 | You can use `mykie:region-str' variable that store region string. 477 | If you specified 'kill or 'copy with :region-handle-flag of mykie's args, 478 | then do `kill-region' or `copy-region-as-kill' before do mykie's command. 479 | So you can use kill-ring variable that store region's variable if you want." 480 | (setq mykie:region-str 481 | (buffer-substring (region-beginning) (region-end))) 482 | (cl-case (plist-get mykie:current-args :region-handle-flag) 483 | (kill (kill-region (region-beginning) (region-end))) 484 | (copy (copy-region-as-kill (region-beginning) (region-end))))) 485 | 486 | (defun mykie:deactivate-mark () 487 | "Deactivate region if you specified :deactivate-region of mykie's 488 | args with non-nil after do mykie's command." 489 | (let 490 | ((deactivation 491 | (plist-get mykie:current-args :deactivate-region))) 492 | (when (or (and (eq 'region deactivation) 493 | (eq :region mykie:current-state)) 494 | (and (eq 'region&C-u deactivation) 495 | (eq :region&C-u mykie:current-state)) 496 | (eq t deactivation)) 497 | (deactivate-mark)))) 498 | 499 | (defmacro mykie (&rest args) 500 | "Call function you are set functions. 501 | You can set below keyword to ARGS by default: 502 | 503 | *Functions* 504 | :default or t | Call this if conditions aren't matched all conditions 505 | :C-u | Call this if you pushed C-u key before pushing the key 506 | :C-u*N | Call this if you pushed N times of C-u 507 | :M-N | Call this if you pushed such as M-1 508 | :region | Call this if you are selecting region 509 | :region&C-u | Call this if you satisfied :region & :C-u condition 510 | :repeat | Call this if you repeat same command at same point 511 | :bolp | Call this if current point is beginning of line 512 | :eolp | Call this if current point is end of line 513 | :bobp | Call this if current point is beginning of buffer 514 | :eobp | Call this if current point is end of buffer 515 | :C-u&bolp | Call this if you satisfied :C-u & :bolp 516 | :C-u&eolp | Call this if you satisfied :C-u & :eolp 517 | :C-u&bobp | Call this if you satisfied :C-u & :bobp 518 | :C-u&eobp | Call this if you satisfied :C-u & :eobp 519 | :email | Call this if current point matched (thing-at-point 'email) 520 | :C-u&email | Call this if you satisfied :C-u & :email 521 | :url | Call this if current point matched (thing-at-point 'url) 522 | :C-u&url | Call this if you satisfied :C-u & :url 523 | :MAJOR-MODE | Call this if :MAJOR-MODE matched `major-mode'. 524 | :C-u&MAJOR-MODE | Call this if you satisfied :C-u & :MAJOR-MODE 525 | :region&MAJOR-MODE | Call this if you satisfied :region & :MAJOR-MODE 526 | :prog | Call this if current buffer is related `prog-mode' 527 | :C-u&prog | Call this if you satisfied :C-u & :prog 528 | :region&prog | Call this if you satisfied :region & :prog 529 | :err | Call this if error is exists of flymake or flycheck. 530 | :C-u&err | Call this if you satisfied :C-u & :err 531 | :region&err | Call this if you satisfied :region & :err 532 | :minibuff | Call this if current point is in minibuffer 533 | :readonly | Call this if current buffer is read-only 534 | :comment | Call this if current point is comment 535 | 536 | *Flags* 537 | :clone - Clone mykie's functions to other KEY. 538 | This function is convenient if you use Emacs either situation 539 | terminal and GUI. Because terminal Emacs can't use partial keybind 540 | such as C-;, this keyword can clone same functions to another key 541 | without :default function. 542 | 543 | For example: 544 | (mykie:global-set-key \"C-;\" 545 | :default (message \"foo\") 546 | :region comment-dwim 547 | :clone \";\") 548 | 549 | Above example copy mykie's function to \";\" key without :default. 550 | 551 | :region-handle-flag - you can set 'copy and 'kill 552 | If you set 'kill then region's string is killed. 553 | If you set 'copy then region's string is copied. 554 | And you can use the value by `kill-ring'. 555 | 556 | But you can use `mykie:region-str' variable that have region's string too. 557 | 558 | :deactivate-region - deactivate region after region command execution 559 | If you set 'region then deactivate region when you did not push C-u. 560 | If you set 'region&C-u then deactivate region when you pushed C-u. 561 | If you set t then deactivate region in both cases." 562 | `(mykie:core (quote ,args))) 563 | 564 | (defun mykie:core (args) 565 | (setq args (if (symbolp args) (symbol-value args) args)) 566 | (cl-loop initially (when (eq 'exit (mykie:init args)) (cl-return)) 567 | for conditions-sym in mykie:group-conditions 568 | for conditions = (symbol-value conditions-sym) 569 | for default-kw = (mykie:get-default-condition-keyword conditions-sym) 570 | if (and (funcall mykie:precheck-function conditions-sym) 571 | (if mykie:use-fuzzy-order 572 | (mykie:by-fuzzy-order args conditions default-kw) 573 | (mykie:by-condition-order args conditions default-kw))) 574 | do (progn (setq mykie:current-state it) 575 | (mykie:execute (plist-get args it)) 576 | (cl-return)) 577 | finally (mykie:execute (funcall mykie:get-default-function args))) 578 | (unless (mykie:repeat-p) 579 | (setq mykie:current-point (point)))) 580 | 581 | (defun mykie:by-fuzzy-order (args conditions default-keywords) 582 | "Check CONDITIONS by keyword base that extracted from ARGS." 583 | (cl-loop with cond-len = (1- (length conditions)) 584 | with default-keyword 585 | for i from 0 to (1- (length args)) by 2 586 | for keyword = (nth i args) 587 | if (member keyword mykie:default-keywords) 588 | do '() ; do nothing 589 | else if (cl-loop for j from 0 to cond-len 590 | for expect = (car (nth j conditions)) 591 | if (or (eq expect keyword) 592 | (and (stringp expect) 593 | (string-match 594 | expect (symbol-name keyword)) 595 | (setq mykie:current-keyword keyword))) 596 | do (cl-return (nth j conditions))) 597 | do (when (eq keyword (mykie:check it)) 598 | (cl-return keyword)) 599 | else if (member keyword default-keywords) 600 | do (setq default-keyword keyword) 601 | finally return default-keyword)) 602 | 603 | (defun mykie:check (kw-and-condition) 604 | "Return keyword if checking condition is succeed. 605 | If condition's result is keyword, return the value. 606 | Otherwise return KW-AND-CONDITION's first element." 607 | (let ((keyword (car kw-and-condition)) 608 | (result (eval (cdr kw-and-condition)))) 609 | (when result 610 | (if (keywordp result) 611 | result 612 | keyword)))) 613 | 614 | (defun mykie:by-condition-order (args conditions default-keywords) 615 | "Check CONDITIONS by the CONDITIONS order." 616 | (cl-loop for condition in conditions 617 | if (member (car condition) mykie:default-keywords) 618 | do '() 619 | else if (mykie:check condition) 620 | do (when (plist-get mykie:current-args it) 621 | (cl-return it)) 622 | finally return (cl-loop with last = (1- (length mykie:current-args)) 623 | for i from 0 to last by 2 624 | if (member (nth i args) default-keywords) do 625 | (cl-return (car it))))) 626 | 627 | (defun mykie:get-default-condition-keyword (conditions-sym) 628 | "Get condition specific keyword that match CONDITIONS-SYM from 629 | `mykie:default-condition-keyword-alist'." 630 | (car (assoc-default conditions-sym mykie:default-condition-keyword-alist))) 631 | 632 | (defun mykie:run-hook (direction) 633 | (when (funcall mykie:region-func-predicate) 634 | (cl-case direction 635 | (before (run-hooks 'mykie:region-before-init-hook)) 636 | (after (run-hooks 'mykie:region-after-init-hook))))) 637 | 638 | (defun mykie:kbd (key) 639 | (kbd key)) 640 | 641 | ;; TODO: Fix this to work around in old version Emacs 642 | (when (version< emacs-version "24.3") 643 | (defadvice mykie:kbd (around macro->function activate) 644 | (setq ad-return-value (apply (macroexpand (list 'kbd (ad-get-arg 0))))))) 645 | 646 | (defun mykie:format-key (key) 647 | (cl-typecase key 648 | (vector key) 649 | (string (mykie:kbd key)) 650 | (t (error "Invalid key")))) 651 | 652 | ;;;###autoload 653 | (defmacro mykie:define-key (keymap key &rest args) 654 | "In KEYMAP, define key sequence KEY as `mykie' command with ARGS. 655 | In other words, `mykie' + `define-key'. 656 | 657 | Example: 658 | (mykie:define-key global-map \"y\" 659 | :default self-insert-command 660 | :region (message \"%s\" mykie:region-str) 661 | :C-u (message \"C-u y\"))" 662 | `(mykie:define-key-core 663 | (symbol-name (quote ,keymap)) ,keymap ,key (quote ,args))) 664 | (put 'mykie:define-key 'lisp-indent-function 2) 665 | 666 | (defun mykie:define-key-core (keymap-name keymap key args) 667 | (let* ((key (mykie:format-key key))) 668 | (unless (member (key-description key) mykie:ignore-keybinds) 669 | (if (eq nil (car args)) 670 | (define-key keymap key nil) 671 | (let* ((args (append (mykie:parse-parenthesized-syntax args) 672 | (unless (plist-get args :key-info) 673 | `(:key-info (,key . ,keymap-name))))) 674 | (sym (funcall mykie:make-funcname-function 675 | args keymap key keymap-name))) 676 | (when (and (equal "global-map" keymap-name) 677 | (< 1 (length (key-description key)))) 678 | (add-to-list 'mykie:global-keys key)) 679 | (fset sym (mykie:make-mykie-function args)) 680 | (define-key keymap key sym) 681 | (mykie:aif (plist-get args :clone) 682 | (progn 683 | (if (and (stringp it) (= 1 (length it))) 684 | (add-to-list 'mykie:self-insert-keys it) 685 | (add-to-list 'mykie:global-keys it)) 686 | (mykie:clone-key 687 | it (mykie:replace-property args `(:key-info (,it . "global-map"))) 688 | '(:default self-insert-command))))))))) 689 | 690 | (defun mykie:make-mykie-function (args) 691 | (lambda (&optional get-args) 692 | (interactive) 693 | (if get-args args (funcall 'mykie:core args)))) 694 | 695 | (defun mykie:clone-key (key args default-keyword-and-func &optional keymap-info) 696 | (let 697 | ((new-args 698 | (mykie:filter (mykie:replace-property args default-keyword-and-func) 699 | :clone)) 700 | (map-name (or (car keymap-info) "global-map")) 701 | (map (or (cdr keymap-info) global-map))) 702 | (funcall 'mykie:define-key-core map-name map key new-args))) 703 | 704 | (defun mykie:replace-property (args key-and-property) 705 | (append (mykie:filter args (car key-and-property)) 706 | key-and-property)) 707 | 708 | (defun mykie:filter (args keyword) 709 | "Delete KEYWORD and the KEYWORD's function or property." 710 | (if (not (member keyword args)) 711 | args 712 | (cl-loop with last = (1- (length args)) 713 | with ignore = nil 714 | for i from 0 to last 715 | if (eq keyword (nth i args)) 716 | do (push (1+ i) ignore) 717 | else if (not (member i ignore)) 718 | collect (nth i args)))) 719 | 720 | ;;;###autoload 721 | (defmacro mykie:global-set-key (key &rest args) 722 | "Give KEY a global binding as `mykie' command. 723 | In other words, `mykie' + `global-set-key'. 724 | 725 | Example: 726 | (mykie:global-set-key \"z\" 727 | :default self-insert-command 728 | :region (message \"%s\" mykie:region-str) 729 | :C-u (message \"C-u z\"))" 730 | `(mykie:define-key-core "global-map" global-map ,key (quote ,args))) 731 | (put 'mykie:global-set-key 'lisp-indent-function 1) 732 | 733 | ;;;###autoload 734 | (defmacro mykie:define-key-with-self-key (key &rest args) 735 | "Set self-insert-key(KEY) with `mykie' command. 736 | This function register :default `self-insert-command' automatically to ARGS. 737 | Example: 738 | (mykie:define-key-with-self-key 739 | \"a\" :C-u (message \"I am C-u\"))" 740 | `(mykie:define-key-with-self-key-core ,key (quote ,args))) 741 | 742 | (defun mykie:define-key-with-self-key-core (key args) 743 | (setq args (mykie:parse-parenthesized-syntax args)) 744 | (add-to-list 'mykie:self-insert-keys key) 745 | (mykie:define-key-core "global-map" global-map (mykie:format-key key) 746 | (append args 747 | '(:default self-insert-command) 748 | (mykie:aif mykie:major-mode-ignore-list 749 | `(:ignore-major-modes ,it)) 750 | (mykie:aif mykie:minor-mode-ignore-list 751 | `(:ignore-minor-modes ,it))))) 752 | (put 'mykie:define-key-with-self-key 'lisp-indent-function 1) 753 | 754 | ;;;###autoload 755 | (defmacro mykie:set-keys (keymap-or-order &rest args) 756 | "Set keybinds as `mykie' command. 757 | Examples: 758 | Set keybinds to global-map: 759 | (mykie:set-keys nil ; You can set 'global or global-map instead of nil too. 760 | \"C-a\" 761 | :default (beginning-of-line) 762 | :C-u mark-whole-buffer 763 | \"C-e\" 764 | :default (end-of-line) 765 | :C-u (message \"Hello\")) 766 | 767 | Set keybinds to specific keymap: 768 | (mykie:set-keys emacs-lisp-mode-map 769 | \"C-1\" 770 | :default (message \"C-1\") 771 | :C-u (message \"C-1+C-u\") 772 | \"C-2\" 773 | :default (message \"C-2\") 774 | :C-u (message \"C-2+C-u\")) 775 | 776 | Set keybinds for self-insert-key 777 | You don't need to specify :default state, it's specified to 778 | 'self-insert-command automatically to it. 779 | (mykie:set-keys 'with-self-key 780 | \"a\" 781 | :C-u (message \"called a\") 782 | :region query-replace-regexp 783 | \"b\" 784 | :C-u (message \"called b\"))" 785 | `(mykie:set-keys-core (quote ,keymap-or-order) (quote ,args))) 786 | (put 'mykie:set-keys 'lisp-indent-function 1) 787 | 788 | (defun mykie:set-keys-core (keymap-or-order args) 789 | (let* 790 | ((pair (cl-typecase keymap-or-order 791 | (null '(nil . global-map)) 792 | (symbol (condition-case _err 793 | (when (keymapp (symbol-value keymap-or-order)) 794 | (cons nil keymap-or-order)) 795 | (error (cons keymap-or-order 'global-map)))))) 796 | (order (car pair)) 797 | (keymap-sym (cdr pair)) 798 | (set-key 799 | (lambda (key-and-prop &optional keymap-name keymap) 800 | (let ((key (car key-and-prop)) 801 | (property (mykie:format-property order (cdr key-and-prop)))) 802 | (cl-case order 803 | (with-self-key 804 | (mykie:define-key-with-self-key-core key property)) 805 | (t (mykie:define-key-core keymap-name keymap key property)))))) 806 | (set-keys 807 | (lambda () 808 | (cl-loop with last = (1- (length args)) 809 | with keymap-name = (symbol-name keymap-sym) 810 | with keymap = (symbol-value keymap-sym) 811 | for i from 0 to last 812 | for next = (1+ i) 813 | for key-or-prop = (nth i args) 814 | collect key-or-prop into key-and-prop 815 | if (or (equal i last) 816 | (and (not (eq :clone (nth i args))) 817 | (cl-typecase (nth next args) 818 | (string t) 819 | (vector t)))) 820 | do (progn 821 | (funcall set-key key-and-prop keymap-name keymap) 822 | (setq key-and-prop nil)))))) 823 | (funcall set-keys))) 824 | 825 | (defun mykie:format-property (order property) 826 | (let ((props (if (condition-case _err 827 | (keywordp (caar property)) 828 | (error nil)) 829 | (mykie:parse-parenthesized-syntax property) 830 | property))) 831 | (if (eq order 'with-self-key) 832 | props 833 | (if (not (or (member :default props) 834 | (member t props))) 835 | (cl-case (length props) 836 | (1 (append '(:default) props)) 837 | (2 (append '(:default) `(,props)))) 838 | props)))) 839 | 840 | ;;;###autoload 841 | (defun mykie:parse-parenthesized-syntax (args) 842 | (cl-typecase (car args) 843 | (list (cl-loop for (keyword . function) in args 844 | collect keyword into new-args 845 | if (listp function) 846 | collect `(progn ,@function) into new-args 847 | else collect function into new-args 848 | finally return new-args)) 849 | (symbol args))) 850 | 851 | ;;;###autoload 852 | (defmacro mykie:define-prefix-key (parent-map prefix-key params &rest mykie-keys) 853 | "Make prefix key with MYKIE-KEYS(mykielized keybindings). 854 | 855 | This function takes PARENT-MAP as parent of PREFIX-KEY. 856 | 857 | The PARAMS can take several parameters of anonymous function: 858 | :keep -- keep prefix keymap as long as this function return non-nil 859 | :exit -- exit prefix keymap when the function return non-nil 860 | :before -- call this function before entering prefix keymap 861 | :after -- call this function after exited prefix keymap 862 | 863 | Note that those keyword PARAMS are optional; which means if you set 864 | nil to the PARAMS, created function will be exited after you type any 865 | created key." 866 | `(let* ((key (mykie:format-key ,prefix-key)) 867 | (keyname (key-description key)) 868 | (prefix-map (intern (format "mykie:%s-prefix-%s" 869 | (quote ,parent-map) keyname))) 870 | (pred 871 | `(lambda () 872 | (let ((exit ,(plist-get (quote ,params) :exit)) 873 | (keep ,(plist-get (quote ,params) :keep))) 874 | (or (when (functionp keep) (funcall keep)) 875 | (when (functionp exit) (not (funcall exit))))))) 876 | (on-enter (plist-get (quote ,params) :before)) 877 | (on-exit (plist-get (quote ,params) :after)) 878 | (parent (symbol-value (quote ,parent-map)))) 879 | (define-prefix-command prefix-map) 880 | (mykie:set-keys-core prefix-map (quote ,mykie-keys)) 881 | (fset prefix-map 882 | `(lambda () 883 | (interactive) 884 | (when (functionp ,on-enter) (funcall ,on-enter)) 885 | (set-transient-map ,prefix-map ,pred ,on-exit))) 886 | (define-key parent key prefix-map))) 887 | (put 'mykie:define-prefix-key 'lisp-indent-function 2) 888 | 889 | (defmacro mykie:combined-command (&rest args) 890 | "This macro return lambda form with `mykie'. 891 | So you can register keybind like this: 892 | 893 | (global-set-key (kbd \"C-j\") 894 | (mykie:combined-command 895 | :default newline-and-indent 896 | :C-u (fill-region (point-at-bol) (point-at-eol))))" 897 | `(lambda () 898 | (interactive) 899 | (mykie:core (quote ,args)))) 900 | 901 | (defadvice mykie:combined-command 902 | (around ad-parse-parenthesized activate) 903 | "Parse args to convert parenthesized-syntax if it was needed." 904 | (ad-set-args 0 (mykie:parse-parenthesized-syntax (ad-get-args 0))) 905 | ad-do-it) 906 | 907 | ;;;###autoload 908 | (defadvice mykie 909 | (around mykie:parse-parenthesized-syntax activate) 910 | "Parse args to convert parenthesized-syntax if it was needed." 911 | (ad-set-args 0 (mykie:parse-parenthesized-syntax (ad-get-args 0))) 912 | ad-do-it) 913 | 914 | (when mykie:use-major-mode-key-override 915 | (mykie:initialize)) 916 | 917 | (defun mykie:setup-magit-popup-mode-keybind () 918 | (mykie:aif (bound-and-true-p magit-popup-mode-map) 919 | (cl-loop with map = it 920 | for key in mykie:self-insert-keys 921 | unless (member key '("?" "-" "=")) 922 | do (define-key map key 'magit-invoke-popup-action)))) 923 | 924 | ;; work around that magit doesn't bind mykie's self-insert keys. 925 | ;; TODO: suppress "Warning: Eager macro-expansion skipped due to cycle" 926 | (eval-after-load 'magit '(mykie:setup-magit-popup-mode-keybind)) 927 | 928 | (provide 'mykie) 929 | 930 | ;; Local Variables: 931 | ;; coding: utf-8 932 | ;; mode: emacs-lisp 933 | ;; End: 934 | 935 | ;;; mykie.el ends here 936 | --------------------------------------------------------------------------------