gcc には気を付けないと。

先日のエントリで、ob-C の編集バッファで flymake を有効にする方法を書いてみましたが、大事なことを忘れていました。
ご存知の方には何を今更ということではありますが、gccコンパイラドライバは、ソースファイルの拡張子を見て言語を推測しています。そのため、拡張子が無いファイルをソースとして指定すると、

$ gcc -c source
gcc: source: linker input file unused because linking not done

などと怒られてしまうことに。先日のエントリで紹介したコードでは、編集バッファの buffer-file-name を元に flymake が一時ファイルを作成して gcc に渡すので、何もしていないと上記のエラーとなってしまいます。
回避するには、`flymake-c-init' で作成する gccコマンドライン (ここで Makefile を指示しているなら Makefile によるコンパイルコマンドライン) に、

$ gcc --help
Usage: gcc [options] file...
Options:
(snipped...)
  -x             Specify the language of the following input files
                           Permissible languages include: c c++ assembler none
                           'none' means revert to the default behavior of
                           guessing the language based on the file's extension

というオプションを指定します。

(list "gcc" (list "-x" "c" "-Wall" "-Wextra" "-fsyntax-only"
                  local-file) (file-name-directory buffer-file-name))

の様な感じですね。
こうしておくと、どんなファイルでも C言語のソースファイルと認識して、C のコンパイル (syntax-check) を実行してくれることになります。

そもそも慎重さが足りないので、慌てて書くと良くないのですよね……と言いつつ、今日も時間がない状態で書いているという……またやらかしてしまうのかも。

C言語なんてみんな使わなくなっちゃった??

もう随分前から、C の評判はどんどん落ちてきてますかね。ちょっと前にも、

卜部昌平のあまりreblogしないtumblr - どうも周知徹底が不足しているようなので再度のお願いとなりますが、C死ね。

なんてコンテンツが話題になったり。まあ、このコンテンツのコメントには色々な視点があって安心したりもしましたが。

私は未だに C で書かれたものを扱わないとならないので、必要に応じて C のコードを触っています。なので、org-babel でも ob-C があって嬉しかったりします。
ob-C では、実は少し問題がありました。ob-C の中に、

  `org-babel-expand-body:c'
  `org-babel-execute:C'

という二つの関数があります。これらは、コードブロックの中身を展開する関数と、コードブロックの中身をコンパイルして実行する関数です。関数名のコロンの後にあるのが言語 (つまり C) を示す識別子になっていて、実はこの文字列から、適用するモード名を生成していました。
何故、この様に大文字と小文字の `C' と `c' で異なるのか、私には意味が掴めませんでしたが、少なくとも私の環境には、`c-mode' (cc-mode パッケージの C 用のメジャーモード) はありますが、`C-mode' はありませんでした。しかし、ob-C は `org-babel-execute:C' となっているせいで、`C-mode' を要求してくるのです。仕方ないので、

(eval-after-load "org"
  '(progn
     (require 'ob-C)
     (require 'ob-sh)
     (require 'ob-R)
     (require 'ob-ruby)
     (require 'ob-python)

     (defalias 'C-mode 'c-mode)))

として、`C-mode' が要求されても良い様にしてあります。

さて、この ob-C ですが C 言語が持つ特徴を上手く隠蔽する仕組みを持っていました。
C プリプロセッサディレクティブである `#include' や `#define' をコード断片に記述しなくても補完してくれるのです。とは言え、推測までして補完してくれる訳ではなく、コードブロックの外側で指示する、ということですが。(まあ、推測なんてされても困ることの方が多そうですから、それで良いんですけどね)
例えば、

#+begin_src C :results output :includes '( )
  fprintf (stdout, "size of long long = %d\n", sizeof (long long));
  return (EXIT_SUCCESS);
#+end_src

みたいな具合です。`#+begin_src' 行で、`:includes '( )' というオプションを指示しておけば、

#+begin_src C :results output
  #include 
  #include 

  fprintf (stdout, "size of long long = %d\n", sizeof (long long));
  return (EXIT_SUCCESS);
#+end_src

の様にわざわざ書かなくても良いのです。そんなに便利か? と言わざるを得ない様な相違でしかないかもしれませんが、何かコードの説明をするときなどに、問題領域だけを表記するのに役立つかもしれないなあ、なんて思ったり。

と、何か上のコードが中途半端で変だなあ、なんて思いませんか? そうなんですね、関数の態を成してないんですよね。実はこれも補完が効いているという話で、実は、ob-C は上のコードを、

#+begin_src C :results output
  #include 
  #include 
  
  int main () {
    fprintf (stdout, "size of long long = %d\n", sizeof (long long));
    return (EXIT_SUCCESS);
  }
