Консультирование интерактивной функции emacs: до

Я хочу перед советом некоторые функции, которые используют интерактивные аргументы, например find-dired:

(defadvice find-dired (before eab-find-dired activate)
  (message "before!")
  (setq find-args '("-iname '**'" . 10)))

Но Emacs выполняет этот совет только после find-dired интерактивный сеанс, и я не могу настроить find-args до. Как разрешить противоречие?

Upd. Обратите внимание, что defadvice макрос устарел.

5 ответов

Решение

artscan ответил на его собственный вопрос с artscan ответом, но он немного неполон и вводит в заблуждение. Это также включает в себя 'interactive, что само по себе может вводить в заблуждение, поскольку выглядит так, как будто оно определено внутри тела команды, но фактически используется до того, как функция введена, и до выполнения какого-либо совета (если этот совет не был 'interactive звонки...)

В документации для консультаций отсутствует ряд деталей, которые могли бы помочь в этой ситуации, поэтому лучшее место для поиска - это источник: advice.el, Посмотрите на это и найдите раздел комментариев @ Foo games: An advice tutorial, Вы также можете найти исходный код в самом Emacs с помощью Mx find-library advice RET.

В частности, для этой проблемы, посмотрите на раздел в advice.el маркированный @@ Advising interactive behavior: - потому что это именно то, что вы пытаетесь сделать.

Если вы прочитаете его внимательно, вы заметите, что совет не обязательно должен иметь форму around, но может быть before а может и after - хотя это просто напрашивается на неприятности. Это потому что interactive это (и должно быть) лечиться особенным.

Итак, следующий код работает (обратите внимание на before):

(defadvice find-dired (before eab-find-dired (dir args) activate)
  "ignore find-args, hard code \"-iname '**'\""
  (interactive
   (list (read-directory-name "Run find in directory: " nil "" t)
         (read-string "Run find (with args): " '("-iname '**'" . 10)
                      '(find-args-history . 1)))))

Вероятно, более чистый способ сделать это, как предлагали другие, - написать свою собственную функцию, и я думаю, что самый простой ответ Lindydancer.

Совет является довольно заманчивым инструментом, но его легко использовать. Я бы не сказал, что это опасно, но его следует использовать с осторожностью. Кажется, это лучше всего использовать, когда написание вашей собственной функции не работает - например, изменение поведения функции, вызываемой кодом, который вы не можете изменить. Я думаю, что хорошие примеры этой ситуации могут быть найдены здесь, здесь и здесь (чтобы поднять мой собственный рог).

Emacs подхватывает interactive спецификация перед вызовом функции.

В общем, это плохая идея defadviceпоэтому вместо этого я бы предложил вам определить свою собственную функцию и связать ее с соответствующей клавишей. Например:

(defun my-find-dired ()
  (interactive)
  (let ((find-args '("-iname '**'" . 10)))
    (call-interactively 'find-dired)))

Конечно, вы также можете просто сделать следующее, если считаете, что этот параметр необходим для всех вызовов. find-dired:

(setq find-args '("-iname '**'" . 10))

Почему вы хотите посоветовать интерактивную функцию?

Вы можете легко определить свою собственную команду

(defun find-dired-my-defaults (dir args)
  "just like `find-dired' but with defaults."
  (interactive
   (list (read-directory-name "Run find in directory: " nil "" t)
         (read-string "Run find (with args): " '("-iname '**'" . 1)
                      '(find-args-history . 1))))
  (find-dired dir args))

И если он был связан в раскладке ключей, вы можете легко переназначить его:

(define-key foo-mode-map [remap find-dired] 'find-dired-my-defaults)

Конечный пользователь почти никогда не должен использовать defadvice несмотря на то, что вики говорит вам.

РЕДАКТИРОВАТЬ: @ Ответ Lindydancer лучше в этом случае, но я оставлю этот ответ здесь, чтобы отговорить будущих читателей от использования defadvice такими искусственными способами.

Оно работает:

(defadvice find-dired (around eab-find-dired (dir args) activate)
  (interactive
   (list (read-directory-name "Run find in directory: " nil "" t)
         (read-string "Run find (with args): " '("-iname '**'" . 10)
                      '(find-args-history . 1))))
  ad-do-it)

я использую interactive form функции find-dired с подстановкой: поставить нужное выражение '("-iname '**'" . 10) вместо find-args прямо в форме. around-advice с аргументами (dir args) используется вместо before-advice,

Я столкнулся с этой проблемой в поисках простого способа расширить интерактивное поведение eval-last-sexp, describe-function и аналогичные функции без необходимости написания специализированных советов для каждой из них. Для этого я проанализировал стек вызовов интерактивного использования, используя toggle-debug-on-error и фиктивная функция (defun x () (interactive (list :interactiveform (error "Int")))),

  • Интерактивное использование всегда предполагает обращение к call-interactively,
  • Когда команда выполняется нажатием горячей клавиши, команда передается command-execute, который вызывает call-interactively,
  • Когда выполняется как M-x COMMANDимя функции сначала передается в виде строки execute-extended-command (это команда, вызываемая M-x), который затем вызывает command-execute...

В зависимости от того, все ли вызовы interactive форма должна зависеть от совета или только от прямой горячей клавиши или M-x вызов, можно рекомендовать одну из этих функций, например, используя современные nadvice.el интерфейс:

(defun setup-var-advice (oldfun command &rest r)
  (if (eq command 'save-buffer)
      (let ((myvar t)) (apply oldfun command r)) ;; Advice sets up variable
    (apply oldfun command r))) ;; Advice has no effect

(advice-add #'call-interactively :around #'setup-var-advice)

В этом простом случае издержки, связанные с функцией рекомендации, невелики, но для более реалистичных ситуаций, вероятно, важно сохранить проверку того, должен ли совет иметь эффект, настолько эффективный, насколько это возможно.

Другие вопросы по тегам