vimコマンド出力をクリップボードへコピー

vimコマンド出力をクリップボードへコピー*1するためには以下のようにすればいいみたい。
例えばノーマル、ビジュアル、演算待ち状態時のキー定義一覧(:map)をクリップボードへコピーするためには以下の3つのコマンドを実行する。

:redir @*>
:silent map
:redir END

silentは付けとかないと画面へも出力されるので鬱陶しい。
もちろん

:redir @*>
:silent map
:silent map!
:redir END

みたいに複数のコマンドを挟んでも可。':redir @*>'の部分を':redir @a>'にすると、レジスタaに代入される。また、':redir @*>>'にするとクリップボードに追記される。詳しくは:help redirect参照。

こんなもの忘れるわ!
という人はユーザvimrcファイルに以下のようなコマンドを追加しておくといいかも(1コマンドの出力をクリップボードへコピー)

func! s:func_copy_cmd_output(cmd)
	redir @*>
	silent execute a:cmd
	redir END
endfunc

command! -nargs=1 -complete=command CopyCmdOutput call <SID>func_copy_cmd_output(<q-args>)

コマンドライン補完も働いて結構便利。(12/01/13修正 call s:→call )

ユーザvimrcファイルの実行時間を1usでも速くしたい人や無駄なメモリは1バイトも許容できない人は関数をautoloadスクリプト内に書くといい。

*1:クリップボードへ貼り付けの方が表現としては正しい??クリップボードvimコマンド出力を転送。とした方が曖昧ではないかも。

vimでオプションの値を貼り付ける

こんなことできないかなーとヘルプをまさぐってたら出来たのでメモ。

vimでオプション(例:runtimepath)の値を知るためには

:set runtimepath

とすればいいが、これの中身を貼り付けたい場合は
●Normalmode時

"=&runtimepath<CR>
p

はEnter押すと言う意味

●Insertmode時

<C-r> =&runtimepath

はCtrlキーを押しながら"r"キーを押すと言う意味。

とすればいい。オプションの値によってはではなく としないといけないかも。

なお、テキストに貼り付けずに"*"レジスタ(クリップボード)へコピーするためにはNormalモードで

:let @*=&runtimepath

とすればおk。奥が深すぎるぜ。

File.DateLastModifiedで 嵌まった事。

以下のようなソースコードを書いていて、原因不明のエラーが出て嵌まった。という話。

// このソースコードは正常に動作しない
var g_objFileSys = new ActiveXObject("Scripting.FileSystemObject");

function copy_file(src_fname, dst_fname)
{
	try
	{
		if ( !g_objFileSys.FileExists(src_fname) )
		{
			WScript.Echo( src_fname + "が存在しません" );
			return null;
		}
		// フォルダ名指定の場合、ファイル名付加
		if ( g_objFileSys.FolderExists(dst_fname) )
			dst_fname = g_objFileSys.BuildPath( dst_fname, g_objFileSys.GetFileName(src_fname) );
		if ( g_objFileSys.FileExists(dst_fname) )
		{
			var obj_src = g_objFileSys.GetFile(src_fname);
			var obj_dst = g_objFileSys.GetFile(dst_fname);
			var date_1 = obj_src.DateLastModified;	
			var date_2 = obj_dst.DateLastModified;
			var time_diff = date_1.getTime() - date_2.getTime();		//←ここでエラー
			if ( obj_src.Size == obj_dst.Size 
				&& Math.abs(time_diff) <= 2000 )
				return dst_fname;
		}
		g_objFileSys.CopyFile( src_fname, dst_fname, true );
		return dst_fname;
	}
	catch(e)
	{
		WScript.Echo( e + "(" + (e.number&0xFFFF) + ")" + e.description );
		return null;
	}
}

copy_file(WScript.Arguments(0), WScript.Arguments(1) );

要するに、ファイルコピーする際にサイズが一緒で、ファイル更新日時が2秒以内ならばコピーしない。ということがしたいわけですが、何故かDate.getTime()の所で「[object Error](5007)オブジェクトを指定してください。」となってしまう。
「もしかして、ヘルプが間違ってて、getTime()というメソッドがないのか?」と思って

var date_1 = new Date();
WScript.Echo(date_1.getTime());

としてみると、エラーにならない←当たり前だ。
違いといえば、デバッガで変数をウォッチすると、エラーになる方は"2011/12/02 13:54:00"。ならない方は"Mon Dec 12 21:47:08 UTC+0900 2011"となんかフォーマットが違う。
「DateLastModifiedプロパティはDateオブジェクトじゃないのかも?」と思って、以下のように修正してみた。

			//var date_1 = obj_src.DateLastModified;	
			//var date_2 = obj_dst.DateLastModified;
			//var time_diff = date_1.getTime() - date_2.getTime();	// ここでエラー
			var date_1 = Date.parse(obj_src.DateLastModified);
			var date_2 = Date.parse(obj_dst.DateLastModified);
			var time_diff = date_1 - date_2;						// OK