#+end_src

と書いたものと同じ状態にしてコンパイラに渡しています。そのため、非常に簡略化した断片だけを記述して実行することができるのでした。

で、こうなると困ってしまうのが flymake です。実は、最初のコードである、

#+begin_src C :results output :includes '( )
  fprintf (stdout, "size of long long = %d\n", sizeof (long long));
  return (EXIT_SUCCESS);
#+end_src

なんかは、コード編集のバッファで flymake が走ると真っ赤になってしまいます。それは非常に具合が悪いので、

;; 関数を跨がって状態を保持する変数。
(defvar org:flymake-C-main-append-num 0)
(defvar org:flymake-C-includes-append-lines nil)
(defvar org:flymake-C-includes-append-line-num 0)

(defadvice flymake-save-buffer-in-file
  (around flymake-save-buffer-in-file-for-org activate)
  "org-babel のバッファ内容から、gcc で compile できる状態の一時バッファを作成
する。"
  (let *1
    (if (> org:flymake-C-includes-append-line-num 0)
        (set 'org:flymake-C-includes-append-lines
             (concat
              (mapconcat
               (lambda (inc) (format "#include %s" inc))
               (if (listp includes) includes (list includes)) "\n") "\n"))
      (set 'org:flymake-C-includes-append-lines nil))))

などとしてお茶を濁しました。ああ、時間が無くなってしまったので詳細は割愛……
flymake が syntax-check するときに main や include を補完してやって、そうすると行番号がバッファ上のものと異なるのでそれを調整しています。

*1:src (concat org:flymake-C-includes-append-lines (buffer-substring (point-min) (point-max))))) (with-temp-buffer (insert (if (string-match "^[ \t]*[intvod]+[ \t\n\r]*main[ \t]*(.*)" src) (progn (set 'org:flymake-C-main-append-num 0) src) (set 'org:flymake-C-main-append-num 1) (format "int main() {\n%s\n}\n" src))) ad-do-it))) ;; line-err-info 内の line-no を書き換える。 (defadvice flymake-add-err-info (before flymake-add-err-info activate) "一時バッファに `int main ()' や `#include <*.h>' などを挿入すると、gcc の メッセージに含まれる行番号が実際のバッファ内容と異なるため調整する。" (set 'line-err-info (flymake-ler-set-line line-err-info (- (flymake-ler-line line-err-info) ;; ここは、全部加算してしまう関数を用意した方が ;; 後々良さそうだ。 (+ org:flymake-C-main-append-num org:flymake-C-includes-append-line-num))))) (defadvice org-babel-parse-src-block-match (after org-babel-parse-src-block-match-for-flymake activate) "`#+begin_src C :includes ' の様に、org-babel の syntax で include や define を指示したときに、compile する一時バッファに内容を反映する。" ;; 今のところ include のみ。 (let ((includes (cdr (assoc :includes (nth 2 ad-return-value))))) (set 'org:flymake-C-includes-append-line-num (length includes

色々な言葉を理解する。

  Babel: active code in Org-mode

これ、本当に凄いですね。
以前のエントリで、gauche を評価する scratch バッファもどきを作ったなんてつまらない話を書きましたが、本当は、こんなものを求めていたんだ、と思いました。

どんなものかと言うと、例えば、プログラムを書くべくアイデアを練っているとします。アイデアをただのテキストにメモしているバッファ上で、ちょっとコードを書いて確認してみたいなんて思ったら、おもむろに、

  M-x org-babel-demarcate-block (C-c C-v d)

とすると、ミニバッファで、

  Lang:

と訊かれるので、書きたいプログラム言語を指示してやります。すると、

#+begin_src C
#+end_src

というコード断片を記述するためのメタブロックが、現在のバッファ上に作成されます。ここに、該当するプログラミング言語のコード断片を記述するのですが、そのやり方も、このブロックにポイントしている状態で、

  M-x org-edit-special (C-c ')

とすると、新たなバッファをプログラミング言語に対応するモードで開いてくれるという親切設計。
更に、

  M-x org-babel-execute-src-block (C-c C-c)

とすると、ポイントされているブロックのコードを実行して結果を出力してくれます。
いや、便利ですし、色んな可能性が感じられてワクワクします。チュートリアルなどを見ると、shell でコマンドを実行した結果を R に喰わせてグラフ化するとか、まあ素晴らしい。

この org-babel のインストールや使い方などは、

Emacs上のマルチな実行環境、Org-babel - sheephead
Emacs org-modeを使ってみる: (35) org-babel-perlを使う1/4 - 屯遁のパズルとプログラミングの日記

などを見て貰えば詳しく解説されています。
但し、現在は org-babel が contrib から org-mode 本体に統合されたので、インストールの手順などは変わってます。まあ、楽になったので問題ないと思いますが。特別な環境でなければ make && sudo make install 的な普通の手順で org-babel までインストールされます。
私はホームディレクトリに作ってある elisp 置場におきたいのと、その際にディレクトリにまとめておきたかったので、以下の様な感じでインストールしました。

$ make prefix=~/.emacs-23.d/ lispdir=~/.emacs-23.d/share/emacs/site-lisp/orgmode
$ make prefix=~/.emacs-23.d/ lispdir=~/.emacs-23.d/share/emacs/site-lisp/orgmode install

そして、.emacs とかに、

(require 'org-babel-init)
(eval-after-load "org"
  '(progn
     (require 'ob-sh)
     (require 'ob-R)
     (require 'ob-ruby)
     (require 'ob-python)))

と書けば良い様です。

さて、本来はこれで動作する筈なんですが、私の環境では上手く動いてくれませんでした。調べてみると問題は、利用する各プログラム言語に対応するモードの hook でした。
前述の様に、コードを編集するときには、各言語の編集用モードを適用したバッファを開いてくれるのですが、そのときに、当然ながらそれぞれのモードフックが実行されます。本来ならば普通にフック関数が実行されるのですが、このバッファには visit しているファイルがありません。元の org-mode で記述されているバッファからメタブロック内のコードを抽出して別のバッファを作っていますので対応するファイルが存在しないのです。実際のところ、新たに開かれたバッファには、最終的には `buffer-file-name' が設定されるのですが、言語ごとのモードが適用されるときにはまだ設定されていないという訳です。
そのため、通常であれば対応するファイルが存在しているバッファ上で使われる各モードフックから実行される関数のうち、buffer-file-name が nil でないことを仮定しているコードがあると、それが失敗してしまいます。

自分が独自に展開しているコードは直せば良いので別に良いのですが、ちょっと困ったのは flymake でした。flymake を有効にしたいプログラミングモードでは、モードフックで `flymake-mode' を有効にしているのですが、flymake が `buffer-file-name' があることを仮定していました。syntax-check を行なうために、言語ごとに異なる設定や準備が必要だからですね。

取り敢えず、`buffer-file-name' が存在しないときには flymake-mode にはしない様に、

(add-hook
 'c-mode-common-hook
 '(lambda ()
    ;; ob-C のために buffer-file-name が設定されていないことを考慮。
    (when buffer-file-name
      (flymake-mode t)
      (define-key (current-local-map) "\C-cc" 'flymake-start-syntax-check)
      (define-key (current-local-map) "\C-ce" 'flymake-show-and-sit)
      (define-key (current-local-map) "\C-cn" 'flymake-goto-next-error)
      (define-key (current-local-map) "\C-cp" 'flymake-goto-prev-error))))

などとして逃げました。ruby-mode の場合は、そもそも rhtml のために似たようなことをしてモードフックから除外していたので問題ありませんでした。
さて、これでは `org-edit-special' して編集するときに、flymake の恩恵を受けることができません。これは余りにも残念です。
なので少しばかり考えました。

(push '(".*Org Src.*\\[ ruby \\]" flymake-ruby-init flymake-simple-cleanup-extra)
      flymake-allowed-file-name-masks)
(push '(".*Org Src.*\\[ C \\]" flymake-c-init flymake-simple-cleanup-extra)
      flymake-allowed-file-name-masks)

(defun enable-flymake-setup ()
  "org-babel (ob-C) のバッファに flymake-mode を適用する。"

  (flymake-mode t)
  (define-key (current-local-map) "\C-cc" 'flymake-start-syntax-check)
  (define-key (current-local-map) "\C-ce" 'flymake-show-and-sit)
  (define-key (current-local-map) "\C-cn" 'flymake-goto-next-error)
  (define-key (current-local-map) "\C-cp" 'flymake-goto-prev-error))

(defadvice org-src-mode-configure-edit-buffer
  (after org-src-mode-configure-edit-buffer-for-c activate)
  "c-mode-common-hook が走るタイミングでは buffer-file-name が設定されていない
ため、少し遅延して setup を実施する。"
  (enable-flymake-setup))

それぞれコード編集用に開かれるバッファに設定される `buffer-file-name' のパターンを flymake に知らせてやった上で、`org-src-mode-configure-edit-buffer' という org-mode の「コード編集バッファを設定する関数」に無理矢理アドバイスして、flymake-mode を有効にする様にしてみました。
これで取り敢えずは使えてます。が、また更に工夫しないとならないところがあったり。

shell-mode のログを自動保存

久し振りのはてな
何か本当にブログとか日記を継続的に書くってことができないんですよね。
色々と書き溜めておきたいことってのはあるんですが、面倒になってしまって。
そういう意味で twitter とかの簡単に post できるアイデアってのは凄いと思います。

ところで、こんな記事を見掛けました。

ターミナルのログを自動保存したい - まちゅダイアリー(2011-05-27)

私も、同じ様なことを以前に考えていましたことがあります。
私の場合は、仕事で初心者に毛の生えた程度の同僚たちに、ターミナル上での作業ログを記録させるためでした。
そのため、本来は制御文字が含まれない形態が良いのですが、ページャで表示すればまあ使えるという割り切りで script コマンドを利用させていました。実際、何かあったときに保存されている内容を解析しようとすると、結構面倒だったりはするのですが。

screen に関しては、自動保存させる方法が判らなかったので、ログの記録という面では利用しませんでした。手動だと必要なときほど忘れていたりするものですよね。但し、screen は、これとは別に、端末入出力を切り離して detach/attach ができるということでは重宝しましたけど。

で、私自身は、端末操作はほぼ全て Emacs の shell-mode か ssh-mode を利用しているので、Emacs さんに自動で保存させる様にしています。

(defun process-buffer-visit-file ()
  (interactive)

  (save-excursion

    ;; bash が起動してバッファに反応があるまで待ち合わせる。
    (while (if (= (point-max) 1) (sit-for 0.5)))

    (let* ((date-alist '(("Jan"   1) ("Feb"  2) ("Mar"  3)
                         ("Apr"   4) ("May"  5) ("Jun"  6)
                         ("Jul"   7) ("Aug"  8) ("Sep"  9)
                         ("Oct"  10) ("Nov" 11) ("Dec" 12)))
           (date (current-time-string))
           (date-time-str
            (format "%02d%02d%02d-%02d%02d"
                    (string-to-number (substring date 22 24))
                    (car (cdr (assoc (substring date 4 7) date-alist)))
                    (string-to-number (substring date 8 10))
                    (string-to-number (substring date 11 13))
                    (string-to-number (substring date 14 16))))
           (buffer-name (buffer-name))
           (buffer-mame-string
            (let ((regexp "[*<>]")
                  (replace "")
                  (string buffer-name))
              (while (string-match regexp string)
                (set 'string (replace-match replace nil nil string)))
              string))
           (pid (number-to-string (emacs-pid)))
           (tty (save-excursion
                  (when (string-match "ssh" buffer-name)
                    (process-buffer-get-tty))
                  (goto-char (point-min))
                  (if (re-search-forward "/dev/" nil t)
                      (let ((start (point)))
                        (end-of-line)
                        (buffer-substring-no-properties start (point)))
                    "ttyX")))
           (tty-string
            (let ((regexp "/") (replace "") (string tty))
              (while (string-match regexp string)
                (set 'string (replace-match replace nil nil string)))
              string))
           (base-dir (expand-file-name "~/tmp/buffers/")))

      ;; バッファの内容を保存。(保存したファイルにバッファが結び付けられる)
      (write-file (concat base-dir date-time-str "-"
                          pid "-" tty-string "-" buffer-mame-string ".log"))

      ;; 時間調整。
      (sit-for 0.5)

      (dirs)
      (rename-buffer buffer-name)))
  (goto-char (point-max)))

(defun process-buffer-get-tty (&optional arg)
  (comint-send-string
   (get-buffer-process (current-buffer)) "tty\n"))

色々と面倒なことをしているのは、ユニークかつ後に判別し易いファイル名を生成するために工夫している部分ですね。

保存するファイル名は、shell-mode の場合、

"日付 (YYMMDD)"-"時刻 (HHMM)"-"プロセスID"-"キャラクタデバイスファイル名"-shell.log

  ex. ~/tmp/buffers/110528-2314-21558-pts12-shell.log

の様になります。
ssh-mode の場合は、base name の末尾の "shell" が "ssh-ホスト名" となります。

これ以外の部分の作りは単純で、

  1. write-file でバッファ内容を保存し、バッファにファイルを貼り付ける。
  2. バッファ名がファイル名に置き換えられてしまうので、元に戻す。

と、これだけです。
私は Emacs を通常、

  • 自動保存する
  • バックアップファイルを作成する

の設定で利用しているので、あとは Emacs が勝手に保存してくれますし、Emacs が落ちてしまったときでも、`recover-session' や `recover-this-file' することでほぼ最新状態に復元することができます。

ツールチップで C プリプロセッサマクロの定義内容を表示

昨日のエントリで、リモートデバッグの際には `gud-tooltip-mode' は無効にしておいた方が良いと書きました。
私の場合、マウスを殆んど使わない (Emacs を使ってる人は殆どそうだと思うのですが、最近だと違ったりしますかね) ので、ツールチップを有効にしていても余り良いことはなく、無効にしていても影響が無いというのはあるのでしょう。

しかし、GDB を `gdb-many-window' で使っていると、ローカル変数の表示バッファ (*locals of BUFFER-NAME*) などに表示されているのが便利で、gdb バッファで `print var' なんてやらなくなってきます。
しかし、`gdb-many-window' でブレークポイントやコールスタックなどバッファも同時に表示していると、それぞれのウィンドウサイズを余り大きく取れず、ローカル変数が少し多いとウィンドウからはみ出てしまってスクロールしないと見られない状態になってしまいます。そうすると、ツールチップで簡単に見られるのが結構有効に思えて来ました。

で、`gud-tooltip-mode' を有効にして利用しようとすると、gdb-ui の `gdb-create-define-alist' で `call-process' を使ってるので、リモート環境のデバッグの場合は、動作しない様になってました。
なのでこれを `process-file' に置き換えます。
`start-process' は `start-file-process' で、`call-process' は `process-file' で置き換えてやるのが、Magic File Names を利用するために simple.el が用意してくれているスキームですね。こうしておけば、適切な `find-file-name-handler' が設定されていれば、良きに図らってくれます。
GDB と同じ位に便利な flymake が `start-process' を使っているので、`start-file-process' に置き換えるって話は、随分前に Emacs Tramp + flymake - higepon blogid:higepon さんが、紹介されていましたね。

ここで、`call-process' で取ってきてるのは、C プリプロセッサの `#define' で定義されたマクロの値ですね。`gcc -E -dM -' とかして、#define されたシンボルとその値を収集して `gdb-define-alist' に保持しています。そして、この値には、マクロ定義の定義内容自体も含まれます。これで、プリプロセッサで定義したシンボルにマウスカーソルを合わせると、定義された値やマクロ定義の内容がツールチップで表示されるという訳です。

ところがこの `gdb-define-alist' の値を表示する挙動なんですが、これが私にはちょっと良く判らない動作をします。リモート環境とかは関係なく、GDB のプロセスが生きているときは見ないんですよね、`gdb-define-alist' のことを。
GDB のプロセスが生きてるときは、必ず `print SYMBOL' なんてコマンドを GDB に送って値を問い合わせています。しかし、プリプロセッサマクロの場合だと、そもそも GDBプリプロセッサでの定義内容のことは知りもしないので、そんなシンボルはスコープにねえ、って素気無いツールチップが表示されることになってしまいます。
折角取ってきてるんだから使えば良いのにと思いますが、実際のところ、プリプロセッサで定義されたシンボルなのか、変数としてのシンボルなのかは判断する術はないので GDB に問い合わせることにしたんでしょうかね。

私としては、プリプロセッサの定義値を見るのに、find-tag する手間を省けた方が嬉しいので、

;; ちょっと無理矢理だが、`gud-tooltip-tips' の実行時には、最初に必ず
;; `gdb-define-alist' から assoc し、存在していればそれを `tooltip-show' し、
;; 無かったときのみ本来の `gud-tooltip-tips' を実行する様にアドバイス。
(defadvice gud-tooltip-tips (around ad-gud-tooltip-tips activate)
  (or (let *1
              expr))))
      ad-do-it))

などとして、無理矢理見れる様にしています。先にも書きましたが、ローカルでのデバッグでも同じですので、常に上記の設定を有効にしています。

まあ、ツールチップに関しては、リモートの場合に遅過ぎでもたるし、下手をすると詰まるようなので、通常は `gud-tooltip-mode' を無効にしておき、必要なときだけ有効にして利用していますが。

*1:expr (tooltip-expr-to-print event))) (with-current-buffer (tooltip-event-buffer event) (let ((define-elt (assoc expr gdb-define-alist))) (unless (null define-elt) (tooltip-show (cdr define-elt) (or gud-tooltip-echo-area tooltip-use-echo-area

実は簡単だったんですね。

いや、全然知りませんでした。
いまどきの Emacs では常識ですか? これって。

古いシステムのトラブルで、久し振りに C 言語のシステムをデバッグしなければならず、とは言えホストマシンは新しくなっていて環境が余り整っていない…… という状況で作業しようとしてみたら。
Emacs 23 以降でないとダメなのかもしれませんが、TRAMP さえあれば、なんと素のままの Emacs でそのままリモートホストGDBデバッグできるんですね。

GDB が持つリモートデバッグ機能は、「通常の方法で GDB を実行させることのできないマシン上で実行中のプログラムをデバッグするためのもの」ですので、ここで言うリモートデバッグは、それとはちょっと意味合いが異なります。
ここでのリモートデバッグは、単純に自分のローカル環境から、リモートホスト上で実行する GDB に接続してのデバッグをそう呼んでいます。

リモートホストEmacsGDB があれば、別段、何も苦労することはありません。
しかし、昨今はどんな環境にも Emacs がインストールされているという時代ではない様です。いや、そもそも Emacs なんかどこにでもあるとは限らないって言われちゃいますかね、vi と違って。無ければ自分で入れれば良いって訳でもないですしね、仕事の環境ではそうそう勝手なことはできないですから。
しかも、最近の仕事は移り変わりが激しく、やれあっちのホストマシン、こっちのホストマシンと、あちらこちらでの作業が必要だったりします。
しかし、TRAMP があれば大抵のことはローカル環境から透過的に利用できますよね。
なにせ、`Transparent Remote (file) Access, Multiple Protocol' ですから。

それが本当に透過的だな、と思わされるのは、リモートホストにあるファイルを開いたバッファ上で何か行うと、それがリモートホスト上で動作して、結果を正しく伝えてくれるときですね。まあ、何でもかんでもそうではなくて、単純な一括編集や文字列を拾って Google で検索、なんてときにはローカルで処理された方が効率が良いし、インタフェースとしても正しい挙動でしょう。
しかし、flymake で syntax check したり、compile したり、バージョンコントロールシステムにアクセスしたり、デバッグしたり、ってのは、リモートホストで動作してくれないと意味がありません。それこそクロス開発環境が構築されてない限りは。

VC との透過的な統合は、TRAMP の売りの一つでもあるので、TRAMP のマニュアルでも紹介されていますが、GDB を使ったデバッグも、ちゃんとリモートホスト上で動作してくれるのですね、しかも、特別な設定は必要ありません。
設定が必要ないのは、M-x gud-gdb や、`gdb-many-windows' が有効でない M-x gdb の場合です。それであれば素のままで何の設定もなく、そのままリモート環境の GDB を利用したデバッグが実行できました。gud は TRAMP を想定している様ですし、comint 経由でプロセスにアクセスする (comint は magic file name に対応している `start-file-process' でプロセスを扱かっている) ことから、そもそも問題なくリモートホストのファイルが扱える様です。
;; 今の私の環境には、Emacs 23 しかないものですから、Emacs 22 以前だと素ではどうだか判りませんが。

gud-gdb で十分と言えば十分なのですが、折角ですから `gdb-many-windows' を有効にした状態でも動作させてみたいところ。実のところ、動作自体はするのです。ただ、`gdb-many-windows' を有効にした gdb-ui でデバッグを行うと、

  • ブレークポイントを設定したときのソースファイルへのマーク。(フリンジにマークが付く)
  • breakpoint バッファに表示されたブレークポイントを選択して、ソース上の該当位置を表示する。

の何れかの操作や動作を行うときに、該当するソースファイルを開いてソースバッファに表示しようとし、その動作が失敗してしまいます。
GDB からの要求でソースを開こうとするため、リモートホスト上でのパス表現のまま、ローカル環境で開こうとしてしまうからです。
なので、ここでのパスを「ローカル環境から見た TRAMP のパス」に変更してやれば良い筈です。
ということで、ちょっと強引ですが、quick hack...

;; gdb-many-windows が有効でも TRAMP 経由で remote debug できるように。
;;
(defadvice find-file-noselect (before ad-find-file-noselect)
  "指定されたファイルがローカル環境に存在するか確認し、無ければリモートホストの
ものと判断して path を変更する。"
  (set 'filename (if (file-exists-p filename) (expand-file-name filename)
                   (gud-file-name filename))))

(defadvice gdb-get-location (around ad-gdb-get-location activate)
  "TRAMP を利用して remote debug するために、`find-file-noselect' のアドバイスを
活性化する。"
  (let ()
    (ad-activate 'find-file-noselect) ad-do-it
    (ad-deactivate 'find-file-noselect)))

(defadvice gdb-info-breakpoints-custom
  (around ad-gdb-info-breakpoints-custom activate)
  "TRAMP を利用して remote debug するために、`find-file-noselect' のアドバイスを
活性化する。"
  (let ()
    (ad-activate 'find-file-noselect) ad-do-it
    (ad-deactivate 'find-file-noselect)))

(defadvice gdb-goto-breakpoint (around ad-gdb-goto-breakpoint activate)
  "TRAMP を利用して remote debug するために、`find-file-noselect' のアドバイスを
活性化する。"
  (let ()
    (ad-activate 'find-file-noselect) ad-do-it
    (ad-deactivate 'find-file-noselect)))

と、`find-file-noselect' に渡すパスを変更してやるアドバイスと、それを適切な期間だけ活性化するアドバイスを定義しています。
これでローカル環境の Emacs 上では、リモートホストのファイルのパスが常に TRAMP の形式となることになるので、問題なく処理される様になりました。ただ、リモートホストのファイルであることの判定を、単純に「ローカル環境無かったらリモートホストのファイルである」としてしまっているので、同じパスとなるファイルがローカル環境に存在するとそれが利用されてしまいます。もう少しちゃんと判定した方が良いのでしょうね。

稚拙なハックなので副作用などあるかもしれませんが、今のところはこれで利用できています。
ただ、`gud-tooltip-mode' は無効化しておいた方が良いですね。絶えずリモートの GDB と通信する様になってしまって、流石に遅く感じます。これを無効にしてさえいれば、速度的にも体感的には殆んどストレスなく利用できています。まあ、私はベースが Cygwin 上の Emacs 23 なので、そもそも反応が遅いものに慣れてしまっているのというはありそうです。サクサク動作する環境で使うと、体感速度にも違いがでるのかもしれません。

サブプロセスの中断とサスペンド

何だかお仲間を見付けるのは嬉しいものです。
Emacs on Cygwin という環境は、Windows を利用せざるを得ない状況では、非常に心強い環境なので、私は離れられません。
元々は XEmacs を利用していましたが、Emacs 22 の頃にようやく移行して、今では Emacs 23 が何とか馴染んできたかな、という具合です。

ところが少し前から bash の起動に異常に時間が掛かる様になり、なんだか非常に使い勝手が悪いと感じて Google さんに訊いていたところ、その問題とは違いますが、同じ shell 繋がりの悩みを持たれたお仲間を発見したのでした。
発見したのは、

Windows で Emacs 内部 shell をストレスなく動かす = Cygwin + sshd + XMing + VMWare + Ubuntu + Emacs + Tramp + shell-mode

で、当然のごとく Emacs + shell-mode を Cygwin で使おうとしたらこれが使えない:

  • コマンドを殺せない (C-c C-c)
  • コマンドをサスペンドできない(C-c C-z)

これはキツい。まるで、 unix 演習で学生が作った shell で仕事をするような辛さ。

という記事です。
Emacs の shell-mode が好き」と仰っている時点でお仲間認定です。(手前勝手な言い草ですが)

私の場合、XEmacs から Emacs 22 へ移行した (二年半くらい前かな?) 後、しばらくしてからこの現象に気が付き、やっぱり非常に難儀したのでした。

で、これ、id:camlspotter さんも仰られてますが、ssh-mode だと問題ないんですよね。なので、M-x ssh localhost すれば解決、そんなに極端に遅くないし……

という訳にも行かないので ssh-mode の中身を覗いてみたところ、shell-mode とは実装が違うのですよね。当然ですが。
shell-mode では `interrupt-process' とか `stop-process' などのシグナルを送るインタフェースを使ってるんですが、ssh-mode では `process-send-string' を利用して制御文字を送ってました。

なので、こいつを拝借して、

;; ssh の関数 `ssh-send-Ctrl-C', `ssh-send-Ctrl-Z' の実装を流用して乗っ取る。
(defadvice comint-interrupt-subjob (around ad-comint-interrupt-subjob activate)
	(process-send-string nil "\C-c"))
(defadvice comint-stop-subjob (around ad-comint-interrupt-subjob activate)
	(process-send-string nil "\C-z"))

と、shell-mode の関数を乗っ取ってしまいました。包囲アドバイスで `process-send-string' を呼び出して終了してしまいます。実体の関数は呼び出しません。

本来は shell-mode の実装の方が良いのでしょうが、これで取り敢えず、サブプロセスの中断と一時停止はできる様になっています。
id:camlspotter さんは、Linux 上の Emacs が使える様になったので、もう不要かもしれませんが。
やっぱり Unix 上 の Emacs の方が反応が良くて良いのですよね。Cygwinsshd は反応悪いですけど、Tramp で Cygwin に login する分には、一旦、login してしまえば、補完も含めて反応もそんなに悪くないですし。
母艦は Linux だよなあ…… 「天麩羅いやざんす」とか言う奴のせい ;-) で難しかったんだけど、そろそろ会社でも Linux のマシンを使えそうだから移行するかなあ。

ああ、bash の起動が遅くなった件は何も解決していない…… 一体、何が影響してるんでしょうか……

Web ブラウザの可能性

もう随分前から公開されているものの様ですが、恥ずかしながらつい最近、これを知りました。

  Web Development Bookmarklets

この中でも特にインパクトがあったのが JavaScript Shell ですね。
これが Bookmarklet でできてしまうのですから、いやはや凄いもんです。Firefox 限定というのがちょっと難ですけど。


そして、どうせ Firefox 限定ならば、と、Extension Developer もあります。これだと、JavaScript Shell が余分な要素のない別ウィンドウで実行され、パネルとして利用できるイメージになるので、更に使い易いですね。
但し、Bookmarklet からの起動と異なり、実行コンテキストが shell のコンテキストになってしまうのでコンテキストの切り換えが必要になりますが、JavaScript の世界なのでそんなに面倒なことではありません。まあ、そもそもが Extension の開発のためのものなので、Firefoxchrome へのアクセスが可能である必要がありますから。
それに、enumerateWindows() なんて関数も用意してくれていて、タブ上に読み込まれているページのオブジェクトも簡単に見付けられる様になっています。scope() はちょっとおかしな動作になる様ですが…… (Firefox の仕様変更で、XPCNativeWrapper によるラップが施される様になったのに対応していないみたいです。window オブジェクトを明示的に取り出せば問題なし)


この JavaScript Shell で強く感じたのが、Web ブラウザの更なる可能性でしょうか。Firefox にも CUI (テキストコマンド機能?) が搭載されるという話もありますが、ブラウザ上でのコンソールライクな処理、しかも、それは単純にブロック要素を追加しているだけ (常に末尾は textarea を含むブロック要素で固定) という発想なだけで、ことさら難しいことをしている訳ではないんですよね……


Web 関連の周辺技術の変化は凄まじいです。これで Comet が実用的になったら、Ajax から更にパラダイムシフトということになるんでしょうか。

動いてくれるかな。

hatena-mode が動いてくれなかった件。
そもそも FSF Emacs に移行してから使ってませんでした。


使ってみると、Cookie が正しく保存されてないみたいです。
最新の hatena-modeCVS から co したものに置き換えたんですが、それでも今のはてなでは使えないんでしょうか……


どうやら `rk' という名前の Cookie が、curl が保存する Cookie file にないのが問題の様なんですが、その他にも色々とありそうな予感がします……
そもそも hatena-mode ではその Cookie を利用しようとしているので、hatena-modeCookie の扱いがおかしい訳ではないのかもしれません。
多分、login で Cookie が保存できていないところに問題があるんでしょうが、それが hatena-mode の問題なのか、curl の問題なのか、はたまたはてなの仕様変更か……


取り敢えず hatena-login でやってる内容そのままに、curl で login を試みてみましたが、やはり login できません。
正しい動作はどういうものか、ということで、Firefox 上でのはてなへのログインを行って、LiveHTTPHeader で post data を確認すると、hatena-login で送っている post data と少し違っているようです。
中でもアカウントを指定する key として指定している文字列が違うのが目立ちます。
hatena-mode では `key=アカウント名' となっているのですが、ブラウザで実行したときの内容だと `name=アカウント名' になっています。
これをブラウザで動作させたときのものに合わせて curl で実行してみると、今度は `rk' を名前とする Cookie が保存されました。


これなら行けそうかな。
この記事が正しく post されたなら、一応の対応はできたってことになるでしょうかね。

という訳で、今のところは、

$ diff -u hatena-mode.el.orig hatena-mode.el
--- hatena-mode.el.orig	2005-10-28 18:14:30.000000000 +0900
+++ hatena-mode.el	2009-05-17 22:04:44.710188100 +0900
@@ -338,7 +338,7 @@
     (call-process hatena-curl-command nil nil nil 
 		  "-k"  "-c" hatena-cookie
 		  "-x" hatena-proxy
-		  "-d" (concat "key=" hatena-usrid)
+		  "-d" (concat "name=" hatena-usrid)
 		  "-d" (concat "password=" password)
 		  "-d" (concat "autologin=1")
 		  "-d" (concat "mode=enter")

としただけです。

とんでもなく久し振りに

エントリを書いてみようか、なんて思ったら Emacs から hatena-mode で書けなくなってます…

最近は、久し振りに Rails を触ってみたら (何と仕事で使える日が来るなんて… いつも無理矢理やってたのに)、何だか凄く進化してたりして、嬉しいやら苦しいやら…
Ajax なんかも要求があったりで色々と書いてみたいことがあったんですが。

しかし、SaaS だとかクラウドだとか、本当にそんなの (うちの会社で) できるの?なんて感じだったり…