Quantcast
Channel: Vimタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 5608

指定時間にコマンドを実行したいのでもうちょっと頑張った

$
0
0

先日、どうしてもdroidVimのためにvimscriptでcronを実装したいのでアラームスクリプト、"vialarm"を作った話をしたのですが、スクリプトの出来は全く満足できるものではありませんでした。問題のすべてはVim自身が持っているautocmdの情報と、vialarmが持っているautocmdの情報を同期させる必要があるためでした。前エントリを書き、本業の金物屋の破綻したスケジュールを案じながら床についてから気がついてしまったんです。

「…どうしてvialarmがタイマー情報を持ってないとだめなんですか?」

ヨシ!

autocmd/vialarm.vim
scriptencoding utf-8function!s:timerStart() abort
    lets:vialarmTimer= timer_start((60-(localtime()%60))*1000,function('s:getAlarm'))ifv:vim_did_enter
        echo '[vialarm]: Start vialarm.'endifendfunctionfunction!s:timerStop() abort
    call timer_stop(s:vialarmTimer)
    unlet s:vialarmTimer
    echo '[vialarm]: Stop vialarm.'endfunctionfunction!s:getAlarm(timer) abort
    let now = strftime('%H:%M', localtime())let autocmdText = execute('autocmd User')ifmatch(autocmdText,'^\s\+vialarm_'.now)>=0
        execute 'doautocmd User''vialarm_'.now
    endifif timer_info(s:vialarmTimer)[0].repeat >0lets:vialarmTimer= timer_start(60000,function('s:getAlarm'),{'repeat':-1})endifendfunctionfunction!s:addOneshot(time, command) abort
    tryifmatch(a:time,'^\d\d:\d\d$')<0throw'vialarm_E01'elseif matchstr(a:time,'^\d\d')>23|| matchstr(a:time,'\d\d$')>59throw'vialarm_E02'elseifa:command==# ''throw'vialarm_E03'endif

        execute 'augroup vialarm_oneshots'
        execute 'autocmd User''vialarm_'.a:time'++once'a:command
        execute 'augroup END'

        echo printf('[vialarm]: Added alarm in %s.',a:time)catch/vialarm_E01/echoerr'[vialarm]: Error: Time format must be ''HH:MM''.'catch/vialarm_E02/echoerr'[vialarm]: Error: No such time. :('catch/vialarm_E03/echoerr'[vialarm]: Error: Command is empty.'endtryendfunctionfunction!s:showAlarms() abort
    let autocmdText = split(execute('1verbose autocmd User'),'\n')[1:]let currentGroup =''let isHit =0letverbose=0

    echohl Title
    echo '[vialarm]: Current alarm is...'
    echohl None

    for txt in autocmdText
        ifmatch(txt,'^\S')>=0let currentGroup = txt
        elseifmatch(txt,'^\s\+vialarm')>=0if currentGroup !=# ''
                echohl Title
                echo currentGroup
                echohl None
                let currentGroup =''endif
            echo txt

            let isHit =match(txt,'\d\d:\d\d$')>=0letverbose=1elseif isHit
            echo txt
            let isHit =0elseifverbose
            echo txt
            letverbose=0endifendforendfunctionfunction!s:showTimerInfo() abort
    echohl Title
    if exists('s:vialarmTimer')
        echo '[vialarm]: Vialarm is running. Current timer information is...'
        echohl None

        let timerState = deepcopy(timer_info(s:vialarmTimer)[0])forkin keys(timerState)->filter('v:val !=? "callback"')
            echo printf('  %-10S: %d',k, timerState[k])endforelse
        echo '[vialarm]: Vialarm was stopped.'
        echohl None
    endifendfunctionfunction! vialarm#main(args, isBang) abort
    ifa:isBang==# ''ifa:args!=# ''let time = matchstr(a:args,'^\S*')let command = matchstr(a:args,'\s\zs.*$')calls:addOneshot(time, command)elsecalls:showAlarms()endifelseifa:args==? 'start'calls:timerStart()elseifa:args==? 'stop'calls:timerStop()elseifa:args==# ''calls:showTimerInfo()else
            echo '[vialarm]: Usage `:Vialarm! [(start|stop)]`'endifendifendfunction
plugin/vialarm.vim
scriptencoding utf-8

command -bang -nargs=* -complete=command Vialarm call vialarm#main(<q-args>,'<bang>')

Vialarm!start

ご安全に!

というわけでvialarm自身はタイマーを回す機能とautocmdからvialarmに関するものピックアップするだけまで機能を削ぎ落としました。機能が削ぎ落とされたのでコマンドの記述も相変わらずbangで詰め詰めになっています。

bangなしの:Vialarm

bangなしではvialarmが発火させるオートコマンドに関する操作を行います。

引数なしの起動でパターン名に'vialarm-'のprefixがついたユーザーコマンドを列挙します。内部的には:1verbose autocmd Userを実行して表示をコントロールしているので、登録元も分かるようになっています。

引数として{time} {command}を取った場合は{time}のエラーを弾いた後、:autocmd vialarm-oneshot User vialarm_{time} ++once {command}を実行します。

bang付きのVialarm!

bang付きではvialarmが起動したタイマーに関する操作を行います。

引数なしでは現在のタイマーの情報が出力されます。

引数として(start|stop)を取った場合、それなりにタイマーをスタート/ストップさせます。

どうして関数が増えたのですか?

vialarmがアラームの情報を持たないので、その都度autocmdから情報を引き出す手順が必要になりました。とはいえs:getAlarm()関数の仕事はむしろ減って、autocmdがonceかどうかも判断せず、:autocmd Userの結果から現在時刻のアラームを検索して発火させるだけになりました。vialarmがタイマーを管理する手順を省いたのでアラーム情報を解析しやすい状態にしておく必要もなくなり、vialarmグループからも解放されました。この時点ではスクリプトの行数は減っていたんです。

問題は、逆に:autocmd vialarmのような方法でお手軽にアラームの情報を引き出せなくなってしまったんです。結局より密な解析をするための関数、s:showAlarms()関数を用意しました。

s:showAlarms()関数

autocmdの列挙モード内でUserイベントを列挙する場合、パターンにマッチパターンが使えません。つまり、:autocmd Userによる結果が何行になるか曖昧なクッソ出力をうまく切り出す必要があります。そのためのisHit?あとついでなのでそのためのverboseif~elseif~elseif~でぺそぺそと分岐を何とかするなら、いっそここでコントロールする行が増えたって別段かめへんかという感じで:1verboseを足しました。

s:showTimerInfo()関数

vialarmが満足する形で動くか否かに関しては、s:showAlarms()関数の時点で満足したのですが、:Vialarmコマンドに機能を詰め込む過程でbang付き、引数なしの状態が余ったので機能を追加するという本末転倒なことをしてしまっています。まぁ、タイマーの開始/終了に引数を使って:Vialarm! stopのような形をとったので、引数なしならタイマー情報が出てくるのが自然でしょ、というノリです。当初引数無しで「タイマーのトグル」をしようかとか考えましたが、冷静になって現在の状態も引き出せずにアラームをトグルさせようとするのは、人間としての資質に関わることに気がついたのでやめました。

どうして?……どうしてですかね……

久しぶりにガッツリvimscriptが書きたかったので、vim充出来たことに満足しています。gitに関しては公開するプラグインには何が必要かよくわかっていないのでまずそれを調べてから…


Viewing all articles
Browse latest Browse all 5608

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>