Monday, December 17, 2007

fcsh-mode for flex work from (x)emacs

A week or so ago I finally bit the bullet and decided I was going to have to learn some Flex/ActionScript so I could be self-sufficient on my latest project, a music game that integrates a Flash frontend with an Erlang backend. Being a Linux user (at work, at least) I downloaded the Flex SDK and was pleased to find that I could compile my Flex application using mxmlc from the command line. However, it was ridiculously slow -- we're talking 25+ seconds to compile a 500 line mxml file.

It turns out the reason for this is that mxmlc is written in Java and has significant startup cost. The solution is to use fcsh, the Flex Compiler Shell, which runs continuously and can incrementally compile your Flex application as you make changes. Using this from my shell reduced compilation time to 1-2 seconds, which is perfectly acceptable.

However, I quickly noticed that fcsh sucks. Most notably, it has no command history. I was typing "compile 1" every 15 seconds and getting annoyed. So I buckled down and wrote an fcsh-mode for Xemacs, reproduced below.

If you want to try it, paste it into a file like ~/.xemacs/fcsh-mode.el and load it using M-x load-file or adding (load-file "~/.xemacs/fcsh-mode.el") to your emacs init file. Then M-x fcsh will launch an fcsh with command history (even cross-session) in a new buffer. I also added an fcsh-repeat-last command which I bind to C-Enter using M-x local-set-key in my editing buffer. I can then just hit control-enter to recompile my flex app in less than a second.

Hope someone finds this useful!

(require 'comint)

(defvar fcsh-mode-map
(let ((fcsh-mode-map (copy-keymap comint-mode-map)))
(define-key fcsh-mode-map [(up)] 'comint-previous-input)
(define-key fcsh-mode-map [(down)] 'comint-next-input)
"Keymap for fcsh major mode")

(defvar fcsh-mode-hook nil
"Functions to run when fcsh mode is actived.")

(defvar fcsh-input-ring-file-name "~/.fcsh_history"
"*When non-nil, file name used to store Fcsh shell history information.")

(defun fcsh-mode ()
"Major mode for running the fcsh, flex compiler shell"
(setq major-mode 'fcsh-mode)
(setq mode-name "FCSH")
(use-local-map fcsh-mode-map)

(setq comint-prompt-regexp "^\\(fcsh\\) ")
(setq comint-eol-on-send t)
(setq comint-input-ignoredups t)
(setq comint-scroll-show-maximum-output t)
(setq comint-scroll-to-bottom-on-output t)

;; Some older versions of comint don't have an input ring.
(if (fboundp 'comint-read-input-ring)
(setq comint-input-ring-file-name fcsh-input-ring-file-name)
(comint-read-input-ring t)
(make-local-variable 'kill-buffer-hook)
(add-hook 'kill-buffer-hook 'comint-write-input-ring)))

(run-hooks 'fcsh-mode-hook))

(defun fcsh ()
"Run an inferior fcsh, with I/O through buffer *fcsh*.
If buffer exists but fcsh process is not running, make new process.
If buffer exists and fcsh process is running, just switch to *fcsh*.
The buffer is put in fcsh-mode.

\(Type \\[describe-mode] in the fcsh buffer for a list of commands.)"
(cond ((not (comint-check-proc "*fcsh*"))
(set-buffer (make-comint "fcsh" "fcsh"))
(pop-to-buffer "*fcsh*"))

(defun fcsh-command (cmd)
"Run a command in the fcsh shell"
(interactive "scommand?")
(let ((old-buffer (current-buffer)))
(cond ((not (comint-check-proc "*fcsh*"))
(set-buffer (make-comint "fcsh" "fcsh"))
(set-buffer "*fcsh*")
(goto-char (marker-position (process-mark (get-buffer-process (current-buffer)))))
(insert cmd)
(switch-to-buffer old-buffer)))

(defun fcsh-repeat-last ()
"Repeat last command in fcsh"
(let ((old-buffer (current-buffer)))
(cond ((not (comint-check-proc "*fcsh*"))
(set-buffer (make-comint "fcsh" "fcsh"))
(set-buffer "*fcsh*")
(goto-char (marker-position (process-mark (get-buffer-process (current-buffer)))))
(call-interactively 'comint-previous-input)
(switch-to-buffer old-buffer)))

No comments: