Советуем интерактивную функцию 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 - это устарело.


person artscan    schedule 30.01.2013    source источник
comment
Для новых читателей обратите внимание, что defadvice устарел, и теперь вы должны использовать add-function, add-advice (и их сопутствующие функции)   -  person ocodo    schedule 07.11.2015


Ответы (5)


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

В документации для советов отсутствует ряд подробности, которые могут помочь в этой ситуации, поэтому лучше всего искать источник: _ 3_. Посмотрите на это и найдите раздел комментариев @ Foo games: An advice tutorial. Вы также можете найти исходный код в самом Emacs с помощью M-x 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 ' s ответ.

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

person Trey Jackson    schedule 30.01.2013
comment
Спасибо за поучительный и точный ответ! - person artscan; 30.01.2013
comment
defadvice опасен, потому что он слишком часто используется в вики. Новички могут легко подумать, что это единственный способ настроить свои Emacs и в конечном итоге коренным образом изменить Emacs API, что затрудняет отладку ошибок, которые нужно сообщать разработчикам пакетов. Это напрасная трата времени. - person event_jr; 31.01.2013
comment
Для новых читателей обратите внимание, что defadvice устарел, и теперь вы должны использовать add-function, add-advice (и их сопутствующие функции) - person ocodo; 07.11.2015

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

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

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

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

(setq find-args '("-iname '**'" . 10))
person Lindydancer    schedule 30.01.2013
comment
Спасибо за ответ, согласен с таким примером. Но я хотел обсудить что-то вроде модификации интерактивной формы: см. Мой ответ. - person artscan; 30.01.2013
comment
Привязка @Lindydancer препятствует тому, чтобы find-dired сохранял последнее использованное значение в find-args. Вы, вероятно, захотите использовать вместо этого setq. - person event_jr; 30.01.2013
comment
Ах да. Что ж, тогда можно было бы спорить, стоит ли его вообще модифицировать. Может быть, лучше установить его только с помощью setq или определить что-то вроде my-new-find-dired, которое восстанавливает его перед вызовом find-dired. В любом случае нет простого решения, поскольку все начинает зависеть от предпочтений пользователя - хотите ли вы иметь последнее введенное выражение или гарантировать, что выражение всегда восстанавливается. - person Lindydancer; 30.01.2013

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

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

(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 такими надуманными способами.

person event_jr    schedule 30.01.2013
comment
Спасибо за ответ. Если у меня много раскладок, мне нужно переназначить каждую из них. Согласен: в 99% случаев такого решения достаточно. - person artscan; 30.01.2013

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

(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.

person artscan    schedule 30.01.2013
comment
Я уже довольно давно использую Emacs. Это научило меня не копировать и вставлять ненужные вещи, так как это имеет тенденцию ломаться при изменении Emacs. - person Lindydancer; 30.01.2013
comment
defadvice опасно, даже если не в этом случае; это все еще опасная привычка. Не используйте его, если в этом нет крайней необходимости. - person event_jr; 30.01.2013
comment
В необходимых случаях использую несколько функций с советами. Например. внутренние функции, такие как ac-inline-show, которые вызываются внутри других функций: их имена уже связаны. - person artscan; 30.01.2013

Я столкнулся с этой проблемой, ища простой способ расширить интерактивное поведение 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)

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

person kdb    schedule 29.02.2016