请教,如下功能的elisp函数该怎么定义。

我用Spacemacs来写LaTeX,有时候会经常插入引用,这一般来讲是用reftex这个包来辅助完成的。

Spacemacs里面定义了一个相关的快捷键, r r。但是这之后还要再做一至两次选择:第一次是选哪种引用,比如\ref\pageref\eqref等,有的选完之后还要再选用引用对应的格式或者标准,或者label type。

事实上,经常使用的就非常有限的一两种,完全不需要这种多级的互动选择。。。

我该怎么定义一个新的函数或者快捷键来实现这种功能呢?也就是说,比如定义一个, r e快捷键,作用等同于, r r加选择\eqref再加选择lable type e

不知道我说清楚了没有。:joy:

扩展开来讲,就是说某个函数对应快捷键是K,调用这个函数后,会出现一个互动的选择列表A(这个列表有可能是实时生成/扫描的),选择某一项a后,有可能再出现一个选择列表B,需要再次做出选择b。这样一次到多次之后,最终实现想要的功能。

我的需求是,能不能把上述过程简化为一个函数或者快捷键?比如快捷键XX = K a(A) b(B) c(C) ...

C-h k查看, r e执行的command, 比如是函数f.

接下来就可以:

(defun f1 () 
   (f "\eqref")) 

