スクリプト/ソースファイルをシェルバッファで手軽に実行したい

C で小さなプログラムを書いてよく実行するんだけど、シェルバッファを開いて

gcc -g ファイル名 -o a.out && ./a.out

と毎回タイプするのがめんどくさい。M-!とかM-x compile でもいいけど、やはり広いシェルバッファがいい。

小さなソースファイル書いたあと、シェルバッファにソースファイルのコンパイルと実行コマンドを自動で展開すると楽なんじゃないか。
つまり、ソースファイルを開いた状態で

  • シェルバッファを開く
  • ソースファイルのあるディレクトリに移動
  • ソースファイルの拡張子に応じたコマンドラインをシェルバッファに貼り付ける

のところまで自動でやらせたい。

ソースファイルを編集/ 保存して、 C-c e を実行すると登録したコマンドラインがシェルバッファに展開されるようにした。

emacsのよいところは自分のようにemacs lispをよくわかっていない人間でも何となく拡張できるところ。

;; ---------------------------------------------------------
;; シェルトグル
;; ---------------------------------------------------------
;; shell-toggleは使わずに独自実装。以下はメモとして残しておく
;; M-x shell-toggle
;; shell バッファで M-x shell-toggleで戻る
;; buffer -> shell
;; M-x shell-toggle-cdで shell バッファに切り替え後,
;; 現在編集中のファイルがあるディレクトリに移動
;; (autoload 'shell-toggle "shell-toggle"
;;   "Toggles between the *shell* buffer and whatever buffer you are editing."
;;   t)
;; (autoload 'shell-toggle-cd "shell-toggle"
;;   "Pops up a shell-buffer and insert a \"cd \" command." t)

;; shellトグル独自実装
(defun my-shell-cd (&optional files)
  (interactive)
  (let ((this (selected-window))
        (other (next-window))
        (dir (expand-file-name default-directory))
        cmd)

    ;; シェルモードで、my-shell-cd を実行したときは、ウィンドウを削除
    (if (string= mode-name "Shell")
        (delete-window)

      ;; シェルモード以外

      ;; ウィンドウ分割
      (if (eq this other)
          (progn
            (setq my-shell-split-window-flag t)
            (split-window))
        (setq my-shell-split-window-flag nil))

      (other-window 1)
      (shell)

      ;; プロンプトの先頭行に移動
      (goto-char (point-max))
      (comint-bol-or-process-mark)

      ;; コマンドラインに残っている文字列を消す
      (delete-region (point) (point-max))

      ;; 頭に1文字空白を付けるのは、cd コマンドをヒストリに残さないため
      ;; ただし、シェルの設定も必要
      (setq cmd (concat " cd " "\'" dir "\'"))
      (insert cmd)
      (comint-send-input)
      (setq cmd "")
      (while files
        (setq cmd (concat cmd " " (car files)))
        (setq files (cdr files)))
      (insert cmd)
      (comint-bol-or-process-mark)
      )))

;; C-c s シェルバッファに移った後、作業中のディレクトリに移動
(global-set-key "\C-cs"  'my-shell-cd)

;; ---------------------------------------------------------
;; 関連づけしたコマンドをシェルプロンプトに展開
;; ---------------------------------------------------------
;; 起動時にシェルバッファを作っておく
(save-excursion
  (let ((current (buffer-name))
        (buffer (get-buffer-create "*shell*")))
    (set-buffer buffer)
    (shell)
    (switch-to-buffer current)))

;; ファイル名と適用するコマンド
(setq my-exec-script-alist
      '(("\\.sh\\'"                "sh")
        ("\\.\\(html\\|htm\\)\\'"  "firefox")
        ("\\.\\(pl\\|pm\\)\\'"     "perl")
        ("\\.t\\'"                 "prove -v --timer -w")
        ("\\.rb\\'"                "ruby")
        ("\\.py\\'"                "python")
        ("\\.mk\\|Makefile\\'"     "make -f")
        ("\\.js\\'"                "cscript")
        ("\\.dot\\'"               "dot -Tgif"  " -o tmp.gif && (display tmp.gif &)")
        ("\\.c\\'"                 "gcc -g"     " -o a.out && ./a.out")
        ("\\.cpp\\'"               "g++ -g"     " -o a.out && ./a.out")))

;; シェルバッファにファイルと関連付けしたコマンドを展開
;; ただし、シェルバッファがあらかじめ存在しないと、動かない?
(defun my-exec-script ()
  (interactive)
  (let ((alist my-exec-script-alist)
        (file (buffer-file-name))
        elt cmds cmd suffix regexp)
    (if (not file)
        (setq file (dired-get-filename)))
    (if file
        (progn
          ;; ファイルをシェルバッファに展開
          (my-shell-cd (list (file-name-nondirectory file)))

          (when (string= (buffer-name) "*shell*")
            ;; ファイルの先頭にコマンドを展開 (shなど)
            (while alist
              (setq elt (car alist)
                    regexp (car elt)
                    alist (cdr alist))
              (if (string-match regexp file)
                  (setq cmds (cdr elt)
                        alist nil)))

            ;; ファイルの後ろに文字列を展開 (-o a.outなど)
            (when cmds
              (setq cmd (car cmds))
              (setq suffix (car (cdr cmds)))
              (insert cmd)
              (end-of-line)
              (if suffix
                  (insert suffix)))))
      ;; ファイル以外のときは、my-shell-cd
      (my-shell-cd))))
;; C-c e スクリプト実行
(global-set-key "\C-ce"  'my-exec-script)