Emacs/Emacs Lisp: can I insert advice before inter

2019-04-28 18:20发布

问题:

What I'd like to do is intelligently pre-set a buffer-local default value for the string argument to the compile function.

Right now compile.el defaults to using "make" as the command. I can set this by setting compile-command. I can even make that variable buffer-local. That works if I want the same static value, always.

But I'd like to intelligently select the compile-command depending on the contents of the buffer, the name of the buffer, the contents of the containing directory of the file (if any), and the phase of the moon. Basically I want control over the default value, and then allow the interactive user to override that pre-set value.

I was hoping to do this with before-advice. But this isn't working as I expected.

Reading the advice.el file, I see

Suppose a function/macro/subr/special-form has N pieces of before advice, M pieces of around advice and K pieces of after advice. Assuming none of the advices is protected, its advised definition will look like this (body-form indices correspond to the position of the respective advice in that advice class):

([macro] lambda <arglist>
   [ [<advised-docstring>] [(interactive ...)] ]
   (let (ad-return-value)
 {<before-0-body-form>}*
       ....
 {<before-N-1-body-form>}*
 {<around-0-body-form>}*
    {<around-1-body-form>}*
      ....
       {<around-M-1-body-form>}*
      (setq ad-return-value
        <apply original definition to <arglist>>)
       {<other-around-M-1-body-form>}*
      ....
    {<other-around-1-body-form>}*
 {<other-around-0-body-form>}*
 {<after-0-body-form>}*
       ....
 {<after-K-1-body-form>}*
 ad-return-value))

What this says to me is that when the advised function is interactive, `call-interactively' invokes the interactive form before invoking the before advice, or any advice.

And, when I add advice to compile, the behavior I observe confirms this. The advice gets invoked after the interactive form is processed. The interactive form suggests the string to use for compilation, before my advice gets a chance to guess at what it should be, and to pre-set it.

So...

  • how can I get my code to run before the interactive form? can advice do this? If not advice, something else? or
  • how can I dynamically pre-set compile-command for any buffer?

Ideas appreciated.

回答1:

One option is to set the variable compile-command in a mode hook, something like

(add-hook 'c++-mode-hook 'my-c++-set-compile-command)
(defun my-c++-set-compile-command ()
   (setq (make-local-variable 'compile-command) (format "gmake %s" (buffer-file-name))))

I've sometimes added specialized commands to tweak the current compile line (turn on/off debug flags, optimization flags, etc.), and then bind those commands to convenient keystrokes in the mini-buffer.

Regarding adding advice before the interactive form, you need to make advice (either before or around) which has an interactive form that you want. From the advice.el library, an example:

;;(defadvice switch-to-buffer (around confirm-non-existing-buffers activate)
;;  "Switch to non-existing buffers only upon confirmation."
;;  (interactive "BSwitch to buffer: ")
;;  (if (or (get-buffer (ad-get-arg 0))
;;          (y-or-n-p (format "`%s' does not exist, create? " (ad-get-arg 0))))
;;      ad-do-it))


回答2:

compile-command doesn't have to be a string. The compile function evals it, so it can be a function that returns a string that's specific to the buffer or that depends on the time of day, etc:

(setq compile-command (lambda () (if (eq phase-of-moon 'waning) 
                                     "make -DWANING=1" 
                                    "make -DWANING=0")))

Also, though not probably not useful for your specific needs, you can always define compile-command in a file variable section:

/* -*- compile-command: "make -DFOO"; -*- */

or

// Local Variables:
// compile-command: "make -DSOMETHING_SPECIAL"
// End:

compile-command is actually used as an example of a file variable in the manual.



回答3:

Ahh, you know what I did? I used an oblique strategy.

I have globally set C-xC-e to compile. Instead of using advice, I defined a function that wraps compile, and then bound C-xC-e to that. Within the wrapper, I make the guess for the compile command.

(defun cheeso-invoke-compile-interactively ()
  "fn to wrap the `compile' function.  This simply
checks to see if `compile-command' has been previously guessed, and
if not, invokes `cheeso-guess-compile-command' to set the value.
Then it invokes the `compile' function, interactively."
  (interactive)
  (cond
   ((not (boundp 'cheeso-local-compile-command-has-been-set))
(cheeso-guess-compile-command)
(set (make-local-variable 'cheeso-local-compile-command-has-been-set) t)))
  ;; local compile command has now been set
  (call-interactively 'compile))

And the guess function is like this:

(defun cheeso-guess-compile-command ()
  "set `compile-command' intelligently depending on the
current buffer, or the contents of the current directory."
  (interactive)
  (set (make-local-variable 'compile-command)
   (cond
    ((or (file-expand-wildcards "*.csproj" t)
     (file-expand-wildcards "*.vcproj" t)
     (file-expand-wildcards "*.vbproj" t)
     (file-expand-wildcards "*.shfbproj" t)
     (file-expand-wildcards "*.sln" t))
     "msbuild ")

    ;; sometimes, not sure why, the buffer-file-name is
    ;; not set.  Can use it only if set.
    (buffer-file-name
     (let ((filename (file-name-nondirectory buffer-file-name)))
       (cond

    ;; editing a .wxs (WIX Soluition) file
    ((string-equal (substring buffer-file-name -4) ".wxs")
     (concat "nmake "
         ;; (substring buffer-file-name 0 -4) ;; includes full path
         (file-name-sans-extension filename)
         ".msi" ))

    ;; a javascript file - run jslint
    ((string-equal (substring buffer-file-name -3) ".js")
     (concat (getenv "windir")
         "\\system32\\cscript.exe c:\\cheeso\\bin\\jslint-for-wsh.js "
         filename))

    ;; something else - do a typical .exe build
    (t
     (concat "nmake "
         (file-name-sans-extension filename)
         ".exe")))))

    (t
     "nmake "))))


回答4:

Here is some code I am using that intelligently selects the compile command courtesy of the University of Wyoming. I currently have it set up for C, C++, and Fortran. You can add more to suit your needs. If I open a programming that has a C++ extension and I execute M-xcompile, my compile command spits out g++ -Wall currentfilename.cpp -o currentfilename -std=c++14. I then just have to hit enter.

;; M-x compile smarter in order to guess language
(require 'compile)
(defvar compile-guess-command-table
  '((c-mode       . "gcc -Wall -g %s -o %s -lm")
    (c++-mode     . "g++ -Wall %s -o %s -std=c++14")
    (fortran-mode . "gfortran -C %s -o %s")
    ))

(defun compile-guess-command ()
  (let ((command-for-mode (cdr (assq major-mode
                                     compile-guess-command-table))))
    (if (and command-for-mode
             (stringp buffer-file-name))
        (let* ((file-name (file-name-nondirectory buffer-file-name))
               (file-name-sans-suffix (if (and (string-match "\\.[^.]*\\'"
                                                             file-name)
                                               (> (match-beginning 0) 0))
                                          (substring file-name
                                                     0 (match-beginning 0))
                                        nil)))
          (if file-name-sans-suffix
              (progn
                (make-local-variable 'compile-command)
                (setq compile-command
                      (if (stringp command-for-mode)
                          ;; Optimize the common case.
                          (format command-for-mode
                                  file-name file-name-sans-suffix)
                        (funcall command-for-mode
                                 file-name file-name-sans-suffix)))
                compile-command)
            nil))
      nil)))


;; Add the appropriate mode hooks.
(add-hook 'c-mode-hook       (function compile-guess-command))
(add-hook 'c++-mode-hook     (function compile-guess-command))
(add-hook 'fortran-mode-hook (function compile-guess-command))


回答5:

According to the manual, you can simply override the interactive form of a function with your own by including an interactive form in your advice. I don't think you can modify or wrap the existing interactive form, only override it completely.

http://www.gnu.org/software/emacs/manual/html_node/elisp/Defining-Advice.html