(evil-leader-set-key "re" 'f1)

这样就可以了.

虽然在代码中可能不是用"\eqref"直接作为参数的,但它肯定有代表它的参数, 道理是一样的.

可惜我没有Spacemacs, 也不用reftex, 要不然可以给你个准确的例子来着.

不然你查一下command告诉我? 我好像有reftex这个包.

, r r绑定的是reftex-reference函数。

我的emacs版本是26.2。看了下,reftex好像是emacs内置的? 这个reftex-reference函数定义在/usr/share/emacs/26.2/lisp/textmodes/reftex-ref.el.gz这个文件里。这个函数的定义很长,我就先不贴出来了。

不过,谢谢你,我会试试你提到的这个办法。

我看了这个函数, 发现按我的方法挺难定义的…

因为我看不懂它参数中的type的类型到底是什么…

这段代码看的我有点头皮发麻…我明天再看看

哈哈,我虽然是个elisp菜鸡,但多少也能看看代码,这个函数我反正是看不懂啦 :fearful:

当当当, 搞定了, 前来填坑.

为了搞定你的问题, 首先要看一下函数reftex-reference的结构:

(defun reftex-reference (&optional type no-insert cut)
  (interactive)

  ;; Check for active recursive edits
  (reftex-check-recursive-edit)

  ;; Ensure access to scanning info and rescan buffer if prefix is '(4)
  (reftex-access-scan-info current-prefix-arg)

  (let ((reftex-refstyle (when (and (boundp 'reftex-refstyle) reftex-refstyle)
		    reftex-refstyle))
	(reftex-format-ref-function reftex-format-ref-function)
	(form "\\ref{%s}")
	label labels sep sep1 style-alist)
    ...)
  ...)

注意其中let binding中的第一个 reftex-refstyle, 就是这个变量控制输入什么格式的ref(比如"\ref"形式还是"\pageref"形式). 这个值在开始的时候是nil, 所以它才会弹出窗口让你选referece的format.

展现技术的时候到了 :sunglasses: : 为了可以以参数的形式配置reftex-refstyle, 你需要定义下面的函数:

(defun my-reftex-reference (&optional my-reftex-refstyle type no-insert cut)
  (interactive)

  ;; Check for active recursive edits
  (reftex-check-recursive-edit)

  ;; Ensure access to scanning info and rescan buffer if prefix is '(4)
  (reftex-access-scan-info current-prefix-arg)

  (let ((reftex-refstyle (when (and (boundp 'reftex-refstyle) reftex-refstyle)
		    reftex-refstyle))
	(reftex-format-ref-function reftex-format-ref-function)
	(form "\\ref{%s}")
	label labels sep sep1 style-alist)
    ;;;;;;;;;;;;;;;;; new command begin ;;;;;;;;;;;;;;;;; 
    (setq reftex-refstyle my-reftex-refstyle)          ;;
    ;;;;;;;;;;;;;;;;  new command end ;;;;;;;;;;;;;;;;;;
    ...)
  ...)

这个函数的实现就是把reftex-reference的实现copy过来, 然后在let开始的时候插入box中的语句.

这样你就可以用my-reftex-reference("\\eqref")来跳过第一次选择了.

现在就可以实现你想要的功能:

(defun my-reftex-reference-ref ()
  (interactive)
  (my-reftex-reference "\\eqref"))

调用my-reftex-reference-ref就会插入"\eqref"形式的ref.

扩展一: 我发现reftex-reference并不会对reftex-refstyle的内容做检查, 所以你就可以这样:

(defun my-reftex-reference-ref ()
  (interactive)
  (my-reftex-reference "\\mamemame"))

然后它就会为你插入\mamemame{…} (火星ref :alien:)

所以, 这里你实际上可以传入任意的string!

扩展二: label type也是可指定参数的, 也就是参数中的type, 它的值是你在弹窗中输入的字母, 比如"f"代表figure lable.

所以, 如果你想避免第二次选择, 可以这样定义:

(defun my-reftex-reference-ref ()
  (interactive)
  (my-reftex-reference "\\eqref" "f"))

你还可以这样, 只跳过第二次选择:

(defun my-reftex-reference-ref ()
  (interactive)
  (my-reftex-reference nil "f"))

附录: 全部代码(虽然应该用不着, 我前面应该说清楚了吧 :sweat:):

(defun my-reftex-reference (&optional my-reftex-refstyle type no-insert cut)
  "Fix version of `reftex-reference', more transparent."

  (interactive)

  ;; Check for active recursive edits
  (reftex-check-recursive-edit)

  ;; Ensure access to scanning info and rescan buffer if prefix is '(4)
  (reftex-access-scan-info current-prefix-arg)
  (let ((reftex-refstyle (when (and (boundp 'reftex-refstyle) reftex-refstyle)
		    reftex-refstyle))
	(reftex-format-ref-function reftex-format-ref-function)
	(form "\\ref{%s}")
	label labels sep sep1 style-alist)

    ;;;;;;;;;;;;;;;;; new command begin ;;;;;;;;;;;;;;;;; 
    (setq reftex-refstyle my-reftex-refstyle)          ;;
    ;;;;;;;;;;;;;;;;  new command end ;;;;;;;;;;;;;;;;;;
    (unless reftex-refstyle
      (if reftex-ref-macro-prompt
	  (progn
            
	    ;; Build a temporary list which handles more easily.
	    (dolist (elt reftex-ref-style-alist)
	      (when (member (car elt) (reftex-ref-style-list))
		(mapc (lambda (x)
			(add-to-list 'style-alist (cons (cadr x) (car x)) t))
		      (nth 2 elt))))
	    ;; Prompt the user for the macro.
	    (let ((key (reftex-select-with-char
			"" (concat "SELECT A REFERENCE FORMAT\n\n"
				   (mapconcat
				    (lambda (x)
				      (format "[%c] %s  %s" (car x)
					      (if (> (car x) 31) " " "")
					      (cdr x)))
				    style-alist "\n")))))
	      (setq reftex-refstyle (cdr (assoc key style-alist)))
	      (unless reftex-refstyle
		(error "No reference macro associated with key `%c'" key))))
	;; Get the first macro from `reftex-ref-style-alist' which
	;; matches the first entry in the list of active styles.
	(setq reftex-refstyle
	      (or (caar (nth 2 (assoc (car (reftex-ref-style-list))
				      reftex-ref-style-alist)))
		  ;; Use the first entry in r-r-s-a as a last resort.
		  (caar (nth 2 (car reftex-ref-style-alist)))))))

    (unless type
      ;; Guess type from context
      (if (and reftex-guess-label-type
	       (setq type (reftex-guess-label-type)))
	  (setq cut (cdr type)
		type (car type))
	(setq type (reftex-query-label-type))))

    ;; Have the user select a label
    (set-marker reftex-select-return-marker (point))
    (setq labels (save-excursion
                   (reftex-offer-label-menu type)))
    (reftex-ensure-compiled-variables)
    (set-marker reftex-select-return-marker nil)
    ;; If the first entry is the symbol 'concat, concat all labels.
    ;; We keep the cdr of the first label for typekey etc information.
    (if (eq (car labels) 'concat)
        (setq labels (list (list (mapconcat 'car (cdr labels) ",")
                                 (cdr (nth 1 labels))))))
    (setq type (nth 1 (car labels))
          form (or (cdr (assoc type reftex-typekey-to-format-alist))
                   form))

    (cond
     (no-insert
      ;; Just return the first label
      (car (car labels)))
     ((null labels)
      (message "Quit")
      nil)
     (t
      (while labels
        (setq label (car (car labels))
              sep (nth 2 (car labels))
              sep1 (cdr (assoc sep reftex-multiref-punctuation))
              labels (cdr labels))
        (when cut
          (backward-delete-char cut)
          (setq cut nil))

        ;; remove ~ if we do already have a space
        (when (and (= ?~ (string-to-char form))
                   (member (preceding-char) '(?\ ?\t ?\n ?~)))
          (setq form (substring form 1)))
        ;; do we have a special format?
	(unless (string= reftex-refstyle "\\ref")
	  (setq reftex-format-ref-function 'reftex-format-special))
        ;; ok, insert the reference
        (if sep1 (insert sep1))
        (insert
         (if reftex-format-ref-function
             (funcall reftex-format-ref-function label form reftex-refstyle)
           (format form label label)))
        ;; take out the initial ~ for good
        (and (= ?~ (string-to-char form))
             (setq form (substring form 1))))
      (message "")
      label))))

(defun my-reftex-reference-ref ()
  (interactive)
  (my-reftex-reference "\\eqref"))

(evil-leader/set-key
  "ii" 'my-reftex-reference-ref)
3 个赞

哈哈,太感谢了。真的帮了大忙了! :kissing_heart:

我现在电脑不在手边,没自己测试。不过我仔细看了你的教程,应该是完全没问题的。再次感谢!

测试成功了,非常好用,感谢!!!:hugs: