MacのTerminal.appで新しいタブを開くときに

Macでは標準付属のターミナルを使っているのですが、すこし困ったことがあります。新しいタブを開くと、カレントディレクトリがホームディレクトリになっているのです。Gnomeのターミナルだともとのタブでのカレントディレクトリと同じところに移動するのですが、そちらの方が便利だと思うので調べてみました。お急ぎの方は最後のスクリプトだけ見ていただければいいと思います。



ぐぐってみると、いろいろ引っかかる。まず見つけたのはこれ

なんだか不自然な日本語に翻訳されちゃってますが、このページのAnswer:61194に書いてあるスクリプトを読み込ませておけば、twdを実行することで新しいタブを開いて、同じディレクトリをカレントディレクトリにしてくれます。AppleScriptというものを使ってターミナルにCmd+Tを仮想的に打ったことにさせてタブをひらかせているみたいです。ちなみにもとのページはどうやらこれのようです。




これで満足すれば問題無いのですが、自分はCtrl+Tでそれが呼び出せたら嬉しいなと思いました。zshで定義した関数をbindkeyで関連付けるにはzleというものを呼びます。

そこで、

zle -N new_terminal_working_directory
bindkey "^T" new_terminal_working_directory

としてCtrl-Tと打ってみました。すると…あまりうまくいきませんね。「あまり」というのは、キーを打ってすぐに離せばちゃんと新しいタブが開くのですが、押しっぱなしだとうまくいきません。これは困りました。理由は詳しくは不明ですが、上のスクリプトは新しいタブを開くときにCmd-Tを呼び出すことで新しいタブをひらかせているので、なにかキーを押していると干渉してしまうのでしょう。そこでCmd-Tを呼ばないで新しいタブを開く方法を考えましょう。








ぐぐってみても、ほとんどがCmd+Tを呼び出すものなのですよね…そこで見つけたのがこのページ

これは、ターミナルのメニューアイテムを呼んで新しいタブをひらかせているみたいです。でも、そのままだとうまくいきません。日本語環境だからというのもあるようです。そこで書いたのがこのスクリプト

-- `menu_click`, by Jacob Rus, September 2006
-- 
-- Accepts a list of form: `{"Finder", "View", "Arrange By", "Date"}`
-- Execute the specified menu item.  In this case, assuming the Finder 
-- is the active application, arranging the frontmost folder by date.

on menu_click(mList)
	local appName, topMenu, r
	
	-- Validate our input
	if mList's length < 3 then error "Menu list is not long enough"
	
	-- Set these variables for clarity and brevity later on
	set {appName, topMenu} to (items 1 through 2 of mList)
	set r to (items 3 through (mList's length) of mList)
	
	-- This overly-long line calls the menu_recurse function with
	-- two arguments: r, and a reference to the top-level menu
	tell application "System Events" to my menu_click_recurse(r, ((process appName)'s (menu bar 1)'s (menu bar item topMenu)'s (menu topMenu)))
end menu_click

on menu_click_recurse(mList, parentObject)
	local f, r
	
	-- `f` = first item, `r` = rest of items
	set f to item 1 of mList
	if mList's length > 1 then set r to (items 2 through (mList's length) of mList)
	
	-- either actually click the menu item, or recurse again
	tell application "System Events"
		if mList's length is 1 then
			click parentObject's menu item f
		else
			my menu_click_recurse(r, (parentObject's (menu item f)'s (menu f)))
		end if
	end tell
end menu_click_recurse


my menu_click({"Terminal", "シェル", "新規タブ"})

これをosascriptで呼び出せば確かに新しいタブが開きます。ただ、ちょっとコードが長いですね…スクリプトエディタで実行結果をしらべて、ちょっといじくったりしたところ、こんなコードに行き着きました。

tell application "System Events" to click menu item 2 of menu 1 of menu bar item 3 of menu bar 1 of application process "Terminal"

短いですね!しかもシェルとか新規タブとか無いので英語環境でも多分大丈夫です。
それでは、これを使ってzshのCtrl-Tに関連付けるにはどうすればいいかというと

# alias twd=new_terminal_working_directory 
function new_terminal_working_directory() { 
  osascript > /dev/null << --END
    tell application "System Events" to click menu item 2 of menu 1 of menu bar item 3 of menu bar 1 of application process "Terminal"  
    delay 0.1
    tell application "Terminal"
      do script "cd $(pwd)" in first window 
    end tell
--END
}

zle -N new_terminal_working_directory
bindkey "^T" new_terminal_working_directory 

こうなります。これを.zshrcなりに書いておきましょう。






メニューアイテムを呼び出す方法は他のアプリケーションでも通用するので有用そうですね。iTermでも同様にかけるでしょう。AppleScriptはもうちょっと調べても良いかもしれない。





追記:これじゃ何かコマンド実行中のときは実行できないじゃないか…何かいい方法ないかな…