これだと動く。time_diffに入る値もそれっぽい。
どうもDateLastModifiedは日時を表す特別なオブジェクトみたい*1。DateLastModifiedをDateオブジェクトとして扱いたい場合は

			//var date_1 = obj_src.DateLastModified;	
			//var date_2 = obj_dst.DateLastModified;
			var date_1 = new Date(obj_src.DateLastModified);
			var date_2 = new Date(obj_dst.DateLastModified);
			var time_diff = date_1.getTime() - date_2.getTime();	//OK

このようにするといいみたい。
MSDNライブラリ 2001年10月リリースのDateオブジェクトのコンストラクタの説明によると

dateObj = new Date()
dateObj = new Date(dateVal)
dateObj = new Date(year, month, date[, hours[, minutes[, seconds[,ms]]]])
引数
dateObj
必ず指定します。Date オブジェクトを代入する変数名を指定します。
dateVal
必ず指定します。数値で指定する場合は、世界協定時刻 (UTC)での指定する日付と 1970 年 1 月 1 日 0 時 0 分 0 秒との間をミリ秒単位の数値で指定します。文字列で指定する場合は、指定した文字列が parse メソッドでの規則に準じて解析されます。引数 dateVal には、ActiveXR オブジェクトから取得した VT_DATE 値を指定することもできます。
(以下略)

ということで、DateオブジェクトのコンストラクタのdateValとして扱えるということは、DateLastModifiedプロパティは 1970/1/1 0:00:00からのミリ秒単位の数値として振る舞えるみたいなので、、最初のサンプルコードで

			//var time_diff = date_1.getTime() - date_2.getTime();	// ここでエラー
			var time_diff = date_1 - date_2;						// OK

と書いておけば嵌まることはなかった。という悲しい事実。

スクリプト言語って型が動的に決定/変換されるから、スクリプトエンジンを信じた方がいいんだろうけど、どうしてもコンパイル時に型が確定している言語に慣れて身からすると、ついついそれを指示したくなってしまう。例えば、

var a,b;
a = "25";
b = "5";
WScript.Echo( a - b );

こんなコードは気持ち悪くって吐きそうだから、変数a,bをどうしても文字列として扱わないといけなくて、数値の引き算もしたいなら、引き算の段階で数値型に確実に戻そうと

var a,b;
a = "25";
b = "5";
WScript.Echo( a.valueOf() - b.valueOf() );

としてしまいがち。頭を切り換えていった方がいいのかな?

*1:Visual Studio 2010では型は"Date"と表示される。JScriptのDateオブジェクトの型は"Object"となってる。これ以上はどう調べたら良いのか分からない

JScript(WSH)の構文チェック

今日も引き続きJScript(WSH)の話題。
例えば以下のようなコード(sample.js)を書いて、デバッグすると

// strをmin_lenの長さにする。長さが足りない場合は先頭に"0"を付加
function fill0_left_str(str, min_len)
{
	var ret = str.toString();
	while ( ret.length < min_len ) ret = "0" + ret;
	return ret:
}

// 現在の日付を年(4桁)月(2桁)日(2桁)で表示
var date_1 = new Date();
var s_date;
s_date = fill0_left_str( date_1.getYear(), 4 );
s_dete += fill0_left_str( date_1.getMonth() + 1, 2 );
s_date += fill0_left_str( date_1.getDay(). 2 );
WScript.Echo(s_date);

sample.js(5, 12) Microsoft JScript コンパイル エラー: ';' がありません。

てなエラーが出ます。んで、5行目の:→;と修正して再度デバッグすると

sample.js(13, 44) Microsoft JScript コンパイル エラー: 識別子がありません。

てなエラーが・・・。
実行時エラーならともかく構文的なエラーはデバッグ前に修正しておきたいところです(厳密にはJScriptでは変数が宣言されてないのは実行時エラーに分類されるみたいですが)
こういう場合、Microsoft .NETのjsc.exeを使うと便利です。

>C:/WINDOWS/Microsoft.NET/Framework/v4.0.30319/jsc.exe /fast- /warnaserror+ sample.js

