[OS X Emacs] How to efficiently work with shells/term? drag & drop, history, several instances

David Reitter david.reitter at gmail.com
Thu Sep 22 17:18:16 EDT 2011

On Sep 22, 2011, at 4:26 PM, Hofert Jan Marius wrote:

> Okay, here are my latest trials:
> 1) download smart-dnd.el and put it in ~/.emacs.d/smart-dnd

This is not needed, please remove it.

> 2) tried to execute M-x smart-dnd-setup and obtained: "Wrong number of arguments: (lambda (alist) "Install smart-dnd feature to the local buffer."…

This won't work because is smart-dnd-setup is not a command, and only commands can be called with M-x.
Commands are a special type of function.  Any function can be called using the syntax (function-name arg1 arg2 …).  You can execute an arbitrary function (that is, "evaluate an expression" in Lisp speak) by typing M-:, followed by the expression.

> 3) tried to comment in the 8 lines after "(require 'smart-dnd)" => same behavior

Not sure what you're referring to.  Don't change package files for now.  It is unlikely to work for many reasons, some of them being that Aquamacs already comes with smart-dnd as I have told you, and others being that ~/.emacs.d is a path that may not work for Aquamacs (see the Aquamacs manual for the directories where to put additional packages).

OK, let me walk you and other interested parties through this.   The documentation to smart-dnd (in smart-dnd.el) tells you this:

>  Major-mode-hook would be good place to install your own configuration.
> ;; For example,
> ;;
> ;; html-mode:
> ;;
> ;; (add-hook
> ;;  'html-mode-hook
> ;;  (lambda ()
> ;;    (smart-dnd-setup
> ;;     '(
> ;;       ("\\.gif\\'" . "<img src=\"%R\">\n")
> ;;       ("\\.jpg\\'" . "<img src=\"%R\">\n")
> ;;       ("\\.png\\'" . "<img src=\"%R\">\n")
> ;;       ("\\.css\\'" . "<link rel=\"stylesheet\" type=\"text/css\" href=\"%R\">\n" )
> ;;       ("\\.js\\'"  . "<script type=\"text/javascript\" src=\"%R\"></script>\n" )
> ;;       (".*" . "<a href=\"%R\">%f</a>\n")
> ;;       ))))
> ;;

So, let's adapt this for use with shell-mode:

 (lambda ()
    '((".*" . "%R")

I tried this by inputting it in some buffer and typing C-x C-e with the cursor after the last closing parenthesis.  This evaluated the expression, calling the `add-hook' function. This means that the variable `shell-mode-hook' is a list that is extended with this lambda expression.   The effect of that is that whenever a new shell is entered and shell-mode is first started, the functions in this mode hook are called (this is the case for all major modes in Emacs), and our lambda expression is run.  

The lambda expression is an anonymous function (a function without a name).  It calls the smart-dnd-setup function with the right argument.

Now, I've tried this, and it caused something like "file://foo/bar/bla.txt" to be inserted whenever I drag&dropped something from Finder into a shell-mode window.

That's not quite what we want, but it's a step forward.  I remembered that I got this to work well at one point for latex,  so I tried it out in latex-mode - and it worked perfectly, without the file:// URL form - it just inserted the file name.

So I typed C-h v latex-mode-hook RET to display the value of the latex-mode-hook variable.  This led me to a new window showing me:

> latex-mode-hook's value is (font-latex-setup smart-dnd-latex)

OK, obviously, we're interested in the second function that is listed.  Because it is a function name (like all values in mode hook variables), I could put the cursor on `smart-dnd-latex' and enter C-h f.   I just had to press Return at this point, because it automatically recognized what was under the cursor. 

This got me a Help buffer explaining this smart-dnd-latex function - unfortunately, there is not much of an explanation, but what we really want is the source code of that function.  So, I clicked with the mouse on the underlined source file name at the top right:   `aquamacs-mode-defaults.el'.

Immediately, the source code popped up, and I could see the definition of the `smart-dnd-latex' function as follows:
> (defun smart-dnd-latex ()
>    (smart-dnd-setup
>     '(
>       ("\\.tex\\'" . "\\input{%r}\n")
>       ("\\.cls\\'" . "\\documentclass{%f}\n")
>       ("\\.sty\\'" . "\\usepackage{%f}\n")
>       ("\\.eps\\'" . "\\includegraphics[]{%r}\n")
>       ("\\.ps\\'"  . "\\includegraphics[]{%r}\n")
>       ("\\.pdf\\'" . "\\includegraphics[]{%r}\n")
>       ("\\.jpg\\'" . "\\includegraphics[]{%r}\n")
>       ("\\.png\\'" . "\\includegraphics[]{%r}\n")
>       )))

