Monday, September 14, 2009

Emacs and Windows

My new job is forcing me, at long last, to embrace Windows as my primary development environment. As a result, I'm finally putting some real effort into getting emacs, complete with shell buffers, working well on Windows.

Here, as far as I can reconstruct it, is what I've done.

  • Download md5sums for Windows, so I can verify the MD5 checksums on other software I download.
  • Download and install keytweak, so I can remap the $#@ Caps Lock key to be a control key.
  • Add a HOME environment variable to the Windows environment. I've set this to C:\Documents and Settings\mylogin\My Documents, in an effort to be as natively Windows-ish as possible. Note that this will affect the location of things like the emacs initialization directory (.emacs.d) and bash initialization files (.bashrc).
  • Download and install emacs for Windows. Now I have an editor; I plunked a bare-bones init.el into $HOME/.emacs.d to get me going. It looks like this:

;; electric buffers
(global-set-key "\C-x\C-b"    'electric-buffer-list)

;; completion
(dynamic-completion-mode)

;; start the server 
(server-start)

;; font lock
(global-font-lock-mode t)

;; Don't store tabs. 
(setq-default indent-tabs-mode nil)

  • Download and install Cygwin/X. My reasoning here is that I'm bound to need an X11 server at some point, and this gives me enough of the entire Cygwin system (including bash, grep, find, etc.) to get started. I also added RCS (for super-easy source control for init files, etc.).

Now the main pieces are installed, but there's a lot of customization left to do. In particular, emacs shell buffers are lame; they run the Windows shell, not bash, and directory tracking doesn't work.

The next steps I acquired from Henry Kautz, specifically his page Using Emacs and Bash or Csh under MS Windows. I confess, this is essentially a black box for me, but it works like a charm. I added this to $HOME/.bashrc:
# directory tracking (from http://henrykautz.org/Computers/using_the_gnu_development_enviro.htm)

alias cd=cdpwd
function cdpwd {
'cd' "$1"
echo "Working directory is $(cygpath -wa .)"
} 
alias pushd=pushdpwd
function pushdpwd {
'pushd' "$1"
echo "Working directory is $(cygpath -wa .)"
} 
alias popd=popdpwd
function popdpwd {
'popd'
echo "Working directory is $(cygpath -wa .)"
}

I preceded these lines with a bit of path setup; this will no doubt vary based on your needs:
# Set up a vaguely useful path, in the context of an emacs shell. Thils will NOT have
# a bunch of standard Windows stuff on it.

cygdirs=/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin
tcldirs=/cygdrive/c/Tcl/bin
windirs=/cygdrive/c/WINDOWS/system32:/cygdrive/c/WINDOWS:/cygdrive/c/WINDOWS/System32/Wbem
cmakedirs=/cygdrive/c/Program\ Files/CMake\ 2.6/bin
emacsdirs=/cygdrive/c/Program\ Files/GNUmacs/emacs-23.1/bin/

export PATH=$cygdirs:$tcldirs:$cmakedirs:$emacsdirs:$windirs

Then I added this to $HOME/.emacs.d/init.el:
(defun my-shell-setup ()
"For bash (cygwin 18) under Emacs 20"
(setq comint-scroll-show-maximum-output 'this)
(setq comint-completion-addsuffix t)
(setq comint-eol-on-send t)
(setq comint-file-name-quote-list '(?\  ?\"))
(setq w32-quote-process-args ?\")
(make-variable-buffer-local 'comint-completion-addsuffix)
(setq shell-dirstack-query "cygpath -w `dirs`")
(add-hook 'comint-output-filter-functions 'perfect-track-directory nil t)
(add-hook 'comint-output-filter-functions 'comint-strip-ctrl-m nil t)
)

(defun perfect-track-directory (text)
(if (string-match "\\w*Working directory is ||\\([^|]+\\)||" text)
(cd (substring text (match-beginning 1) (match-end 1)))))

(setq shell-mode-hook 'my-shell-setup)
(setq process-coding-system-alist (cons '("bash" . undecided-unix)
process-coding-system-alist))
(setq exec-path (cons "C:/cygwin/bin" 
(cons "C:/cygwin/usr/bin" (cons "C:/cygwin/usr/local/bin" exec-path))))
(setenv "PATH" (concat "C:\\cygwin\\bin;C:\\cygwin\\usr\\bin;C:\\cygwin\\usr\\local\\bin" 
(getenv "PATH")))
(setq shell-file-name "bash")
(setenv "SHELL" shell-file-name) 
(setq explicit-shell-file-name shell-file-name)
(cd "~")

Now M-x shell creates a fully-functional bash shell, with directory tracking enabled. Sweet!

While customizing .bashrc I ran into a little problem with line endings; I configured Cygwin to use Unix-style line endings (LF, or \n), but emacs by default creates files with DOS line endings (CR LF, or \r\n). Bash doesn't like this. Saving a text file in emacs on Windows to have Unix line endings is done via C-x <enter> f unix <enter>.
"Give yourself to the Dark Side."

Oh, and speaking of the dark side: Those ugly white-on-black command windows can be customised to be a bit less depressing. There's a "colors" tab in the properties menu of the command window, and you can save the preferences for subsequent sessions.

1 comment:

  1. An addendum: I'm trying out Chrome as my primary browser, now that it supports extensions. One of my favorite FF extensions is It's All Text, which allows me to use emacs to edit text entry forms. There's an equivalent for Chrome, called Edit with Emacs. It works a little differently; in particular, it creates its own server, rather than using the server used by emacsclient. But once it's set up, it works fine.

    ReplyDelete