TextWrangler上でmakeしたい
TextWranglerをひとしきり褒めた?ところで、本題。TextWrangler上で統合開発環境のような勝手でmakeしたいという話。
"Run current line as shell command"みたいなのを作ってEmacsのターミナルモードもどきも考えたのですけれど、前にも書いた気がしますがWindows出身の私としてはTerminal.appに充分に満足してまして、正直これでいいじゃんと思ってます。なのでこの話は置いておいて。
やりたいこと
TextWrangler上で特定キーを押す→Terminal.app上でmakeが走る→TextWrangler上でエラー位置にキャレットを移動
この流れを1アクションでやりたい。
何をどう組み合わせるか
TextWrangler上で特定キーを押す→OSXの機能でTextWranglerのメニューに出てるAppleScriptを起動→AppleScriptからTerminal.appにコマンドを投げる→Terminal.app上でmakeが走る→エラー位置を切りだしてeditコマンドを実行→TextWrangler上でエラー位置にキャレットを移動
この赤字の部分を作ればOK。
AppleScriptからTerminal.appにコマンドを投げる部分(make.scpt)
tell application "TextWrangler" set saved to false set n to count text document if n > 0 then set saved to on disk of front text document if saved then set the_file to file of front text document end if end if if saved then set makefile_exists to false set cmd to "make" tell application "Finder" set the_parent to parent of the_file repeat if POSIX path of (the_parent as alias) = "/" then exit repeat end if -- .#makefile (Finder can not take hidden file) set makefile_path to (POSIX path of (the_parent as alias)) & ".#makefile" if ((do shell script "test -f " & quoted form of makefile_path & " ; echo $?") as integer) = 0 then set makefile_exists to true set cmd to "make -f .#makefile" exit repeat end if -- makefile set makefile_path to (the_parent as string) & "makefile" -- ":" format if file makefile_path exists then set makefile_exists to true exit repeat end if -- next set the_parent to parent of the_parent end repeat end tell if makefile_exists then try -- treat makefile_path as alias, raise an error when the normal file set link to do shell script "readlink " & quoted form of makefile_path -- extract dir set make_body to (POSIX path of (the_parent as alias)) & link set rev_make_body to (reverse of characters of make_body) as text set delimiter to offset of "/" in rev_make_body set rev_parent to text delimiter thru (length of rev_make_body) of rev_make_body set dirname to (reverse of characters of rev_parent) as text -- removing "." or ".." (slash of "$PWD/" is necessary) set dirname to do shell script "cd " & quoted form of dirname & " && echo $PWD/" on error set dirname to POSIX path of (the_parent as alias) -- with last slash end try set cd_cmd to "cd " & (quoted form of dirname) & " # compiling" tell application "System Events" set terminal_launched to exists (application processes whose bundle identifier is "com.apple.Terminal") end tell tell application "Terminal" if terminal_launched then set existed to false repeat with the_window in windows set the_tab to tab 1 of the_window if (history of the_tab) contains cd_cmd then set existed to true do script "clear" in the_window do script cmd in the_window -- activate exit repeat end if end repeat if not existed then do script cd_cmd set new_window to front window do script cmd in new_window activate end if else repeat set n to count window if n > 0 then exit repeat end if delay 0.1 end repeat set new_window to window 1 do script cd_cmd in new_window do script cmd in new_window activate end if end tell else display dialog "makefileが見つかりません" end if else display dialog "TextWrangler上でファイルが保存されていません" end if activate end tell
エラー位置を切りだしてeditコマンドを実行する部分(autojump)
#!/usr/bin/env ocaml if Sys.command "which edit" > 0 then ( print_string "command-line tools of TextWrangler were not intalled.\n"; exit 1 );; if Array.length Sys.argv <= 1 then ( print_string "usage: autojump make -w\n"; exit 1 );; let cmd = let b = Buffer.create 256 in let last = Array.length Sys.argv - 1 in for i = 1 to last do Buffer.add_string b Sys.argv.(i); if i < last then Buffer.add_char b ' ' done; let s = Buffer.contents b in if s = "make" then "make -w" else s;; let log = Filename.concat (Sys.getenv "TMPDIR") "autojump.log";; let (_: int) = Sys.command (cmd ^ " 2>&1 | tee \"" ^ log ^ "\"");; #load "str.cma";; let re_cd = Str.regexp "^make\\(\\[[0-9]+\\]\\)?: Entering directory `\\(.*\\)'$";; let re1 = Str.regexp "^\\([^:]+\\):\\([0-9]+\\): ";; (* gcc *) let re2 = Str.regexp "^File \"\\([^\"]+\\)\", line \\([0-9]+\\)";; (* ocaml *) let f = open_in log in try let edit dir file line = ( Sys.chdir dir; let command = "edit " ^ file ^ ":" ^ line in print_string command; print_newline (); ignore (Sys.command command) ) in let rec loop ~dir = ( let line = input_line f in if Str.string_match re_cd line 0 then ( let entering_dir = Str.matched_group 2 line in loop ~dir:entering_dir ) else if Str.string_match re1 line 0 then ( let file = Str.matched_group 1 line in let line = Str.matched_group 2 line in edit dir file line ) else if Str.string_match re2 line 0 then ( let file = Str.matched_group 1 line in let line = Str.matched_group 2 line in edit dir file line ) else ( loop ~dir ) ) in loop ~dir:"." with End_of_file -> close_in f;;
シェルスクリプトで書けなかった私にはUNIX使いを名乗る資格は無い。
使い方
make.scptをTextWranglerのScripts Folderに入れます。(キーボードショートカットを振る)
make.scptを動かすと、ターミナルをアクティブにして、現在開いているファイルと同じかそれより上の位置にあるmakefile(または.#makefile)を使ってmakeして、再びTextWranglerをアクティブにするところまでやってくれます。同じディレクトリで繰り返す場合はウィンドウは使いまわします。これだけではエラー位置には飛びません。
エラー位置に飛ぶにはmakefileの方でautojump経由でコンパイルする必要があります。.#makefile(.#で始まる名前はsubversionのグローバル無視ファイルですのでこの名前)があればmakefileより優先して使いますので、autojump make -wとでもしておけばOKです。めんどい場合はAppleScript側のset cmd to〜を弄ってください。あれ、なんで今やってないんだっけ……。
ソース位置とmakefileのある位置が違う場合は、makefileへのシンボリックリンクを.#makefileの名前でソース位置に置けば辿ります。AppleScriptからのファイル操作はtell application "Finder"で行ないますので、隠しファイル扱えないんですよ……ひそひそ……。do shell scriptでtest -fやreadlinkを呼んでます、もっと賢い回避策無いかな……。