About
この記事はK3 Advent Calender20日目の記事です。区切りだよ(o・∇・o)
今回はVimにはまったり、VimScriptいじったりしてみていたので、それを題材にしてみました。
今回行ったこと
- 時報機能を追加
- 作業開始時間を記録&時報を呼ぶと何分やってるかを教えてくれる
- なんとなく労ってくれる
経緯
- HaskellでghcもghciもVimから簡単に呼べるようにしたい→自作コマンドを作ろう
- あれ、Vim scriptさん凄く色々できそうじゃない?
- なんかやろう
こんな経緯からお察しの通り、私自身はここ1ヶ月くらいでVim熱が一気に高まった弱々Vimmerです。何かアドバイス有りましたら頂ければ嬉しいです。
環境
- Windows 10 Education
- Vim 8.0.596
前準備
実際にコーディングに入る前の段階です。
Scriptファイルの作成
今回はUserディレクトリの下に各種設定用のフォルダを作り、その下にmycmd.vim
を作成しました。
この中身をいじっていくのが中心となります。
.vimrcの設定
上記のことを.vimrcに教えてやり、vim起動時に有効になるようにします。
常時起動にしない場合は普通に各コマンドと同じようにコマンド打てばOKですが、私は面倒でした。
source C:\Users\XXX\setting\mycmd.vim
基礎知識
とりあえず、必要だった知識を先にまとめます。スルーしていることがあるかもしれないです…そしたらすみません………
ここから先のことは試すときはmycmd.vim
に書くのをおすすめします。
Vimスクリプト基礎文法最速マスター←この辺を参考にさせていただきました。
独自コマンドの作成
とりあえず、Hello, (o・∇・o)
と出力するだけのコマンドMco
を作ってみましょう。独自コマンドは大文字から始めなければならない規則があるようです。
command Mco echo "Hello, (o・∇・o)"
簡単ですね。command [コマンド名] [実行する内容]
です。
echoは単純に出力するやつなので、Cのprintf
とか、JavaのSystem.out.println
とか、pythonのprint
とか、JSのconsole.log
とか、そのイメージですね。
関数を作ろう&ついでに変数
Vim scriptさん、関数を定義できます。正直この辺りナメてました。すみません。
上のやつをmcoecho
とか関数にしてみます。
function s:mcoecho()
echo "Hello, (o・∇・o)"
endfunction
あまり難しいことはないですね。よくある関数定義と一緒です。function
をfunction!
と末尾にエクスクラメーションマークを付けると既存の関数を上書きしてくれるようですが、あまり既存のものを壊すのが好きではないので、なんとなく付けていません。かぶらないような名前をつけていきたい(願望)。
関数名の頭のs:
についてはこの辺りを参考にしました。要するにprivate
というか、そんなもんです。変数についての話ですが関数も大方同じみたい?
vimスクリプトの変数
変数の代入時は頭に毎度letが必要です。この辺りも注意。宣言ではなく、代入の頭に必要です。むしろ宣言は不要。一度上のリンクで確認頂くのが良いと思います。
引数を取る関数&while/for/if
引数も勿論取れます。
function s:mcoechorepeat(n)
let i = 0
while i < a:n
echo "Hello, (o・∇・o)"
let i += 1
endwhile
endfunction
引数を取るときはn
に対してa:n
のように頭に接頭辞がいります。ここだけご注意。
ぬるっと使いましたが、while
は大方の言語と似ています。括弧がいらない。
ところで閉じるときは大体endXXXX
です。BASICを思い出す。
この引数、実は可変長を取れます。...
で示します。
function s:multifunc(...)
[やること]
endfunction
こいつ、a:0
で引数の個数を、以降はa:1
, a:2
と順番に引数が格納されていきます。便利です。詳細は最初に提示したサイトさんに掲載されていました。
for
はリストの中身を回します。pythonのfor
とか各種言語のforeach
に近いですね。
for i in [1,2,3,4,5]
echo "Hello, (o・∇・o)" . i
endfor
最後にifはこんな感じです。
for i in [1,2,3,4,5]
if i % 2 == 0
echo "Even"
elseif i == 1
echo "One"
else
echo "Odd"
endif
endfor
関数を独自コマンドとして適用
超大事。これできないとあんまり意味なくなっちゃう。
command Mco call s:mcoecho()
function s:mcoecho()
echo "Hello, (o・∇・o)"
endfunction
commandで使うときはcall
が必要です。関数内で関数を呼び出す等のときはcall
なしで普段の手続き型言語のように書いてOKです。
文字の結合
文字の結合は.
です。
let strQiita = "Qii" . "ta"
って感じですね。数値とか大丈夫かな~って思ってたんですけど結構柔軟に扱ってくれます。良い子です。
モチベ維持作戦スタート
ざっくり説明したんだか他のサイトさんに丸投げしたんだかわかりませんが、とりあえず本題に入ります。以降メインで実行させる関数はs:nowtime
です。
時間を言ってもらおう
まずは時間を取得しなければなりません。strftime
という関数を使います。
どこかの言語で時刻を扱ったことのある人なら見たことはあるかもしれない形で扱えます。
let nowtime = strftime("%Y-%m-%d %H:%M:%S")
とりあえずこれで現在時間が2019-11-25 11:00:00
という感じにnowtime変数に代入されます。第2引数としてUNIX時間をつっこんでやるとその時間出したりもできます。
command Mtime call s:nowtime()
function s:nowtime()
let nowtime = strftime("%m月%d日%H時%M分")
echo ("(o・∇・o)<いま、" . nowtime . "だよ~")
endfunction
じゃあこれを使って、
これで、:Mtime
と打ってやればとりあえず時報は言ってくれるようになります。
こんな感じ。可愛いですね。
作業時間を記録しよう
記録はUNIX時間で行いました。経過時間の計算の際に、現在時刻のUNIXとの差を求めていい感じにやると楽できるかなぁと思ったので。
というところで現在のUNIX時間を取ってきましょう。localtime()
で持ってきます。
let nowUnix = localtime()
これでnowUnix
には1574698112
みたいなのが入ってくれます。
とりあえず単純に記録機能を追加させてみます。
command Mtime call s:nowtime()
let s:timerec = -1
function s:nowtime()
let nowtime = strftime("%m月%d日%H時%M分")
echo ("(o・∇・o)<いま、" . nowtime . "だよ~")
let s:timerec = localtime()
endfunction
はい、これで記録できますね。ただ無限に記録させ続けてしまう……コマンドでなんとかしたいですね。
引数にr
を取るときだけ記録させてみます。
command -nargs=? Mtime call s:nowtime(<f-args>)
let s:timerec = -1
function s:nowtime(...)
let nowtime = strftime("%m月%d日%H時%M分")
echo ("(o・∇・o)<いま、" . nowtime . "だよ~")
if a:0 >= 1
if a:1 == "r"
let s:timerec = localtime()
endif
endif
endfunction
ちょっと面倒になりました。まず1行目コマンド定義の行。
コマンドに引数を取るときは-nargs=1
のように、何個引数を取るか書かねばなりません。引数が可変の場合は-nargs=?
です。今回は0個or1個なので可変ですね。nowtime()
はnowtime(<f-args>)
と変えとく必要がある。
そして、function
側も引数に可変を表す...
を入れる必要があります。内部では引数を持つ場合1つ目のif文がTrue,その引数が"r"
であればs:timerec
にlocaltime()
を入れています。
作業終了させる
同様に、引数にb
を取るときだけ作業終了の処理をします。
これはs:timerec
と現在時刻の差を出力、s:timerec
を初期化する、の2点ができればOKです。
とりあえずs:timerec
と現在時刻の差を整形してくれる関数を作りましょう、ということで、引数に現在時刻をとって「4時間17分56秒」とか返してくれるs:getElapsedTime
を作りました。
function s:getElapsedTime(loctime)
let worktime = a:loctime - s:timerec
let wsec = worktime % 60
let worktime = worktime / 60
let wmin = worktime % 60
let whour = worktime / 60
let strtime = wsec . "秒"
if whour > 0
let strtime = whour . "時間" . wmin . "分" . strtime
elseif wmin > 0
let strtime = wmin . "分" . strtime
endif
return strtime
endfunction
特に難しいことはなく。let
とif
文べたーっと書いてreturn
しているくらいです。return
は関数の戻り値を指定する、多くの言語であるやつです。
これを使って、(o・∇・o)
に経過時間を喋らせてみましょう。
elseif a:1 == "b"
if s:timerec == -1
echo("(o・∇・o)<時間が記録されてないよ~")
return
end
let strtime = s:getElapsedTime(loctime)
echo ("(o・∇・o)<いま、" . nowtime . "! " . strtime . "作業したよ~おつかれさま~")
let s:timerec = -1
s:timerec
は記録されていないときに-1
にしています。スタートしてないのに終了しようとしたら時間が記録されてないよ~
と煽ってあげましょう。
正しく実行されるとこんな感じになります。
可愛いですね。
単に時間を出力するコマンドの修正
こうなると、単に時間を出力させるのも味気ないですし、そのときに今何分やったかを知らせてほしいですね。
ついでに、先のコードだと、2回も時間を出力させられてしまうので、そこも上手く修正したいところです。
ざっくり、s:nowtime()
関数の概形はこんな感じにしましょう。
let s:timerec = -1
funtion s:nowtime(...)
if [引数が1つ以上]
if [引数が"r"]
" 記録スタート
elseif [引数が"b"]
" 記録終了
end
else
if [時間が記録されていない]
" 単に時間を出力
else
" 時間と一緒に経過時間も表示
end
end
endfunction
完成!
これらを踏まえて最終的なs:nowtime()
はこうなります。
let s:timerec = -1
function s:nowtime(...)
let nowtime = strftime("%m月%d日%H時%M分")
let loctime = localtime()
if a:0 >= 1
if a:1 == "r"
echo ("(o・∇・o)<すたーと~ いま" . nowtime . "です~がんばっていこ~")
let s:timerec = loctime
elseif a:1 == "b"
if s:timerec == -1
echo("(o・∇・o)<時間が記録されてないよ~")
return
end
let strtime = s:getElapsedTime(loctime)
echo ("(o・∇・o)<いま、" . nowtime . "! " . strtime . "作業したよ~おつかれさま~")
let s:timerec = -1
end
else
if s:timerec == -1
echo ("(o・∇・o)<いま、" . nowtime . "だよ~")
else
let strtime = s:getElapsedTime(loctime)
echo ("(o・∇・o)<いま、" . nowtime . "! " . strtime . "作業したよ~がんばっていこ~")
end
end
endfunction
結論
- 可愛い
- 進捗は言うても変わんない
- でも可愛い