The "Practical Elisp" series aims to describe my experience in customizing Emacs with Elisp, and to give a good idea, and I would like to ask the majority of Emacs colleagues to let me know-if there are really a large number of Emacs users, hahaha.
Emacs' org-mode uses a markup language called Org. Like most markup languages, it also supports unordered lists and checklists-the former is -
with 060c752e4cca77 (a hyphen, a space), and the latter - [ ]
who use 060c752e4cca7c or - [x]
as the prefix (one more x
than the unordered list)
In addition, org-mode also provides the shortcut key M-RET
to quickly insert a new line for editing these two lists (ie, hold down the alt
key and press the Enter key). If the cursor is in the unordered list, the new line will automatically insert the -
prefix. Unfortunately, if the cursor is in the checklist, the new line does not automatically insert a square bracket
[ ]
quite tedious to manually type 060c752e4ccb19 every time. Fortunately, this is Emacs, which is extensible and customizable. Just type a few lines of code and you can let Emacs enter the square brackets for you.
AOP features of Emacs- advice-add
With the help of Emacs's describe-key
function, you can know that when you press M-RET
org-mode
file, Emacs will call the function org-insert-item
. If you want M-RET
achieve the effect of automatically appending square brackets, you can immediately think of a simple and rude way:
- Define a new function and
M-RET
to it; - Redefine the
org-insert-item
function to add square brackets;
Regardless of the above, it is necessary to re-implement the existing functions of inserting hyphens and space prefixes. There is a more gentle way to extend its behavior on the basis of the org-insert-item
advice
feature of Emacs.
advice
is a kind of aspect-oriented programming paradigm. Using Emacs's advice-add
function, you can do something before or after an ordinary function is called-such as adding a square bracket. For these two occasions, respectively, can be directly used advice-add
of :before
and :after
to achieve, but used here are inappropriate because:
- Check whether it is in the checklist, it needs to be
org-insert-item
before calling 060c752e4ccce5; - To append a pair of
org-insert-item
brackets, you need to do it after 060c752e4ccd1f.
Therefore, the correct approach is to use :around
to modify the original org-insert-item
function
(cl-defun lt-around-org-insert-item (oldfunction &rest args)
"在调用了org-insert-item后识时务地追加 [ ]这样的内容。"
(let ((is-checkbox nil)
(line (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
;; 检查当前行是否为checkbox
(when (string-match-p "- \\[.\\]" line)
(setf is-checkbox t))
;; 继续使用原来的org-insert-item插入文本
(apply oldfunction args)
;; 决定要不要追加“ [ ]”字符串
(when is-checkbox
(insert "[ ] "))))
(advice-add 'org-insert-item :around #'lt-around-org-insert-item)
Now, M-RET
the checklist equally
Common Lisp's method combination
advice-add
's :after
, :around
, and :before
have the exact equivalent of the same name in Common Lisp, but instead of using a advice-add
, it is fed to a macro defmethod
For example, use defmethod
to define a polymorphic len
function, which performs different logic for different types of input parameters
(defgeneric len (x))
(defmethod len ((x string))
(length x))
(defmethod len ((x hash-table))
(hash-table-count x))
:after
, :around
, and :before
modified methods for the specialized version where the parameter type is a string
(defmethod len :after ((x string))
(format t "after len~%"))
(defmethod len :around ((x string))
(format t "around中调用len前~%")
(prog1
(call-next-method)
(format t "around中调用len后~%")))
(defmethod len :before ((x string))
(format t "before len~%"))
The calling rules of this series of methods are:
- First call the modified method of
:around
- Since the above method is called
call-next-method
, so recall:before
modification method; - Call the unmodified method (this is called the
primary
method in CL); - Then call the modified method
:after
- Finally, it returns to the position where
:around
is called incall-next-method
At first glance, Emacs advice-add
supports many more modifiers, but it is not. In the CL, :after
, :around
, and :before
belong to the same named standard
of method combination
, CL and also built other method combination
. In "Other method combinations" section, the author demonstrated the examples progn
and list
If you want to simulate advice-add
, then you must define a new method combination
.
Programmable programming language- define-method-combination
I used to think that defmethod
can only accept :after
, :around
, and :before
. I think these three modifiers must be supported at the language level. Until one day I broke into LispWorks' define-method-combination entry, only to find that they are just three ordinary modifiers.
(define-method-combination standard ()
((around (:around))
(before (:before))
(primary () :required t)
(after (:after)))
(flet ((call-methods (methods)
(mapcar #'(lambda (method)
`(call-method ,method))
methods)))
(let ((form (if (or before after (rest primary))
`(multiple-value-prog1
(progn ,@(call-methods before)
(call-method ,(first primary)
,(rest primary)))
,@(call-methods (reverse after)))
`(call-method ,(first primary)))))
(if around
`(call-method ,(first around)
(,@(rest around)
(make-method ,form)))
form))))
Adhering to the principle of "the persimmon should be soft, let me try to simulate the advice-add
's :after-while
and :before-while
.
:after-while
and :before-while
are still easy to understand
Call function after the old function and only if the old function returned non-
nil
.Call function before the old function and don’t call the old function if function returns
nil
.
Therefore, in define-method-combination
generated by form
(I still remember that Umbrella translated it into a form in "PCL"), it is necessary:
- Check whether there is a method modified
:before-while
- If so, check whether the return value after
:before-while
NIL
; - If not, or if the return value of the method modified
:before-while
NIL
, call theprimary
method; - If there are
:after-while
modified by 060c752e4cd362, and the return value of theprimary
NIL
, call these methods; - Return the return value of the
primary
For the sake of simplicity, although the after-while
and before-while
variables point to multiple "callable" methods, only the "most specific" one is called here.
Name this new method combination
emacs-advice
, and its specific implementation is a matter of course
(define-method-combination emacs-advice ()
((after-while (:after-while))
(before-while (:before-while))
(primary () :required t))
(let ((after-while-fn (first after-while))
(before-while-fn (first before-while))
(result (gensym)))
`(let ((,result (when ,before-while-fn
(call-method ,before-while-fn))))
(when (or (null ,before-while-fn)
,result)
(let ((,result (call-method ,(first primary))))
(when (and ,result ,after-while-fn)
(call-method ,after-while-fn))
,result)))))
call-method
(and its partner make-method
) is a macro specifically used to call the incoming method define-method-combination
Use a series of foobar
methods to verify
(defgeneric foobar (x)
(:method-combination emacs-advice))
(defmethod foobar (x)
'hello)
(defmethod foobar :after-while (x)
(declare (ignorable x))
(format t "for side effect~%"))
(defmethod foobar :before-while (x)
(evenp x))
(foobar 1) ;; 返回NIL
(foobar 2) ;; 打印“fo side effect”,并返回HELLO
postscript
Although I appreciate CL, the more I think about define-method-combination
, the more I will find that the ability of the programming language is , unless the programming language . For example :filter-args
and :filter-return
supported by advice-add
cannot be define-method-combination
elegantly with 060c752e4cd4ea-not completely impossible, but they need to be incorporated into the method modified :around
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。