こんな感じで実行します。(パスは各自環境で異なると思うので、ファイル検索して下さい)
/fast-の意味は、厳密にはこの実行ファイルはJScriptではなくJScript.NETコンパイラなので、/fast-を付けておかないと、JScript(WSH)の独自拡張?プロパティやメソッドを使ったときにエラーが出ます(RegExp.$1とか)。
また、/warnaserror+の意味はexeファイルが作られるのを防ぎます*1
実行すると

sample.js(5,12) : error JS1004: ';' が必要です。
sample.js(13,44) : error JS1010: 識別子が必要です。
sample.js(12,1) : error JS1135: 変数 's_dete' が宣言されていません。
sample.js(14,1) : error JS1135: 変数 'WScript' が宣言されていません。

というメッセージが表示されるので、1つずつ修正していけばOK。
「WScriptが宣言されてません。」というエラーは気にしちゃ負けです。あと、Visual C++コンパイラとかと比較するとさほど賢くないのか、「変数が初期化されていない可能性があります」というエラーがよく出ます。これも場合によっては気にしなくても大丈夫です。

vimを使ってる人へ

vimを使ってる人はruntimepath*2/ftplugin/javascript.vim

setlocal makeprg=C:/WINDOWS/Microsoft.NET/Framework/v4.0.30319/jsc.exe\ /fast-\ /warnaserror+\ %
setlocal errorformat=%f(%l\\,%c)\ :\ %t%*\\D%n:%m

などと書いておくと幸せになれるかも*3
コード編集し終わったら、保存した上で

:make

とすると最初のエラーに飛んでくれます。後は:cn、:cpなどのコマンドを使って修正していけばいいです。
WScriptが宣言されてないエラーが気になる人はバッチファイルとかでその行を抜くように頑張って下さい(私は今のところ気にならないんで。)

*1:通常JScript(WSH)を使うと、WScriptオブジェクトを使用しますが、これの宣言がないという警告がエラーとして扱われて、exeができない

*2:Windows環境の人は~/vimfiles、unix環境なら~/.vimなど。:set runtimepathをすれば、自分の環境のruntimepathが分かる

*3:本当のjavascript使ってる人は新しいFileType作ったりしないと今の設定とあたる可能性があります

JScriptのデバッグ

最近、JScript(WSH)でスクリプト書いてるんですが、そのデバッグ方法について。
多分Visual Studioがインストールされてないと駄目だと思う。(Expressでいけるかどうかは不明)
今までやってたのは

WScript.Echo(variable);

とかってやって変数を出力することですが、何時の時代のデバッグ方法やねん。て感じだし、作業効率もよろしくない
で。なんか無いかな−とグーグル先生に聞いてみたけど、キーワードが悪いのか「これだ!」というのがない。藁にもすがる思いでWSHの実行プログラムであるcscriptのコマンドラインオプションを調べてたら

使い方 : CScript scriptname.extension [オプション...] [引数...]

オプション :
//B         バッチ モード : スクリプトのエラーおよびプロンプトを非表示にする
//D         アクティブ デバッグを使用可能にする
//E:engine  スクリプト実行時にエンジンを使用する
//H:CScript 既定のスクリプト ホストを CScript.exe に変更する
//H:WScript 既定のスクリプト ホストを WScript.exe に変更する (既定値)
//I         対話モード (既定値、//B と逆の動作)
//Job:xxxx  WSF ジョブを実行する
//Logo      ロゴを表示する (既定値)
//Nologo    ロゴを表示しない : 実行時に見出しを表示しない
//S         このユーザーの現在のコマンド ライン オプションを保存する
//T:nn      秒単位のタイムアウト時間 :  スクリプトを実行できる時間の最大値
//X         デバッガでスクリプトを実行する
//U         コンソールからリダイレクトされた I/O に Unicode を使用する

おー。あるじゃん。ということで、

>cscript //X hoge.js

こんな感じで起動すると

こんな画面が出るんで、適当なやつを選択してOKを押す。
私は何となく、Visual Studio 2010を選択してます
デバッグ中に編集は出来ますが、エディットコンティニューは不可です(ウチの環境だけかも知れないが、編集すると強制終了することがあったんで、編集しない方が無難かも)。
他はブレークポイント貼ったり、ウォッチ変数追加したり、「次のステートメントを設定」まで出来ます。
デバッグの中断はデバッガで[デバッガの中断]とやっても中断してくれないっぽいので、スクリプトを実行してるコマンドライン窓を閉じるといいみたい。
あと、ブレークポイントは実行する度に初期化されるみたいなので、注意が必要です。
キーワードとか行数が分かってるなら、キーボードマクロとか登録しておくと便利かも。
・・・デバッグが必要なほどのスクリプト書くなら他の言語(rubyとかPythonとか)使うって方法が正道なんじゃ?とかいう突っ込みはなしの方向で