When lexical scoping attacks

June 30th, 2009

Consider the following Python code:

bar = []
def test():
    new = ['this', 'comes', 'from', 'test']
    bar.extend(new)
    print bar

print bar
test()
print bar

This code generates this output:

[]
['this', 'comes', 'from', 'test']
['this', 'comes', 'from', 'test']

Which is to say, bar is empty before test(), has the contents of new inside of test(), and continues to have that value when test() returns.

Now, look at this code:

bar = []
def test():
    new = ['this', 'comes', 'from', 'test']
    bar = new
    print bar

print bar
test()
print bar

Which outputs:

[]
['this', 'comes', 'from', 'test']
[]

The value of bar is only changed inside test(). Upon further reflection, I realized that the assignment was assigning a new local variable inside the test() scope, whereas accessing bar returns the variable from the outer scope, which is then modified.

This behavior doesn’t make a whole lot of sense to me.

Abbrevs in the REPL

June 12th, 2009

I love abbrevs, and I use them all the time. While I prefer yasnippet for more complex things, abbrevs are great. I use them for simple (1-5 word) expansions, and they’re particularly helpful with SQL mode.

One of my gripes is that when I define abbrevs for a programming mode like python-mode, the abbrevs don’t get used when I use the REPL via run-python.

This is pretty straightforward to fix:

(eval-after-load 'python
  '(progn
     (derived-mode-merge-abbrev-tables python-mode-abbrev-table
                                       inferior-python-mode-abbrev-table)))

This tells Emacs to merge the python-mode abbrevs into the inferior-python-mode abbrev table. Now, when I fire up Python in Emacs, I have all my usual abbrevs. This may be inadvisable if you’re using abbrev to hook into auto-insert or to trigger behavior which expects a non-interactive environment, but it’s perfect when you just want to type “imp” instead of “import.”

Brief updates on Elisp projects

June 8th, 2009

I’m behind on my twit.el fork. Some of my changes got rolled in to mainline, but many didn’t, and this led to a merge nightmare. I’m wiping out what I did and starting over. I also need to figure out if there’s a way to line wrap variable-width fonts, since auto-fill doesn’t seem to work quite right.

I forked php-mode. I don’t have anything major planned, but I fixed the fontification of docblock comments.

I added PHPUnit support to test-case-mode. Consider phpunit-el deprecated.

There are a couple things missing vs. phpunit-el, some of which may need some changes to test-case-mode.

Emacs Nerdery: Search & replace across files

May 7th, 2009

Whenever I encounter a problem that’s painful to solve, I consider finding a better way to do it. This involves weighing how long I think it might take to find (or create) a better solution versus the amount of time it would take to suck it up and do it the hard way. Where this fails is with painful processes encountered infrequently; since they don’t happen often, it’s less worthwhile to invest the time to improve it.

Multi-file search and replace is a perfect example of this kind of infrequently painful process. After messing around with for loops and sed in the shell, I finally sucked it up and got familiar with the Emacs way of doing it. It’s a great example of the synergy of Emacs tools, since it combines familiar features in a new way.

Search and replace in a single file

The facility for one-file S&R is called query-replace, and it’s bound to M-% (plain strings) and C-M-% (regexes) by default. As you’d expect, it prompts for search and replacement text, then replaces it in your buffer. When using regular expressions, you have the full power of them, with the ability to capture sub-expressions and use them in your replacement.

Directory editing

Dired is the Emacs file management package, and it’s quite powerful. You can invoke it with C-x d, or by pointing find-file (C-x C-f) at a directory.

Dired lets you mark files with m, or files matching a regular expression with % m. Pressing Q runs query-replace over the contents of those files.

Dired only shows files in one directory, so this doesn’t work if you want to replace in files which span directories.

Find with dired

This can be mostly solved with find-dired. As the name implies, it runs find, putting the results in a dired buffer. Once you have that, you can use Q to S&R in the matched files.

This is good if you need to replace a string in every file under a subdirectory, or in every file whose name matches a pattern. Where it fails is when you need to replace a string in files which contain that string.

Grep

