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:

  1. First call the modified method of :around
  2. Since the above method is called call-next-method , so recall :before modification method;
  3. Call the unmodified method (this is called the primary method in CL);
  4. Then call the modified method :after
  5. Finally, it returns to the position where :around is called in call-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 the primary method;
  • If there are :after-while modified by 060c752e4cd362, and the return value of the primary 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

read the original text


用户bPGfS
169 声望3.7k 粉丝