micis uncustomizable. Define your ownmic.
mic is minimal and combinable configuration manager for Emacs.
This package is yet another use-package and leaf, but is also used with them (See Alternative).
mic is minimal, so if you would like to write complex configuration,
mic is a little redundant. However, there is no problem. mic is combinable, in the other words, thought to be
used as core to define your own, and more convenient mic.
There are some functions to define your own mic. See Define your own mic.
For Emacs Lisp beginners, original mic macro is useful to configure your init.el.
(mic lsp-mode
;; These are transformed to `define-key' sexp.
;; Each argument is `(KEYMAP (KEYS . COMMAND)...)'.
;; KEYS is passed to `kbd'.
:define-key
((global-map
("M-l" . #'lsp)))
;; These are same as `:define-key' argument,
;; but evaluated after loading the feature (`lsp-mode' for this example).
;; This is needed because `lsp-mode-map' is unavailable before `lsp'
;; loading.
:define-key-after-load
((lsp-mode-map
("M-r" . #'lsp-rename)
("M-c" . #'lsp-execute-code-action)))
;; These are transformed to `with-eval-after-load' and `define-key' sexp.
;; Each argument is `(FEATURE (KEYMAP (KEYS . COMMAND)...))'.
;; `cdr' is same as `:define-key' arguments. Each `define-key' sexp is
;; evaluated after FEATURE is loaded.
;; This is needed because `dired-mode-map' is unavailable before `dired'
;; loading.
:define-key-with-feature
((dired
(dired-mode-map
("M-q" . #'lsp-dired-mode))))
;; These are transformed to `customize-set-variable' sexp.
;; Each argument is `(VARIABLE . VALUE)'.
:custom
((lsp-sesstion-file . (expand-file-name "etc/.lsp-session-v1" user-emacs-directory))
(lsp-log-io . t))
;; These are transformed to `add-hook' sexp.
;; Each argument is `(HOOK . FUNCTION)'.
:hook
((c-mode-hook . #'lsp)
(c++-mode-hook . #'lsp)
(tex-mode-hook . #'lsp)
(latex-mode-hook . #'lsp)
(bibtex-mode-hook . #'lsp)
(rust-mode-hook . #'lsp))
;; Each element is evaluated immediately when this `mic' sexp is evaluated.
:eval
((message "This is evaluated when this `mic' sexp is evaluated.")
(message "This is also evaluated."))
;; Each element will be evaluated after the package (`lsp-mode' for this example) is loaded.
:eval-after-load
((message "This is evaluated when `lsp-mode' is loaded."))
;; Each element is evaluated immediately when this `mic' sexp is evaluated.
;; These are evaluated before `:eval' and `:eval-after-load' elements.
;; This is for such use as defining function to use `:custom' argument.
:eval-before-all
((message "This is evaluated when this `mic' sexp is evaluated.")
(message "These are evaluated before `:eval' and `:eval-after-load' sexp.")))
;; `mic' sexp above is expanded to:
(prog1 'lsp-mode
;; `:eval-before-all'
(message "This is evaluated when this `mic' sexp is evaluated.")
(message "These are evaluated before `:eval' and `:eval-after-load' sexp.")
;; `:eval-after-load'
(with-eval-after-load 'lsp-mode
(message "This is evaluated when `lsp-mode' is loaded.")
;; `:define-key-after-load'
(define-key lsp-mode-map
(kbd "M-r")
(function lsp-rename))
(define-key lsp-mode-map
(kbd "M-c")
(function lsp-execute-code-action)))
;; `:eval'
(message "This is evaluated when this `mic' sexp is evaluated.")
(message "This is also evaluated.")
;; `:custom'
(customize-set-variable 'lsp-sesstion-file
(expand-file-name "etc/.lsp-session-v1" user-emacs-directory))
(customize-set-variable 'lsp-log-io t)
;; `:define-key'
(define-key global-map (kbd "M-l") #'lsp)
;; `:define-key-with-feature'
(with-eval-after-load 'dired
(define-key dired-mode-map (kbd "M-q") #'lsp-dired-mode))
;; `:hook'
(add-hook 'c-mode-hook #'lsp)
(add-hook 'c++-mode-hook #'lsp)
(add-hook 'tex-mode-hook #'lsp)
(add-hook 'latex-mode-hook #'lsp)
(add-hook 'bibtex-mode-hook #'lsp)
(add-hook 'rust-mode-hook #'lsp))For Emacs Lisp expert, original mic is a little unsatisfactory or redundant.
mic is not customizable, but you can define your own mic easily.
- Determine parent. You can use as parent
mic,mic-core, which is simplermic.mic-corerecieves only keywords start from:eval, such as:eval,eval-after-load. - Define filter functions. Each one recieves plist (property list) and returns plist.
returned plist is passed to parent (such as
mic,mic-core) or next filter. Note that filter function can get feature name as value of property:name. Of course, you can use pre-defined filters.micis defined by some filters from the parentmic-core. - Define your own mic by
mic-defmic. It recievesNAME, optionalDOCSTRING, and keyword argumentFILTERS.NAMEis name of your ownmic.DOCSTRINGis the document string of yours.FILTERSare list of filter. As explained, filter recieves plist and returns plist. It filter plist to get desired behavior.
(defun my-filter-global-set-key-without-quote (plist)
(let ((alist
;; Get value from your own keyword
(plist-get plist :bind))
sexps)
(setq sexps
;; Transform each element
(mapcar
(lambda (arg)
(let ((keys (car arg))
(command (cdr arg)))
`(global-set-key (kbd ,keys) #',command)))
alist))
;; Put sexps to `:eval' arguments
(mic-plist-put-append plist :eval sexps)
;; Don't forget to delete your own keyword!
;; When forget it, parent recieves it and may cause unexpected result.
(mic-plist-delete plist :bind)
plist))
(mic-defmic mymic
;; Parent is here. You can also use `mic-core'.
mic
:filters
'(my-filter-global-set-key-without-quote
;; You can add other filters below
))
;; Then you can use `mymic' like:
(mymic simple
:bind
(("C-d" . delete-forward-char)
("C-x l" . toggle-truncate-lines))
;; Of course parent keywords are accepted.
:custom
((kill-whole-line . t)
(set-mark-command-repeat-pop . t)
(mark-ring-max . 50)))
;; `mymic' sexp is expanded to:
(mic simple
:custom
((kill-whole-line . t)
(set-mark-command-repeat-pop . t)
(mark-ring-max . 50))
:eval
((global-set-key (kbd "C-d") #'delete-forward-char)
(global-set-key (kbd "C-x l") #'toggle-truncate-lines)))
;; Expanded to:
(mic-core simple
:eval
((global-set-key (kbd "C-d") #'delete-forward-char)
(global-set-key (kbd "C-x l") #'toggle-truncate-lines)
(customize-set-variable 'kill-whole-line t)
(customize-set-variable 'set-mark-command-repeat-pop t)
(customize-set-variable 'mark-ring-max 50))
:eval-after-load nil)
;; Expanded to:
(prog1 'simple
(global-set-key (kbd "C-d") #'delete-forward-char)
(global-set-key (kbd "C-x l") #'toggle-truncate-lines)
(customize-set-variable 'kill-whole-line t)
(customize-set-variable 'set-mark-command-repeat-pop t)
(customize-set-variable 'mark-ring-max 50))mic-core is minimum. It can recieves only several keywords:
:eval:eval-after-load:eval-after-others:eval-after-others-after-load:eval-before-all:eval-installation
Each element of :eval arguments are evaluated.
Time to evaluate is different.
Each element of these arguments are evaluated when the mic sexp is evaluated.
The order is:
:eval-before-all- (
with-eval-after-loadsexp, explained on =eval-after-load= keyword section, is evaluated) :eval:eval-after-others
(mic-core feature-name
:eval
((message "eval1")
(message "eval2"))
:eval-after-others
((message "eval-after-others1")
(message "eval-after-others2"))
:eval-before-all
((message "eval-before-all1")
(message "eval-before-all2"))
:eval-after-load
((message "eval-after-load1")
(message "eval-after-load2")))
;; Expanded to:
(prog1 'feature-name
(message "eval-before-all1")
(message "eval-before-all2")
(with-eval-after-load 'feature-name
(message "eval-after-load1")
(message "eval-after-load2"))
(message "eval1")
(message "eval2")
(message "eval-after-others1")
(message "eval-after-others2")):eval-before-all exists because a filter function appends sexp to :eval argument.
When some action should be evaluated before all action added by other filters,
you can put it to :eval-before-all argument. Note that it should NOT be used
by filters. Any filter should not use this. If it is used by filters,
users cannot make their sexp to be evaluate before filter sexps.
:eval-after-others exists because similar reason to :eval-before-all.
Some action should be evaluated after all action added by other filters.
Because of same reasons as :eval-before-all, it should NOT be used
by filters.
:eval-after-load:eval-after-others-after-load
(mic-core feature-name
:eval-after-load
((message "eval-after-load1")
(message "eval-after-load2"))
:eval-after-others-after-load
((message "eval-after-others-after-load1")
(message "eval-after-others-after-load2")))
;; Expanded to:
(prog1 'feature-name
(with-eval-after-load 'feature-name
(message "eval-after-load1")
(message "eval-after-load2")
(message "eval-aftepr-others-after-load1")
(message "eval-after-others-after-load2"))):eval-after-others-after-load exists because similar reason to :eval-after-others.
Some action should be evaluated after all action added by other filters.
Because of same reasons as :eval-before-all, it should NOT be used
by filters.
:eval* argument except :eval-before-all.
This exists because sexp to install the package is evaluated before sexp which uses package features.
(mic-core feature-name
:eval-before-all
((message "before all2")
(message "before all1"))
:eval-installation
((message "install1")
(message "install2"))
:eval-after-load
((message "eval-after-load1")
(message "eval-after-load2"))
:eval-after-others-after-load
((message "eval-after-others-after-load1")
(message "eval-after-others-after-load2"))
:eval
((message "eval1")
(message "eval2")))
;; Expanded to:
(prog1 'feature-name
(message "before all2")
(message "before all1")
(message "install1")
(message "install2")
(with-eval-after-load 'feature-name
(message "eval-after-load1")
(message "eval-after-load2")
(message "eval-after-others-after-load1")
(message "eval-after-others-after-load2"))
(message "eval1")
(message "eval2")):eval-after-others-after-load exists because similar reason to :eval-after-others.
Some action should be evaluated after all action added by other filters.
Because of same reasons as :eval-before-all, it should NOT be used
by filters.
mic is minimal for use. mic-core is minimum core, but it is not enough to use as it is.
In addition to keywords allowed by =mic-core=, it allows some keyword arguments:
:autoload-interactive:autoload-noninteractive:auto-mode:custom:custom-after-load:declare-function:define-key:define-key-after-load:define-key-with-feature:defvar-noninitial:face:hook:package:require:require-after
These are transformed to autoload sexps. Each element is function to autoload.
Since autoload should be informed whether the function is interactive or not,
both :autoload-interactive and :autoload-noninteractive exist.
(mic feature-name
:autoload-interactive
(interactive-func1
interactive-func2)
:autoload-noninteractive
(noninteractive-func3
noninteractive-func4))
;; Expanded to:
(mic-core feature-name :eval
((autoload #'interactive-func1 "feature-name" nil t)
(autoload #'interactive-func2 "feature-name" nil t)
(autoload #'noninteractive-func3 "feature-name")
(autoload #'noninteractive-func4 "feature-name"))
:eval-after-load nil)
;; Expanded to:
(prog1 'feature-name
(autoload #'interactive-func1 "feature-name" nil t)
(autoload #'interactive-func2 "feature-name" nil t)
(autoload #'noninteractive-func3 "feature-name")
(autoload #'noninteractive-func4 "feature-name"))It is transformed to sexp like (add-to-list 'auto-mode-alist ...).
Each element of the value should be valid as an element of auto-mode-alist.
(mic feature-name
:auto-mode
(("\\.html?\\'" . web-mode)
("\\.css\\'" . web-mode)))
;; Expanded to:
(mic-core feature-name :eval-installation
((add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.css\\'" . web-mode)))
:eval nil :eval-after-load nil)
;; Expanded to:
(prog1 'feature-name
(add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.css\\'" . web-mode)))
These are transformed to customize-set-variable sexps.
Each element is (VARIABLE . VALUE).
Each VARIABLE is set to VALUE.
Sexp from :custom argument are evaluated when the mic sexp is evaluated,
while sexp from :custom-after-load argument are evaluated after the feature is loaded.
:custom-after-load is used when you want to use initial value of customized variable
or function defined in the feature.
(mic feature-name
:custom
((variable1 . 1)
;; VALUE is evaluated
(variable2 . (+ 1 1)))
:custom-after-load
;; You can use the initial value of `variable3'
((variable3 . (+ variable3 1))
;; You can use function defined in the feature (for this example `feature-name')
(variable2 . (function-defined-in-feature-name))))
;; Expanded to:
(mic-core feature-name
:eval
((customize-set-variable 'variable1 1)
(customize-set-variable 'variable2
(+ 1 1)))
:eval-after-load
((customize-set-variable 'variable3
(+ variable3 1))
(customize-set-variable 'variable2
(function-defined-in-feature-name))))
;; Expanded to:
(prog1 'feature-name
(with-eval-after-load 'feature-name
;; `variable3' is already defined.
(customize-set-variable 'variable3
(+ variable3 1))
;; `function-defined-in-feature-name' is already defined.
(customize-set-variable 'variable2
(function-defined-in-feature-name)))
(customize-set-variable 'variable1 1)
(customize-set-variable 'variable2
(+ 1 1)))These arguments declare functions and variables.
Each element of declare-function / defvar-noninitial is symbol as function/variable.
They exist in order to suppress warning of undefined functions/variables.
(mic feature-name
:declare-function
(function1
function2)
:defvar-noninitial
(variable1
variable2))
;; Expanded to:
(mic-core feature-name
:eval
((declare-function function1 "ext:feature-name")
(declare-function function2 "ext:feature-name")
(defvar variable1)
(defvar variable2))
:eval-after-load nil)
;; Expanded to:
(prog1 'feature-name
;; They declare that the functions `function1' and `function2' is defined in
;; the feature `feature-name'.
(declare-function function1 "ext:feature-name")
(declare-function function2 "ext:feature-name")
;; They declare that the variables `variable1' and `variable2' will be defined.
;; `defvar' without initial value declares symbol as variable.
(defvar variable1)
(defvar variable2))These arguments is transformed to define-key sexps.
On :define-key or :define-key-after-load, each element of the argument is
(KEYMAP (KEYS . COMMAND)...). KEYMAP is keymap. KEYS is passed to kbd.
COMMAND is interactive function.
On :define-key-with-feature, each element is (FEATURE (KEYMAP (KEYS . COMMAND)...)).
FEATURE is feature, and the define-key sexp is evaluated after loading the FEATURE.
This exists in order to define COMMAND in the feature with KEYS to KEYMAP defined in FEATURE.
Use it to make sure that KEYMAP is defined.
(mic feature-name
:define-key
;; (KEYMAP (KEYS . COMMAND)...)
((global-map
;; #' is needed
("M-l" . #'feature-name-command1))
(prog-mode-map
;; #' is needed
("M-a" . #'feature-name-comman2)))
:define-key-after-load
;; When `feature-name-mode-map' is defined in `feature-name',
;; use `:define-key-after-load'.
((feature-name-mode-map
("M-r" . #'feature-name-command3)
("M-c" . #'feature-name-command4)))
;; When `other-feature-mode-map' is defined in `other-feature', which is not `feature-name',
;; use `:define-key-with-feature'.
:define-key-with-feature
((other-feature
(other-feature-mode-map
("M-q" . #'feature-name-command5)))))
;; Expanded to:
(mic-core feature-name
:eval
((define-key global-map (kbd "M-l") #'feature-name-command1)
(define-key prog-mode-map (kbd "M-a") #'feature-name-comman2)
(with-eval-after-load 'other-feature
(define-key other-feature-mode-map (kbd "M-q") #'feature-name-command5)))
:eval-after-load
((define-key feature-name-mode-map (kbd "M-r") #'feature-name-command3)
(define-key feature-name-mode-map (kbd "M-c") #'feature-name-command4)))
;; Expanded to:
(prog1 'feature-name
(with-eval-after-load 'feature-name
;; `:define-key-after-load'
(define-key feature-name-mode-map (kbd "M-r") #'feature-name-command3)
(define-key feature-name-mode-map (kbd "M-c") #'feature-name-command4))
;; `:define-key'
(define-key global-map (kbd "M-l") #'feature-name-command1)
(define-key prog-mode-map (kbd "M-a") #'feature-name-comman2)
;; `:define-key-with-feature'
(with-eval-after-load 'other-feature
(define-key other-feature-mode-map (kbd "M-q") #'feature-name-command5)))This is transformed to custom-set-faces sexp.
Each element is (FACE-SYMBOL . FACE-DEFINITION).
(mic feature-name
:face
((face-1
. ((t (:foreground "red" :height 10.0))))
(face-2
. ((t (:background "#006000" :foreground "white" :bold t))))))
;; Expanded to:
(mic-core feature-name
:eval
((custom-set-faces
'(face-1
((t (:foreground "red" :height 10.0))))
'(face-2
((t (:background "#006000" :foreground "white" :bold t))))))
:eval-after-load nil)
;; Expanded to:
(prog1 'feature-name
(custom-set-faces
'(face-1
((t (:foreground "red" :height 10.0))))
'(face-2
((t (:background "#006000" :foreground "white" :bold t))))))This is transformed to add-hook sexp.
Each element is (HOOK . FUNCTION).
(mic feature-name
:hook
;; #' is needed
((hook1 . #'function1)
(hook2 . #'function2)
;; `lambda' is allowed (but not recommended)
(hook3 . (lambda (arg) 1))))
;; Expanded to:
(mic-core feature-name
:eval
((add-hook 'hook1 #'function1)
(add-hook 'hook2 #'function2)
(add-hook 'hook3 (lambda (arg) 1)))
:eval-after-load nil)
;; Expanded to:
(prog1 'feature-name
(add-hook 'hook1 #'function1)
(add-hook 'hook2 #'function2)
(add-hook 'hook3 (lambda (arg) 1)))This is transformed to package-install sexps.
Each arguments are PKG used by package-install.
The expandation result is complicated, because it is annoying to fetch package archives many times.
(mic feature-name
:package
(package-name1
package-name2))
;; Expanded to:
(mic-core feature-name
:eval
;; When package is not installed
((unless (package-installed-p 'package-name1)
;; Ensure package is exists in archive
(when (assq 'package-name1 package-archive-contents)
(ignore-errors
(package-install 'package-name1)))
(unless (package-installed-p 'package-name1)
;; Refresh (fetch) new archive
(package-refresh-contents)
(condition-case _
(package-install 'package-name1)
(error
(warn "Package %s is not found" 'package-name1)))))
(unless (package-installed-p 'package-name2)
(when (assq 'package-name2 package-archive-contents)
(ignore-errors
(package-install 'package-name2)))
(unless (package-installed-p 'package-name2)
(package-refresh-contents)
(condition-case _
(package-install 'package-name2)
(error
(warn "Package %s is not found" 'package-name2))))))
:eval-after-load nil)
;; Expand to:
(prog1 'feature-name
(unless (package-installed-p 'package-name1)
(when (assq 'package-name1 package-archive-contents)
(ignore-errors
(package-install 'package-name1)))
(unless (package-installed-p 'package-name1)
(package-refresh-contents)
(condition-case _
(package-install 'package-name1)
(error
(warn "Package %s is not found" 'package-name1)))))
(unless (package-installed-p 'package-name2)
(when (assq 'package-name2 package-archive-contents)
(ignore-errors
(package-install 'package-name2)))
(unless (package-installed-p 'package-name2)
(package-refresh-contents)
(condition-case _
(package-install 'package-name2)
(error
(warn "Package %s is not found" 'package-name2))))))This is transformed to require sexps.
Each element is feature symbol and required on :eval.
(mic feature-name
:require
(feat1
feat2))
;; Expand to:
(mic-core feature-name
:eval-installation nil
:eval
((require 'feat1)
(require 'feat2))
:eval-after-load nil)
;; Expand to:
(prog1 'feature-name
(require 'feat1)
(require 'feat2))This is transformed to require sexps in with-eval-after-load section.
Each element is alist. car of each element is feature symbol which is
used as first argument of with-eval-after-load.
cdr of each element is list of features required after the car.
This is used when you should require package after another one but
there is no functions to call so autoload cannot be used.
(mic feature-name
:require-after
((feat-after1
. (feat1 feat2))
(feat-after2
feat3
feat4)))
;; Expand to:
(mic-core feature-name
:eval-installation nil
:eval
((with-eval-after-load 'feat-after1
(require 'feat1)
(require 'feat2))
(with-eval-after-load 'feat-after2
(require 'feat3)
(require 'feat4)))
:eval-after-load nil)
;; Expand to:
(prog1 'feature-name
(with-eval-after-load 'feat-after1
(require 'feat1)
(require 'feat2))
(with-eval-after-load 'feat-after2
(require 'feat3)
(require 'feat4)))mic behavior? It is OK. You can define your own mic!
There are some ways to define it:
- Use
mic-defmic - Use
defmacro
If you would like to add keywords, or to make some keywords more simple,
you can define filter and apply it to mic (or mic-core, and another mic, any parent is allowed).
The filter recieves one argument, PLIST (plist, property list), and returns RETURNED-PLIST.
It filters or transforms it into returned plist.
It is better to divide filters by every keyword, because of reusability.
- Each filter recieves 1 argument
PLIST, which is plist (property list). - Each filter returns
RETURNED-PLIST, which is plist. PLISTis given by user or filter before.PLISThave feature name:nameproperty.RETURNED-PLISTis passed to next filter or parentmic(mic,mic-core, or another).RETURNED-PLISTshould have same value of:nameproperty.- The property only used by your filter should be removed in
RETURNED-PLIST.
Here is example:
(defun my-filter-global-set-key-without-quote (plist)
(let ((alist
;; Get value from your own keyword
(plist-get plist :bind))
sexps)
(setq sexps
;; Transform each element
(mapcar
(lambda (arg)
(let ((keys (car arg))
(command (cdr arg)))
`(global-set-key (kbd ,keys) #',command)))
alist))
;; Put sexps to `:eval' arguments
(mic-plist-put-append plist :eval sexps)
;; Don't forget to delete your own keyword!
;; When forget it, parent recieves it and may cause unexpected result.
(mic-plist-delete plist :bind)
plist))
;; `defmic' defines new `mic' (see "Define mic with mic-defmic" section for more infomation)
(mic-defmic yourmic
mic ; Derived from `mic'
:filters '(my-filter-global-set-key-without-quote))
;; Here is `yourmic' expression
(yourmic package-name
;; New keyword you added by `my-filter-global-set-key-without-quote'
:bind
(("M-a" . beginning-of-defun)
("M-e" . end-of-defun))
;; Of course keywords for `mic', which is original of `yourmic', is allowed.
:hook ((after-init-hook . #'ignore)))
;; Then first `PLIST' is:
'( :name package-name
:bind (("M-a" . beginning-of-defun)
("M-e" . end-of-defun))
:hook ((after-init-hook . #'ignore)))
;; When you expand the sexp before, the filter you defined is called like:
(my-filter-global-set-key-without-quote
'( :name package-name
:bind (("M-a" . beginning-of-defun)
("M-e" . end-of-defun))
:hook ((after-init-hook . #'ignore))))
;; It returns `RETURNED-PLIST':
'( :name package-name
:hook ((after-init-hook function ignore))
:eval
((global-set-key (kbd "M-a") #'beginning-of-defun)
(global-set-key (kbd "M-e") #'end-of-defun)))
;; The `RETURNED-PLIST' is passed to a next filter if exists.
;; You use only one filter in definition,
;; so it is expanded to:
(mic package-name
:hook ((after-init-hook . #'ignore))
:eval
((global-set-key (kbd "M-a") #'beginning-of-defun)
(global-set-key (kbd "M-e") #'end-of-defun)))Some pre-defined filter, unused by mic definition, are available in mic-filter.el.
mic-filter-ell-getmic-filter-straightmic-filter-quelpa
For more infomation, see docstring of each filter.
;;; el-get
(mic-defmic mic-with-el-get mic
:filters '(mic-filter-el-get))
(mic-with-el-get hydra
:el-get ((hydra :repo "abo-abo/hydra" :fetcher github)))
;; Expanded to:
(mic hydra
:eval-installation
((el-get-bundle hydra :repo "abo-abo/hydra" :fetcher github)));;; quelpa
(mic-defmic mic-with-quelpa mic
:filters '(mic-filter-quelpa))
(mic-with-quelpa hydra
:quelpa ((hydra :repo "abo-abo/hydra" :fetcher github)))
;; Expanded to:
(mic hydra
:eval-installation
((quelpa
'(hydra :repo "abo-abo/hydra" :fetcher github))));;; straight
(mic-defmic mic-with-straight mic
:filters '(mic-filter-straight))
(mic-with-straight hydra
:straight ((hydra :repo "abo-abo/hydra" :host github)))
;; Expanded to:
(mic hydra
:eval-installation
((straight-use-package
'(hydra :repo "abo-abo/hydra" :host github))))mic-filter-define-key-general,mic-filter-general-define-keymic-filter-mykiemic-filter-hydramic-filter-pretty-hydra,mic-filter-pretty-hydra+mic-filter-mode-hydra
Here is summaries and examples for these filters. See a docstring and definition of each filter for more information.
general.el makes key definition more convenient. There are some filters for integration with it:
mic-filter-define-key-generalmic-filter-general-define-key
The both are expanded to general-define-key call.
mic-filter-define-key-general, which uses a :define-key-general keyword, is compatible with :define-key keyword.
In the other words, the syntax like ((keymap (key . function)...)...) is allowed but general-define-key is used as backend.
On the other hand, mic-filter-general-define-key, which uses :general-define-key keyword, uses general-define-key syntax.
So you can use :keymap or :prefix keyword. Each element of the value of :general-define-key is directly passed to general-define-key.
(mic-defmic mic-with-define-key-general mic
:filters
'(mic-filter-define-key-general))
(mic-with-define-key-general package-name
:define-key-general
((keymap1
("C-d" . #'func1)
("C-q" . #'func2))
(override
("C-a" . #'func3)
("C-e" . #'func4))))
;; Expanded to:
(mic package-name
:eval
((general-define-key :keymaps 'keymap1
"C-d" (function func1)
"C-q" (function func2))
(general-define-key :keymaps 'override
"C-a" (function func3)
"C-e" (function func4))))Mykie.el is is multiplexer of key definition. There is filter for mykie:
mic-filter-mykie
mic-filter-mykie, which uses a :mykie keyword, creates mykie:define-key sexp.
Each element of the value on :mykie keyword is a cons cell like ((keymap (key [:keyword function1] ...)...)...).
car of each element, which is keymap, and each element of cdr of each element of the value is passed to mykie:define-key.
(mic-defmic mic-with-filter-mykie mic
:filters
'(mic-filter-mykie))
(mic-with-filter-mykie package-name
:mykie
((global-map
("C-w" :default hydra-window-resizer/body :region kill-region))))
;; Expanded to:
(mic package-name
:eval
((mykie:define-key global-map "C-w" :default hydra-window-resizer/body :region kill-region)))Hydra makes Emacs bindings stick around. There is a filter for integration of Hydra:
mic-filter-hydra
mic-filter-hydra, which uses a :hydra keyword, creates defhydra sexp.
Each element of the value on the :hydra keyword is passed to defhydra directly.
(mic-defmic mic-with-hydra mic
:filters '(mic-filter-hydra))
(mic-with-hydra package-name
:hydra
;; Spacing induces good indent
(( hydra-window-resizer ()
("p" shrink-window "shrink")
("n" enlarge-window "enlarge")
("f" enlarge-window-horizontally "enlarge-horizontally")
("b" shrink-window-horizontally "shrink-horizontally")
("<down>" shrink-window)
("<up>" enlarge-window)
("<right>" enlarge-window-horizontally)
("<left>" shrink-window-horizontally)
("q" nil "quit"))))
;; Expanded to:
(mic package-name
:eval
((defhydra hydra-window-resizer nil
("p" shrink-window "shrink" :exit nil :cmd-name hydra-window-resizer/shrink-window :column nil)
("n" enlarge-window "enlarge")
("f" enlarge-window-horizontally "enlarge-horizontally")
("b" shrink-window-horizontally "shrink-horizontally")
("<down>" shrink-window)
("<up>" enlarge-window)
("<right>" enlarge-window-horizontally)
("<left>" shrink-window-horizontally)
("q" nil "quit"))))Pretty Hydra defines prettier hydra. There is some filters for integration of it:
mic-filter-pretty-hydramic-filter-pretty-hydra+
mic-filter-pretty-hydra uses :pretty-hydra, whereas mic-filter-pretty-hydra+ uses :pretty-hydra+.
Each element is passed to pretty-hydra-define, which defines new hydra, or pretty-hydra-define+, which appends to existing hydra if exist.
The both have absolutely same syntax. Each element is passed to each defining macros directly.
(mic-defmic mic-with-pretty-hydra mic
:filters '(mic-filter-pretty-hydra
mic-filter-pretty-hydra+))
;;; `:pretty-hydra'
(mic-with-pretty-hydra package-name
:pretty-hydra
(( hydra-window-resizer ()
("Alphabet"
(("p" shrink-window "shrink")
("n" enlarge-window "enlarge")
("f" enlarge-window-horizontally "enlarge-horizontally")
("b" shrink-window-horizontally "shrink-horizontally"))
"Arrow"
(("<down>" shrink-window)
("<up>" enlarge-window)
("<right>" enlarge-window-horizontally)
("<left>" shrink-window-horizontally))
"Quit"
("q" nil "quit")))))
;; Expanded to:
(mic package-name
:eval
((pretty-hydra-define hydra-window-resizer nil
("Alphabet"
(("p" shrink-window "shrink")
("n" enlarge-window "enlarge")
("f" enlarge-window-horizontally "enlarge-horizontally")
("b" shrink-window-horizontally "shrink-horizontally"))
"Arrow"
(("<down>" shrink-window "shrink-window")
("<up>" enlarge-window "enlarge-window")
("<right>" enlarge-window-horizontally "enlarge-window-horizontally")
("<left>" shrink-window-horizontally "shrink-window-horizontally"))
"Quit"
("q" nil "quit")))))
;;; `:pretty-hydra+'
(mic-with-pretty-hydra package-name
:pretty-hydra+
(( hydra-window-resizer ()
("Vim-like"
(("h" enlarge-window-horizontally "enlarge-horizontally")
("j" shrink-window "shrink")
("k" enlarge-window "enlarge")
("l" shrink-window-horizontally "shrink-horizontally"))))))
;; Expanded to:
(mic package-name
:eval
((pretty-hydra-define+ hydra-window-resizer nil
("Vim-like"
(("h" enlarge-window-horizontally "enlarge-horizontally")
("j" shrink-window "shrink")
("k" enlarge-window "enlarge")
("l" shrink-window-horizontally "shrink-horizontally"))))))Major Mode Hydra defines major-mode specific hydra function, major-mode-hydra.
There is a filter for integration of it:
mic-filter-mode-hydra
mic-filter-mode-hydra uses a :mode-hydra keyword.
Each element of the value of the keyword is passed to major-mode-hydra-define directly.
(mic-defmic mic-with-mode-hydra mic
:filters '(mic-filter-mode-hydra
mic-filter-mode-hydra+))
;;; `:mode-hydra'
(mic-with-mode-hydra package-name
:mode-hydra
(( c-mode (:title "C Mode" :quit-key "q")
("Alphabet"
(("p" shrink-window "shrink")
("n" enlarge-window "enlarge")
("f" enlarge-window-horizontally "enlarge-horizontally")
("b" shrink-window-horizontally "shrink-horizontally"))
"Arrow"
(("<down>" shrink-window)
("<up>" enlarge-window)
("<right>" enlarge-window-horizontally)
("<left>" shrink-window-horizontally))))))
;; Expanded to:
(mic package-name
:eval
((major-mode-hydra-define c-mode
(:title "C Mode" :quit-key "q")
("Alphabet"
(("p" shrink-window "shrink")
("n" enlarge-window "enlarge")
("f" enlarge-window-horizontally "enlarge-horizontally")
("b" shrink-window-horizontally "shrink-horizontally"))
"Arrow"
(("<down>" shrink-window "shrink-window")
("<up>" enlarge-window "enlarge-window")
("<right>" enlarge-window-horizontally "enlarge-window-horizontally")
("<left>" shrink-window-horizontally "shrink-window-horizontally"))))))
;;; `:mode-hydra+'
(mic-with-mode-hydra package-name
:mode-hydra+
(( c-mode (:title "C Mode" :quit-key "q")
("Alphabet"
(("p" shrink-window "shrink")
("n" enlarge-window "enlarge")
("f" enlarge-window-horizontally "enlarge-horizontally")
("b" shrink-window-horizontally "shrink-horizontally"))
"Arrow"
(("<down>" shrink-window)
("<up>" enlarge-window)
("<right>" enlarge-window-horizontally)
("<left>" shrink-window-horizontally))))))
;; Expanded to:
(mic package-name :eval
((major-mode-hydra-define+ c-mode
(:title "C Mode" :quit-key "q" :hint nil :color teal :separator "═")
("Alphabet"
(("p" shrink-window "shrink")
("n" enlarge-window "enlarge")
("f" enlarge-window-horizontally "enlarge-horizontally")
("b" shrink-window-horizontally "shrink-horizontally"))
"Arrow"
(("<down>" shrink-window "shrink-window")
("<up>" enlarge-window "enlarge-window")
("<right>" enlarge-window-horizontally "enlarge-window-horizontally")
("<left>" shrink-window-horizontally "shrink-window-horizontally"))))))mic-filter-hook-list-
This is almost same as
mic-filter-hook, butcarof each element of the value should be list of hook, and thecdrshould be list of function (should be quoted).:hook-listis used as keyword. mic-filter-hook-list-maybe-
This is almost same as
mic-filter-hook, butcarof each element of the value should be list of hook or just one hook, and thecdrshould be list of function or just one function (should NOT be quoted).:hook-list-maybeis used as keyword. mic-filter-hook-quote-
This is almost same as
mic-filter-hook, butcdrof each element of the value should not be quoted.:hook-quoteis used as keyword.
There are some helpers for defining a filter.
Usually, a filter proceeds filtering by 4 steps:
- Get data on a specific keyword in
PLIST - Convert data to sexp
- Append the sexp to value on
:evalinPLIST - Delete the specific keyword from
PLIST
There are some macros to help step 3. and 4. in mic-utils.el.
mic-plist-put-append, which helps step 3., takes three arguments,PLIST,PROP, which means keyword, andVAL. It get a value onPROPinPLIST, and appendsVALto the value.mic-plist-delete, which helps step 4., takes one obligatory argumentPLIST, and extra argumentsPROPS. It removesPROPSkeywords fromPLISTand return it.
To define a simple filter or to modify an existing filter,
you can use mic-deffilter-* macros in mic-deffilter.el.
See each macro definition and docstring for more information.
mic-deffilter-alias- Induce alias keyword.
(mic-deffilter-alias example-filter-alias :alias :origin) (example-filter-alias '(:alias "Hello")) ;; => (:origin "Hello")
mic-deffilter-const- Put constant value on keyword.
(mic-deffilter-const example-filter-const "Optional docstring." :eval '((message "Hello"))) ;; Add a :eval keyword when it does not exist. (example-filter-const '(:other-keyword "Hi")) ;; => (:other-keyword "Hi" :eval ((message "Hello"))) ;; Overwrite when a :eval keyword exists. (example-filter-const '(:eval ((message "Good bye")) :other-keyword "Hi")) ;; => (:eval ((message "Hello")) :other-keyword "Hi")
mic-deffilter-const-append- Append constant value on keyword.
(mic-deffilter-const-append example-filter-const-append :eval '((message "Hello"))) ;; Same as `mic-deffilter-const' when any :eval keyword does not exist. (example-filter-const-append '(:other-keyword "Hi")) ;; => (:other-keyword "Hi" :eval ((message "Hello"))) ;; Append the value when the a :eval keyword exists. (example-filter-const-append '(:eval ((message "Good bye")) :other-keyword "Hi")) ;; => (:eval ((message "Good bye") (message "Hello")) :other-keyword "Hi")
mic-deffilter-ignore- Just remove value on keyword.
(mic-deffilter-ignore example-filter-ignore :ignore-me) (example-filter-ignore '(:ignore-me "Ignored" :remain-me "Remained")) ;; => (:remain-me "Remained")
mic-deffilter-nonlist-to-list- If value is not list, wrap it into list.
(mic-deffilter-nonlist-to-list example-filter-nonlist-to-list :package) (example-filter-nonlist-to-list '(:package t)) ;; => (:package (t))
mic-deffilter-replace-keyword-append- From an existing filter, define a new filter which uses another keywords as input and output.
Value is appended to the keyword for output.
;; Original filter: `mic-filter-mykie' (mic-filter-mykie '(:mykie ((global-map ("C-a" :default beginning-of-line))))) ;; => (:eval ((mykie:define-key global-map "C-a" :default beginning-of-line))) (mic-deffilter-replace-keyword-append example-filter-replace-keyword-append mic-filter-mykie :mykie-after-load :mykie '((:eval . :eval-after-load))) ;; An input keyword and an output keyword is replaced (example-filter-replace-keyword-append '(:mykie-after-load ((global-map ("C-a" :default beginning-of-line))))) ;; => (:eval-after-load ((mykie:define-key global-map "C-a" :default beginning-of-line)))
mic-deffilter-convert-after-load- From an existing filter, define a new filter which outputs an
:eval-after-loadkeyword instead of:eval. It is same as(mic-deffilter-replace-keyword-append name filter old-keyword new-keyword '((:eval . :eval-after-load))).;; Original filter: `mic-filter-mykie' (mic-filter-mykie '(:mykie ((global-map ("C-a" :default beginning-of-line))))) ;; => (:eval ((mykie:define-key global-map "C-a" :default beginning-of-line))) (mic-deffilter-convert-after-load example-filter-convert-after-load mic-filter-mykie :mykie-after-load :mykie) ;; An input keyword and an output keyword is replaced (example-filter-convert-after-load '(:mykie-after-load ((global-map ("C-a" :default beginning-of-line))))) ;; => (:eval-after-load ((mykie:define-key global-map "C-a" :default beginning-of-line)))
mic-deffilter-t-to-name- Replace
twith feature name in a list keyword.(mic-deffilter-t-to-name example-filter-t-to-name :replace) ;; :name keyword is needed in addition to :replace keyword (example-filter-t-to-name '(:name feature-name :replace (1 2 3 t 5 6 t))) ;; => (:name feature-name :replace (1 2 3 feature-name 5 6 feature-name))
mic-deffilter-validate- Return a recieved plist except that it validates and sieves keyword in the plist
to confirm the returned plist has no invalid keywords.
(mic-deffilter-validate example-filter-validate :name :key1 :key2) (example-filter-validate '(:name feature-name :key1 "Hello" :key2 "Hi" :key3 "Bad" :key4 "Sad")) ;; => (:name feature-name :key1 "Hello" :key2 "Hi") ;; In addition, warnings are displayed like: ;; Warning (emacs): 'mic' feature-name: The keyword :key3 is not allowed by filter 'example-filter-validate' ;; Warning (emacs): 'mic' feature-name: The keyword :key4 is not allowed by filter 'example-filter-validate'
mic-defmic recieves arguments: NAME, PANRENT, optional DOCSTRING, keyword argument FILTERS.
NAME is your new mic macro name. PARENT is parent mic, which recieves RETURNED-PLIST at last.
FILTERS is list of your filters. When your mic recieves plist, the plist is filtered by all of your FILTERS in order,
then the plist is passed to PARENT.
Here is example:
;; Define `mymic'
(mic-defmic mymic
;; Parent is here. You can also use `mic-core'.
mic
:filters
'(my-filter-global-set-key-without-quote
;; You can add other filters below
)
;; You can comment out the line below to catch, warn and ignore errors.
;; :error-protection? t
)
;; Then you can use `mymic' like:
(mymic simple
:bind
(("C-d" . delete-forward-char)
("C-x l" . toggle-truncate-lines))
;; Of course parent keywords are accepted.
:custom
((kill-whole-line . t)
(set-mark-command-repeat-pop . t)
(mark-ring-max . 50)))
;; Expanded to:
(mic simple
:custom
((kill-whole-line . t)
(set-mark-command-repeat-pop . t)
(mark-ring-max . 50))
:eval
((global-set-key (kbd "C-d") #'delete-forward-char)
(global-set-key (kbd "C-x l") #'toggle-truncate-lines)))When you would like to use mic-core as PARENT, mic-filter-core-validate is useful to validate plist.
Please put it tail of =FILTERS= if you use it.
If you want your mic to catch, warn and dismiss errors and to continue evaluation, set :error-protection? t.
(mic-defmic mymic-with-error-protection
;; Parent is here. You can also use `mic-core'.
mic
:filters
'(my-filter-global-set-key-without-quote)
:error-protection? t)
(mymic-with-error-protection simple
:bind
(("C-d" . delete-forward-char)
("C-x l" . toggle-truncate-lines))
;; Of course parent keywords are accepted.
:custom
((kill-whole-line . t)
(set-mark-command-repeat-pop . t)
(mark-ring-max . 50)))
;; Expanded to:
(condition-case-unless-debug error ; Catch error
(mic simple
:custom
((kill-whole-line . t)
(set-mark-command-repeat-pop . t)
(mark-ring-max . 50))
:eval
((global-set-key (kbd "C-d") (function delete-forward-char))
(global-set-key (kbd "C-x l") (function toggle-truncate-lines))))
;; Warn caught error but continue evaluation
(error
(warn "`%s' %s: evaluation error: %s" 'mymic-with-error-protection 'simple
(error-message-string error))))Like use-package and leaf, you can define mic which accepts non-plist input.
If you want to do so, you should pass :inputter argument to mic-defmic.
INPUTTER is a function which takes one argument INPUT, and transform it into PLIST as returned value.
Simply, you can use mic-definputter-pseudo-plist defined in mic-definputter.el
to define inputter like use-package or leaf.
it takes two arguments NAME and LISTIZED-KEYWORDS. NAME is a name of the inputter function,
and LISTIZED-KEYWORDS is list of keyword whose value can be passed multiple times.
(mic-definputter-pseudo-plist my-inputter
'(:eval :eval-after-load :define-key))
(mic-defmic mymic-with-inputter mic
:inputter #'my-inputter)
(mymic-with-inputter feature-name
:eval
;; Like `use-package', you can put multiple sexps after :eval, instead of list of sexp
(message "Hello")
(message "Good bye")
:eval-after-load
(message "Hello, after load")
(message "Good bye, after load")
;; Instead, list of sexp is not allowed
;; :eval-after-load
;; ((message "Hello, after load")
;; (message "Good bye, after load"))
:define-key
(global-map
("M-a" . #'beginning-of-defun))
(esc-map
("e" . #'end-of-defun))
;; Other keyword is not affected by inputter
:package
(ivy hydra))You can use other configuration managers, such as use-package and leaf.el.
However, filters defined by mic output keyword for mic family, such as :eval, :eval-after-load.
So you should tell mic-defmic how to adapt outputs to its parent by :adapter option.
The adapter takes one argument PLIST, and returns a list to pass to the parent.
Two adapter are pre-defined:
mic-adapter-use-package- Adapter for
use-package. mic-adapter-leaf- Adapter for
leaf.
(mic-defmic mic-with-use-package use-package
:filters '(mic-filter-define-key-with-feature)
:adapter #'mic-adapter-use-package)
(mic-with-use-package feature-name
:define-key-with-feature
((org
(org-mode-map
("M-a" . #'feature-name-command))))
;; You can use `use-package' feature
:bind
(("M-a" . beginning-of-defun)
("M-e" . end-of-defun)))
;; Expanded to:
(use-package feature-name
:bind
(("M-a" . beginning-of-defun)
("M-e" . end-of-defun))
;; :defer is needed to wrap :config section around `eval-after-load'
:defer t
:init
(with-eval-after-load 'org
(define-key org-mode-map (kbd "M-a") (function feature-name-command))))When you read here, you should know defmacro.
You can do anything with defmacro. mic-defmic is easy way to define your mic,
but may be not enough for you, because of restriction. Then I RECOMMEND to use =defmacro=.
I am looking forward to seeing your mic defined by defmacro!
They are more easy to use, but sometimes have less expressive ability.
mic is more simple and has more expressive ability, but sometimes more redundant.
It is just your preference.
In addition, they are customizable, while mic is not customizable, but re-definable.
You can define your own mic according to your preference, with mic help.
Of course you can define your own mic with use-package or leaf as backend.
When you think you would like to share your filter or your own mic, use GitHub Discussion.
Of course your mic defined by defmacro. Any issue is welcome.
This package is licensed by GPLv3. See LICENSE.