Emacs has excellent integration with grep, in several flavors:

  • grep. Basic grepping, with results placed in *grep*. The buffer shows filenames, line numbers, and the content from the file which matched.
  • rgrep. My favorite, rgrep performs a recursive grep and filters out unwanted files, such as backups, stuff in .svn, etc.
  • lgrep. Just like rgrep, but not recursive.
  • find-grep. Getting close to what we want, find-grep runs grep on files located with find. And finally…
  • find-grep-dired. Just like find-grep, but puts the results in a dired buffer instead.

As you’ve no doubt figured out, we’ll be using find-grep-dired for this task.

Putting it together

Now we have all the pieces, let’s put them together. As an example, let’s say that we renamed an exception and need to change the catch blocks in every .php file in package.

We start by running find-grep-dired:

M-x find-grep-dired RET catch (FooException RET

This gives us a buffer of files containing catch (FooException. It includes stuff in .svn; we only want .php files, so we can mark those:

% m .php$ RET

Now that they’re marked, run query-replace on them:

Q catch (FooException RET catch (BarException RET

At this point, Emacs will cycle through every match in every file and ask you to confirm the replacement. If you press !, it will replace the rest of the matches in the current file, and start prompting you for the next; Y will replace every match in every file with no further prompting. Then you’ll need to save the files:

C-x s

This saves every modified file at once, and we’re done.

Emacs 23: Easier directory-local variables

May 5th, 2009

I’ve been using directory-local-variables for a while now, and written about them before

I currently have two problems with DLV:

  1. It takes a lot of code to set anything. Here’s a very simple example that makes all files opened under a subdirectory read-only:
    (dir-locals-set-class-variables 'read-only
                                    '((nil . ((buffer-read-only t)))))
    (dir-locals-set-directory-class "~/Library/Preferences" 'read-only nil)
        
  2. Many of my variables need to be set to paths of files within the base directory. As an example, phpunit-program needs to be $ROOT/bin/phpunit. This doesn’t map well onto the class/directory split of DLV; I have to define a class for every directory, since the values are dependent on the path. You also end up duplicating a lot of long paths inside the directory-class.

Here’s my current solution, dir-locals.

(defmacro absolute-dirname (path)
  "Return the directory name portion of a path.

If PATH is local, return it unaltered.
If PATH is remote, return the remote diretory portion of the path."
  `(cond ((tramp-tramp-file-p ,path)
          (elt (tramp-dissect-file-name ,path) 3))
         (t ,path)))

(defmacro dir-locals (dir vars)
       "Set local variables for a directory.

DIR is the base diretory to set variables on.

VARS is an alist of variables to set on files opened under DIR,
in the same format as `dir-locals-set-class-variables' expects."
       `(let ((name (intern (concat "dir-locals-"
                                    ,(md5 (expand-file-name dir)))))
              (base-dir ,dir)
              (base-abs-dir ,(absolute-dirname dir)))
          (dir-locals-set-class-variables name ,vars)
          (dir-locals-set-directory-class ,dir name nil)))

It takes two arguments, a directory and a list of variables. The directory-class is autogenerated, so you don’t need to worry about it. Here’s how you’d use it:

(dir-locals "~/Projects/hello-world"
            '((nil . ((user-mail-address . "ian@foo.com")))))

Much better.

Within your varible list, you have two special variables you can use: base-dir and base-abs-dir. These contain the root of the directory the variables apply to, and the local directory part of that, respectively.

We need this because of the way Emacs invokes processes. If you’re visiting a remote file, Emacs will spawn external processes on the remote system. To set a DLV for a remote file, the root needs to be in the form /ssh:user@host:/path/to/root. If you try to invoke a program like that, it won’t work; you need just the directory part of the path, and this is what’s available in base-abs-dir.

On the other hand, if you want per-project TAGS files, you’ll need the full path to the remote system.

Here’s how you’d use it:

(dir-locals "~/Projects/hello-world"
            `((nil . ((user-mail-address . "ian@foo.com")
                      (tags-table-list . '(,(concat base-dir "/TAGS.gz")))))
              (php-mode . ((phpunit-program . ,(concat base-abs-dir
                                                       "/bin/phpunit"))))))

The important thing to note here is the different syntax used for the variables. Rather than using a regular quote, we use a backquote, and a comma where the special variables are used.

And that’s that. Now I have one-(or two-)line setting of variables, with access to the base directory.