very cool.  This illustrated the right syntax that would insert the full path name without the file:// stuff.  (It is documented, but that documentation is hidden deep inside smart-dnd.el - I wasn't smart enough to look there!) 

> (add-hook
>  'shell-mode-hook
>  (lambda ()
>    (smart-dnd-setup
>     '((".*" . "%r")
>       ))))

Without restarting Aquamacs, I had to evaluate (setq shell-mode-hook nil) before evaluating the above `add-hook' expression.  This is because I didn't want to add to this hook - I wanted to replace what's in there.

I started a new shell, and voila, drag&dropping works as expected.

I'm sure it would also work for eshell and terminal - adapting the mode hook variable is enough.

Finally, a further detail.  We don't want lambda expressions in hooks.  The reason for that is that we can't easily use `remove-hook' to get rid of a function in a hook variable, unless that function has a name.  Similarly, we don't always want to add to the hook.  So, what we can do is this:

> (defun smart-dnd-always-insert-file-name ()
>   "Setup `smart-dnd-mode' so that drag&drop always inserts the file path."
>   (smart-dnd-setup '((".*" . "%r"))))
> (add-hook 'shell-mode-hook 'smart-dnd-always-insert-file-name)
> (add-hook 'eshell-mode-hook 'smart-dnd-always-insert-file-name)

This will almost do the job.  When I dragged and dropped files/path names that had spaces in them, I noticed that they weren't properly quoted.  That's not good.  So:

> (defun smart-dnd-always-insert-file-name ()
>   "Setup `smart-dnd-mode' so that drag&drop always inserts the file path."
>   (smart-dnd-setup '((".*" . "\"%r\""))))
> (add-hook 'shell-mode-hook 'smart-dnd-always-insert-file-name)
> (add-hook 'eshell-mode-hook 'smart-dnd-always-insert-file-name)

Note that we need to use \" within our Lisp string to quote the double quote.   That's because strings are already delimited with double quotes.

That's it, you can add it to your Preferences.el, or manipulate it as you wish.

My final idea here is that I'd like the cursor to appear after, not before the inserted text.  I think that makes more sense.  This will probably require changing smart-dnd.el itself.  The place to do that is `smart-dnd-execute', but it requires an advanced understanding of what `save-excursion' does, and how to decide between different cases of insertion (at point, or at the location where the mouse is moved).  I'll leave this for another day.

Another problem is that the window with the shell buffer (within a frame) needs to be selected (i.e., the active cursor should be in it).  That issue is, I think, not quite so easy to address.

I hope that this tutorial helps you and others understand some important steps in researching how to solve simple problems in Emacs Lisp, and in adding hooks. This is powerful knowledge, as it lets you manipulate and configure major modes in many ways with your own code.  Along the way, you've learned how to look up the documentation and source of Emacs Lisp functions, how define a function, how to call it.

http://aquamacs.org -- Aquamacs: Emacs on Mac OS X
http://aquamacs.org/donate -- Could we help you? Return the favor and support the Aquamacs Project!

More information about the MacOSX-Emacs mailing list