mic
is 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-core
recieves 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.mic
is defined by some filters from the parentmic-core
. - Define your own mic by
mic-defmic
. It recievesNAME
, optionalDOCSTRING
, and keyword argumentFILTERS
.NAME
is name of your ownmic
.DOCSTRING
is the document string of yours.FILTERS
are 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-load
sexp, 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. PLIST
is given by user or filter before.PLIST
have feature name:name
property.RETURNED-PLIST
is passed to next filter or parentmic
(mic
,mic-core
, or another).RETURNED-PLIST
should have same value of:name
property.- 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-get
mic-filter-straight
mic-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-key
mic-filter-mykie
mic-filter-hydra
mic-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-general
mic-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-hydra
mic-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
, butcar
of each element of the value should be list of hook, and thecdr
should be list of function (should be quoted).:hook-list
is used as keyword. mic-filter-hook-list-maybe
-
This is almost same as
mic-filter-hook
, butcar
of each element of the value should be list of hook or just one hook, and thecdr
should be list of function or just one function (should NOT be quoted).:hook-list-maybe
is used as keyword. mic-filter-hook-quote
-
This is almost same as
mic-filter-hook
, butcdr
of each element of the value should not be quoted.:hook-quote
is 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
:eval
inPLIST
- 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 onPROP
inPLIST
, and appendsVAL
to the value.mic-plist-delete
, which helps step 4., takes one obligatory argumentPLIST
, and extra argumentsPROPS
. It removesPROPS
keywords fromPLIST
and 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-load
keyword 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
t
with 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.