从Emacs中的挂钩中删除特定lambda的函数

从Emacs中的挂钩中删除特定lambda的函数,emacs,lambda,elisp,hook,Emacs,Lambda,Elisp,Hook,我找到此宏,用于运行特定项目路径的代码: (defmacro project-specifics (name &rest body) `(progn (add-hook 'find-file-hook (lambda () (when (string-match-p ,name (buffer-file-name)) ,@body))) (add-hook 'dired-af

我找到此宏,用于运行特定项目路径的代码:

(defmacro project-specifics (name &rest body)
  `(progn
     (add-hook 'find-file-hook
             (lambda ()
               (when (string-match-p ,name (buffer-file-name))
                 ,@body)))
     (add-hook 'dired-after-readin-hook
             (lambda ()
               (when (string-match-p ,name (dired-current-directory))
                 ,@body)))))
我用它:

(project-specifics "projects/test"
  (message "z"))
我正在进行修改,将prevoius lambda从钩子中移除,到目前为止,我已经有了帮助函数

(defun remove-lambda-helper (list matcher)
  (dolist (item list)
    (if (and (listp item) (eq (car item) 'lambda))
        (when (funcall matcher item)
          (message "found")
          (setq list (delete item list))))))

(defun remove-hook-name-lambda (name hook)
  (remove-lambda-helper hook
                        (lambda (body)
                          (equal (cadr (cadr (caddr body))) name))))
但当我打电话时:

(remove-hook-name-lambda "projects/test" find-file-hook)
found显示在
*Messages*
缓冲区中,但lambda未被删除。这里怎么了?

问题出在哪里 原因可能是找到的对象是
列表中的第一个对象,在这种情况下
(删除项列表)
返回
(cdr列表)
,而不是修改其结构以保留标识

这里重要的一点是,
delete
不能保证

(eq x (delete item x))
==> t
e、 g.当
x
的唯一元素时,
delete
nil
返回到原始
cons

解决方案 解决方案是通过替换来
删除lambda helper
返回列表的新值

(dolist (item list) ...)

并在
删除钩子名称lambda
中使用它,如:

最后一句话 将lambda添加到挂钩中不是一个很好的主意,尤其是如果您想稍后删除它们。请注意,如果您碰巧编译了代码,lambda删除测试将失败

如果修改lambda,例如,如果

(add-hook 'my-hook (lambda () ...))
然后修改
lambda
并再次计算
addhook
表单,最后将在钩子中得到twolambda

更好的解决方案是使用
defun
定义函数:

(defmacro add-project-specifics (name &rest body)
  (let ((ffh (intern (concat name "-find-file-hook")))
        (darh (intern (concat name "-dired-after-readin-hook"))))
    `(progn
       (defun ,ffh ()
         (when (string-match-p ,name (buffer-file-name))
           ,@body))
       (add-hook 'find-file-hook ',ffh)
       (defun ,darh ()
         (when (string-match-p ,name (dired-current-directory))
           ,@body))
       (add-hook 'dired-after-readin-hook ',darh))))

(defmacro remove-project-specifics (name)
  (let ((ffh (intern (concat name "-find-file-hook")))
        (darh (intern (concat name "-dired-after-readin-hook"))))
    `(progn
       (remove-hook 'find-file-hook ',ffh)
       (unintern ',ffh nil)
       (remove-hook 'dired-after-readin-hook ',darh)
       (unintern ',darh nil))))
附言 回应你在评论中表达的担忧,符号中的特殊字符是可以的,只要你在与读者打交道时引用它们;由于您不打算这样做-您将只使用
添加项目细节
删除项目细节
-您应该可以对它们进行实习

解决实际问题的最佳方案
使用
.dir locals.el

将lambda添加到钩子中,并添加到您自己的按名称键入的哈希表中。然后,当需要删除它时,在哈希表中查找lambda并从钩子中删除它

关于Elisp哈希表:


顺便说一句,词法绑定t时(eq(car item)'lambda)将失败。

我想提出一种重构,允许您定义一次挂钩,然后让它仅在满足某些条件时运行实际操作

(defvar project-specific-name-regex "projects/test"
  "*Regex to match on buffer name in order to trigger `project-specific-fun'.")
(defvar project-specific-fun (lambda () (message "z"))
  "*Lambda form to run when `project-specific-name-regex' triggers.")

(defsubst project-specific-trigger-maybe (name)
  (and project-specific-name-regex
       (stringp project-specific-name-regex)
       (string-match-p project-specific-name-regex name)
       project-specific-fun
       (funcall project-specific-fun) ) )
(defun project-specific-find-file ()
  "Hook run from `find-file-hook' to run `project-specific-fun' if it's set
and the buffer name matches `project-specific-name-regex'."
  (project-specific-trigger-maybe (buffer-file-name)) )
(defun project-specific-dired-after-readin ()
  "Hook run from `dired-after-readin-hook' to run `project-specific-fun'
if it's set and the directory name matches `project-specific-name-regex'."
  (project-specific-trigger-maybe (dired-current-directory)) )

(add-hook 'find-file-hook #'project-specific-find-file)
(add-hook 'dired-after-readin-hook #'project-specific-dired-after-readin)

因此,只需取消设置
特定于项目的名称
和/或
特定于项目的乐趣
,即可禁用钩子中的操作。

如果是
lambda
,则明智的做法是使用
defun
。然后删除它是很简单的(特别是如果你可能想在两次运行之间编辑正文。然后如果你使用
lambda
,你如何找到旧定义或新定义的实例?等等)。@triple的答案是最好的-s?他应该将其作为答案发布,你应该接受它(IMHO)。您不应该随意添加额外的函数来完成您要做的事情(删除特定的lambda表单)。帮你自己一个忙,给那些匿名函数取个名字。这是为什么要命名函数的一个主要例子。当然,如果您无法控制将此类匿名函数添加到钩子中,那么您就不走运了。但通常可以避免这样做。@Drew我需要想出一种方法,基于unix路径定义2个函数名,这可能会中断(比如路径有空格或
),我不认为在宏中创建具有名称的自定义函数,然后将该名称分配给挂钩是个好主意,你有兰博达斯,我明白了。你没有提到这些。不知道为什么您需要基于路径或其他什么的名称,但是如果您这样做了,您就这样做了。@Drew查看
项目细节
宏,它会基于路径添加钩子。如果您想添加函数而不是lambda,您需要以某种方式命名该函数,识别该函数的唯一方法是名称参数(即路径),因此您需要将该路径转换为函数名。但是如果我想有几个项目,该怎么办。我认为代码需要将它们保存在列表或散列中,这样才能工作;你会如何用你所拥有的来实现这一点?是的,用一个alist扩展它会使它更加通用,而不会使它复杂化。如果您对一个操作感到满意,但需要许多项目,
string-match-p
将正则表达式作为其第一个参数。我的文档串有点乱。因此,您可以将
“projects/test\\\\ foo/bar”
放在两个项目中同时触发。现在修复了docstring,并进行了一些重构以减少重复代码。大+1用于提及
.dir locals.el
。令人惊讶的是,现在这是多么少,有多少人痛苦地实现他们自己的黑客项目特定的变量。
(defmacro add-project-specifics (name &rest body)
  (let ((ffh (intern (concat name "-find-file-hook")))
        (darh (intern (concat name "-dired-after-readin-hook"))))
    `(progn
       (defun ,ffh ()
         (when (string-match-p ,name (buffer-file-name))
           ,@body))
       (add-hook 'find-file-hook ',ffh)
       (defun ,darh ()
         (when (string-match-p ,name (dired-current-directory))
           ,@body))
       (add-hook 'dired-after-readin-hook ',darh))))

(defmacro remove-project-specifics (name)
  (let ((ffh (intern (concat name "-find-file-hook")))
        (darh (intern (concat name "-dired-after-readin-hook"))))
    `(progn
       (remove-hook 'find-file-hook ',ffh)
       (unintern ',ffh nil)
       (remove-hook 'dired-after-readin-hook ',darh)
       (unintern ',darh nil))))
(defvar project-specific-name-regex "projects/test"
  "*Regex to match on buffer name in order to trigger `project-specific-fun'.")
(defvar project-specific-fun (lambda () (message "z"))
  "*Lambda form to run when `project-specific-name-regex' triggers.")

(defsubst project-specific-trigger-maybe (name)
  (and project-specific-name-regex
       (stringp project-specific-name-regex)
       (string-match-p project-specific-name-regex name)
       project-specific-fun
       (funcall project-specific-fun) ) )
(defun project-specific-find-file ()
  "Hook run from `find-file-hook' to run `project-specific-fun' if it's set
and the buffer name matches `project-specific-name-regex'."
  (project-specific-trigger-maybe (buffer-file-name)) )
(defun project-specific-dired-after-readin ()
  "Hook run from `dired-after-readin-hook' to run `project-specific-fun'
if it's set and the directory name matches `project-specific-name-regex'."
  (project-specific-trigger-maybe (dired-current-directory)) )

(add-hook 'find-file-hook #'project-specific-find-file)
(add-hook 'dired-after-readin-hook #'project-specific-dired-after-readin)