AppVeyor で Go のバージョンを固定する方法
基本的に Go アプリのテストは以下を参考にしたら概ねうまく言ってたんだけど、 (最新じゃなく)バージョン固定してる場合に問題となったため書いておく。
起こった問題
最近 AppVeyor の Go のバージョンが上がって Go 1.10 をデフォルトで使うようになった。
上記記事の設定だと GOROOT
の定義が抜けてるために以下のようなエラーが出るようになった。
compile: version "go1.10" does not match go tool version "go1.9.4"
対応策
GOROOT
設定すればいいだけ。
今開発してる Volt というツールでは Go 1.9 でテストしたかったので以下のように指定した。
他のバージョンまたはアーキテクチャでテストしたい場合は C:\go19
じゃなく別のパスにする必要がある。
volt/.appveyor.yml at feabe7feeecf2bddda135cc7e43cc188ed781d81 · vim-volt/volt · GitHub
差分は見る必要ないと思うけど一応こんな対応をした。
[AppVeyor] Fix go version mismatch by tyru · Pull Request #204 · vim-volt/volt · GitHub
解説
Vim プラグインのパッケージマネージャなので Vim のインストールとか色々書いてあるけど、 以下の記述だけ参考にすればいい。
GOPATH
の設定GOROOT
の設定PATH
の設定set PATH=%GOPATH%\bin;%GOROOT%\bin;%PATH%
後は go build
やら go test
するだけ。
go build
はビルドが通る事を確認するためのもの。
go test
はテストするためのもの。
詳しくは上の設定見てください。
どうでもいいけどビルドが通ることを確認するなら Build configuration | AppVeyor を使った方がいい気がする。 まぁそれは後で。
参考リンク
ロガー用 vital モジュールを作った (ただし Vim 8 専用)
まだ vim-jp/vital.vim には PR してないけど十分使える (使えてる)。
https://github.com/tyru/nesk.vim/blob/0775e7d6fb3502ce52b64911d84b19cde32825aa/autoload/vital/__nesk__/Nesk/Log.vim https://github.com/tyru/nesk.vim/tree/0775e7d6fb3502ce52b64911d84b19cde32825aa/autoload/vital/__nesk__/Nesk/Log
ちなみに使ってるのはここら辺。
https://github.com/tyru/nesk.vim/blob/9d0422c3ac064183d64a137116ab305786c99248/autoload/vital/__nesk__/Nesk.vim#L21-L36 https://github.com/tyru/nesk.vim/blob/9d0422c3ac064183d64a137116ab305786c99248/autoload/vital/__nesk__/Nesk.vim#L395-L420
注意点
- Vim 7.4 には対応してない (Vim 8 専用)
Log.XXX
を実装する場合logger.flush()
を読んだ時点で出力するlogger.log(level, msg)
を読んだ時点では出力しないLog.File
で1行ずつwritefile()
したらめっちゃ遅かったので flush 前提の API とした
- 遅い場合は
{autoflush: 0}
を指定する- デフォルトでは毎行 flush する
- それでも遅い、特に
Log.File
が遅い場合は{file_redir: 1, autoflush: 0}
を指定するwritefile()
での追記が遅い ので:redir
を使える場合は指定すると速くなる- デフォルトじゃないのは
:redir
はネストして使用した場合にエラーとなるため、 プラグインが:redir
を使用している可能性があるのでデフォルトはwritefile()
の追記で実装している
出力先
Log.File
: ファイル出力ロガーLog.Echomsg
::echomsg
出力ロガーLog.Nop
: 何も出力しないロガー
Log.Nop
は logger.log(msg)
を呼ぶのに if
で分岐せずに済むようにするため。
レベルによらず全く出力させたくない場合はこの出力先にする。
上記のモジュールのオブジェクトは new() で生成できる。
Log.new({'output': 'File'})
file_path
(String
)- 例: '/path/to/logfile'
file_format
(Funcref
){options -> {level,msg -> ...}}
のような Funcref を指定する。- 例 (デフォルト値):
{options -> {level,msg -> printf('[%s] %s', get(options.levels[0], level, '?'), msg)}}
file_redir
(Number | Bool
)- もし true 値が指定されたら
:redir
を使ってファイル末尾に追記する。 - デフォルトは
writefile()
で追記する。
- もし true 値が指定されたら
Log.new({'output': 'Echomsg'})
echomsg_hl
:echohl
の引数をリストで渡す。それぞれの要素の位置はoptions.levels
と対応している。- デフォルト値:
[]
- 例:
['None', 'WarningMsg', 'ErrorMsg']
echomsg_format
- File の
file_format
と同じ - デフォルト値は
file_format
と違ってタイムスタンプも出力される{options -> {level,msg -> printf('[%s] %s %s', get(options.levels[0], level, '?'), strftime('%Y-%m-%d %H:%M'), msg)}}
- File の
echomsg_nomsg
- もし true 値が指定されたら
:echo
を使って出力する。 - デフォルトは
:echomsg
で出力する。
- もし true 値が指定されたら
Log.new({'output': 'Nop'})
Log.File.new(path)
や Log.Echomsg.new()
としないのは切り替えをより楽にするため。
例えばデバッグのために Log.Echomsg
を Log.File
に切り替えたいとする。
そうすると Log.File.new(path)
の場合
s:V.import()
の引数 (モジュール名)Log.new()
の引数
の両方を変えなければいけない。
Logger API
logger.set_level(level Number)
logger.log(level Number, msg String | Funcref)
ImplLogger.log()
を呼び出す。- 第二引数に Funcref が渡された場合、その関数を実行した返り値がログ出力される。
logger.flush()
ImplLogger.flush()
を呼び出す。
デフォルトではそれぞれのレベルに対応した以下のメソッドとラベルが追加される。
レベルに対応したメソッドは Logger.new(options Dictionary)
の引数 options.levels
で指定できる。
logger.info(msg String | Funcref)
logger.warn(msg String | Funcref)
logger.error(msg String | Funcref)
logger.INFO
(0)logger.WARN
(1)logger.ERROR
(2)
ラベルは既存メソッド名(Logger.log)と被らなければ何でもいい。
[['INFO', 'info'], ['WARN', 'warn'], ['ERROR', 'error']]
のような配列を渡せば
メソッドとそれに対応するラベル定数を生成してくれる (ちなみにこの値は levels
のデフォルト値)。
この場合それぞれのレベルは INFO < WARN < ERROR
となる。
Dictionary ではなく List で指定するのはこのため。
出力先モジュールの API
出力先のモジュールは Log.new()
によって裏で import() され new() される。
切替可能にするため以下のメソッドを実装しなければならない。以下がその API。
implLogger.log(level Number, msg String)
implLogger.flush()
TODO
最近の Vim で入った面白 Vim script 関数
:h eval.txt
を眺めてたら結構色々関数追加されてるなーと思ったのでまとめてみた。
(追記: 結構古くからある関数も交じってた…)
基本的に詳細な仕様は書くつもりはありません (古くなるかもしれないし help 見た方が正確)。
wordcount()
g<C-g>
を押すと現在のバッファのバイト数、文字数、単語数 (ビジュアルモードの時は選択されたバイト数、文字数、単語数も) を表示してくれますが、それを関数で取れるようにしたやつです
getcharsearch()
[tfTF]
コマンド (例:f<char>
) の情報を返してくれるやつです- どの char を押したか、前方検索 or 後方検索、[tT] か [fF] かを返してくれます
- 要するに直前に
[tfTF]
コマンドのどれを使ったか、引数の char は何なのかの情報
- 要するに直前に
eventhandler()
complete_check()
- ニッチだけど補完系プラグインでは便利そう
- 候補の取得に時間がかかった場合ユーザ入力があった場合に中断できる
getcompletion({pat}, {type} [, {filtered}])
set wildmode
してればコマンドラインで<Tab>
を押せば補完が効くが、その補完の候補を取得できる- 例えば
getcompletion('', 'shellcmd')
で PATH が通った全ての実行ファイル名を取得できる (ちょっと時間かかる)
searchdecl({name} [, {global} [, {thisblock}]])
gd
やgD
の関数版。ううーんできればカーソルの状態変えずに位置だけ返してほしかった…
getbufinfo([{expr}])
orgetbufinfo([{dict}])
getwininfo([{winid}])
- こんなんあったのか…!
:ls
の結果を Dictionary で取れる…もう:ls
をパースとかする必要なんてなかったんや…- (むしろなぜ今までなかったのか)
setbufline({expr}, {lnum}, {text})
- なぜ今まで(ry)。前から Vimmer がほしいほしい言ってたやつ…
- 別バッファの指定された行を追加/更新する関数
:terminal
を実装する過程でひょっこり追加された記憶
- Window ID 系
- Window ID に関しては Vim 8.0 Advent Calendar 7 日目 ウィンドウ ID - Qiita
- タブを跨いでも一意なのが便利
win_findbuf({bufnr})
- bufnr のバッファを表示している Window ID のリストを返す
win_gotoid({expr})
- 引数の window ID のウィンドウに移動する
execute tabnr 'tabn' | execute winnr 'wincmd w'
とかやってたのが関数一つでできるようになった
win_id2tabwin({expr})
- 引数の Window ID のタブ番号、ウィンドウ番号を返す
let [tabnr, winnr] = win_id2tabwin(winid)
- 引数の Window ID のタブ番号、ウィンドウ番号を返す
- Window ID に関しては Vim 8.0 Advent Calendar 7 日目 ウィンドウ ID - Qiita
- index の代わりに
N
文字目のN
(以下文字インデックス) を受け取る文字列操作 (7.4 からあるものもあるけど)- (文字の定義はエンコーディング (
encoding
オプション) によって変わります) strchars({expr} [, {skipcc}])
- 文字数を返す
strcharpart({src}, {start} [, {len}])
- 文字列のスライス (
strpart()
) の文字インデックス版
- 文字列のスライス (
strgetchar({str}, {index})
- 文字列の index (文字インデックス) の位置の文字の数値を返す
- 文字列で取得したい場合は
nr2char()
で要変換
strwidth({expr})
- 文字列の文字幅を返す
- タブ文字の文字幅は1
strdisplaywidth({expr} [, {col}])
- 基本
strwidth({expr})
と同じだが、タブ文字はtabstop
,display
オプションに影響される
- 基本
- (文字の定義はエンコーディング (
- タグファイル系
tagfiles()
taglist({expr} [, {filename}])
- 実行ファイルパス取得
exepath('prog')
で PATH 中の prog のフルパスを取得
changenr()
undotree().seq_last
と同じ?これいつから追加されたんだろう。もしかしたら結構古いかも:helpgrep changenr
したら version7.txt に載ってたので 7.0 で追加されたっぽいです
上のはちょっと便利系関数ですがこれはマジ便利なので知ってほしい系関数(語彙力)です。
- Channel 系
ch_*()
- Job 系
job_*()
- Terminal 系
term_*()
- 部分適用
function()
で引数、self を束縛できるようになった
- 小ネタ: Vim 8 からは関数名から Funcref を取得する時は
function()
よりも 新規追加されたfuncref()
のが安全function()
は関数名の string で lookup しようとするので 同じ名前で再定義された場合は再定義後の function を見に行ってしまうまぁ再定義とかあんまりないけど…(以下 2018/2/14 追記):function
のclosure
属性を使う場合は頻繁に再定義されるケースがほとんどだと思います- 例えば以下のコードで外側の関数 (
s:add()
) が実行される度に内側のs:add1()
は再定義されます
function! s:add(n) function! s:add1(m) closure return a:n + a:m endfunction return funcref('s:add1') endfunction
例えば上記のコードで function()
を使ってしまっていると、
以下のように期待と異なる結果が出力されてしまいます。
function! s:add(n) function! s:add1(m) closure return a:n + a:m endfunction " ここは funcref() を使うべき! return function('s:add1') endfunction let s:f = s:add(1) let s:g = s:add(2) " この時点で s:add1() が 2 + a:m に再定義されてしまっている。 " s:f は function() を使っているので "s:add1" という関数名で定義を探し出す。 " よって再定義後の関数を実行してしまう " 1+1 = 2 のつもりが 3 が出力されてしまう echo s:f(1)
funcref()
ならば Funcref のリファレンスで関数の定義を探すため、このような事は起こりません。
おまけ: カーソル下の1文字を取得
getline()
とcol('.')-1
(index) だとマルチバイト文字の場合にちぎれる- 以前までは
normal! yl
を使っていた- レジスタを退避したりしなければならなかったりして面倒
と思ってたけどこれでいいじゃんと思った。
echo matchstr(getline('.')[col('.')-1 :], '^.')
ちなみに Vim にはカーソルのバッファ中のバイトオフセットから行番号を取得する
byte2line({byte})
という関数があるのだけど、なぜか byte2col({col})
という関数はない。
あれば正規表現使わず
echo nr2char(strgetchar(getline('.'), byte2col(wordcount().cursor_bytes)-1)
とかできるかなーとか思ったけど結局必要なかったので提案しようにも理由がなくなってしまったので決め手に欠ける…
set virtualedit=all
とかして行末以降にカーソルがある状態を考慮してなかったのと、
col (=バッファのカーソルの位置) と行の文字列を比較するのも何か意味合いが違うなと感じたのでやっぱ無しで…