2019/12/31


この記事は Go の編集環境について書いていません。昨日書いた、ぼくがかんがえたさいきょうの Vim のこうせい 2019年 年末版は、僕個人の好みに依存するため一緒に書くべきではないですし、おすすめするつもりも無いです。IDE 機能の説明だけ欲しいと思う方もいるでしょうし、また純粋に Go の編集環境だけの説明が欲しいと思う方もいると思ったからです。

はじめに

以前からも「Vim はテキストエディタではない IDE だ」と言われる事は割と多かったのですが、昨今 Language Server Protocol の登場により本当に Vim を「テキストエディタ」と呼べなくなってきてしまう状況になりつつあります。

これまで Vim は、ctags でタグを生成し、Vim から定義位置ジャンプを行い、フォーマットコマンドを使ってファイルを整形してきました。これはとてもうまく行きました。ctags は多くの各種プログラミング言語をサポートしていますし、taglist の様なプラグインを使えば tags ファイルを読み込んでシンボルリストを表示する事もできます。Vim 標準の機能を伝えばタグスタックも使えます。対応していない言語についてはそれ専用の tags 生成プログラムを探せば良いですし、フォーマットコマンドもそれ専用の物を探してくればよかったのです。

しかしこれをずっと繰り返してきた Vim の設定ファイルは一体どの様な物になっていったでしょうか。個々のプラグイン専用の設定項目が散乱し、プラグイン毎にスタイルの異なった設定を行う必要があり、それ専用のタグ生成コマンド、整形コマンドを用意する時間を捻出しなければなりませんでした。入力補完については色々な言語毎に異なる仕様に悩まされるばかりでした。

結局、僕が行き着いた先は「消しやすい Vim の構成」でしたが、この消しやすい Vim の構成であっても、各プログラミング言語毎に用意すべきツール類をまとめる事は出来ませんでした。

Language Server Protocol の登場

そこにやってきたのが Language Server Protocol です。Language Server を導入する事で、各言語に対する定義位置ジャンプや入力補完のインタフェースが統一され、Vim の設定はある程度統一される様になりました。元々の Vim を「昭和」、各プログラミング言語向けのプラグインが氾濫していた頃を「平成」と例えるなら、ようやく Vim は「令和」になったと言って良いと思います。それでも問題が全て消え去った訳でありません。Language Server を導入する為にはその言語に対する知見が必要です。「このプログラミング言語を触ってみたいな」と思った人が定義位置ジャンプや入力補完、ソースファイル整形といった、最近の IDE であればあって当然の機能を得るには、Language Server を導入する為の知見が必要でした。そこで筆者は先日、vim-lsp-settings というプラグインを作りました。

vim-lsp の導入コストを下げるプラグイン vim-lsp-settings を書いた。 - Qiita

これら全ての機能は、テキストエディタと Language Server との間で JSON-RPC を使い、ソースコード本体、コード補完候補、座標情報などを交換する事で実現されています。 温故知新 実...

https://qiita.com/mattn/items/e62b9f16bc487a271a7f

vim-lsp を利用するには

このプラグインを導入する事で、そのプログラミング言語について詳しくない人でもコマンド1発で入力補完や定義位置ジャンプやソースコード整形といった「IDE 機能」を得る事ができる様になります。やるべき事は2つ

  • vim-lsp とその動作に必要なプラグインを入れる事
  • vim-lsp-settings を入れる事

ファイルを開いて拡張機能のインストールを提案されたら :LspInstallServer を実行するだけなのです。これだけで以下のプログラミング言語の IDE 機能が得られるのです。

Language Language Server Local Install
C/C++ clangd No
C# omnisharp Yes
Clojure clojure-lsp Yes
TypeScript typescript-language-server Yes
JavaScript typescript-language-server Yes
JavaScript javascript-typescript-stdio Yes
Python pyls Yes
Rust rls No
Go gopls Yes
Ruby solargraph Yes
PHP intelephense Yes
Java eclipse-jdt-ls Yes
Lua emmylua-ls Yes
Vim vim-language-server Yes
Bash bash-language-server Yes
Terraform terraform-lsp Yes
Dockerfile dockerfile-language-server-nodejs Yes
YAML yaml-language-server Yes
XML lsp4xml Yes
Fortran fortls Yes
Scala Metals Yes
Elm elm-language-server Yes
JSON json-languageserver Yes
Swift sourcekit-lsp No
COBOL cobol-language-support Yes
Reason reason-language-server Yes
TeX texlab Yes
TeX digestif No
Nim nimls No
D dls No
Elixir elixir-ls Yes
Groovy groovy-language-server Yes

Go 言語の IDE 機能を得る為に何か知る必要はありません。Java の IDE 機能を得る為に何か知る必要はありません。HTML の IDE 機能をインストールする為に npm コマンドの使い方を覚えたり、LaTeX の IDE 機能をインストールする為に、配置場所を考える必要もありません。もしインストールを実行しても動かなかったら、それは vim-lsp-settings のバグです。

以前まででれば vim-lsp を導入すると Language Server の登録が必要でした。

if executable('gopls')
    au User lsp_setup call lsp#register_server({
        \ 'name''gopls',
        \ 'cmd'{server_info->['gopls']},
        \ 'whitelist': ['go'],
        \ })
    autocmd BufWritePre *.go LspDocumentFormatSync
endif

その為、新しい Language Server を導入する度に以下の様な設定が増えていき、設定ファイルがどんどん増えてしまっていました。それが vim-lsp-settings の導入により、その殆どを消し去る事ができました。以下が僕の ~/.vim/_config/200-lsp.vim です。

if empty(globpath(&rtp, 'autoload/lsp.vim'))
  finish
endif

functions:on_lsp_buffer_enabled() abort
  setlocal omnifunc=lsp#complete
  setlocal signcolumn=yes
  nmap <buffer> gd <plug>(lsp-definition)
  nmap <buffer> <f2> <plug>(lsp-rename)
  inoremap <expr> <cr> pumvisible() ? "\<c-y>\<cr>" : "\<cr>"
endfunction

augroup lsp_install
  au!
  autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
augroup END
command! LspDebug let lsp_log_verbose=1 | let lsp_log_file = expand('~/lsp.log')

let g:lsp_diagnostics_enabled = 1
let g:lsp_diagnostics_echo_cursor = 1
let g:asyncomplete_auto_popup = 1
let g:asyncomplete_auto_completeopt = 0
let g:asyncomplete_popup_delay = 200
let g:lsp_text_edit_enabled = 1

サーバ登録に関する物は無くなりました。let を設定している箇所は、vim-lsp およびそれに必要なプラグインの動作を変更する物です。人によっては好ましくない物もあるので皆さんの好きな様に変更頂くのが良いです。

lsp_diagnostics_enabled はファイルの変更に伴いリアルタイムにエラー表示する機能 Diagnostics を有効にする設定、asyncomplete_auto_popup および asyncomplete_auto_completeopt は自動で入力補完ポップアップを表示する設定、asyncomplete_popup_delay はポップアップを表示するまでのディレイ、lsp_text_edit_enabled は LSP の仕様である textEdit を有効にする設定です。この lsp_text_edit_enabled に関しては少し実験的な実装になっている為、もし誤動作する様であれば 0 に設定するのが良いです。

vim-lsp-settings も合わせて vim-lsp を使いたいのであれば以下を vimrc に記述します。

Plug 'prabirshrestha/asyncomplete.vim'
Plug 'prabirshrestha/asyncomplete-lsp.vim'
Plug 'prabirshrestha/vim-lsp'
Plug 'mattn/vim-lsp-settings'
Plug 'mattn/vim-lsp-icons'

Plug 'hrsh7th/vim-vsnip'
Plug 'hrsh7th/vim-vsnip-integ'

vim-lsp-icons はソースコードにエラーがあった際にアイコンでエラー表示をしてくれる為のプラグインです。また Language Server の中には、穴あき形式で補完候補を返してくる物もあり、これをうまく対応してくれるのが vim-vsnip です。

これだけ設定して、上記の 200-lsp.vim を有効にしておけば、おおよその Language Server が正しく動作し、F2 キー(または :LspRename)でシンボルのリネーム、gd で定義位置ジャンプ、:LspDocumentFormat でソースコード整形、gVim を起動したらアイコンでエラー表示がされる様になります。

これを導入する事で、これまで C# の補完の為に導入していた omniSharp-vim や、tags ファイルで定義位置ジャンプを行っていた rust.vim の様な言語専用のプラグイン、それに必要な rustfmt といったツール類を消し去る事ができました。

僕が現在、言語専用に入れているプラグインは Vim のデフォルト syntax では対応しきれていない物に対応させる為に入れている数種類のプラグインだけになりました。

おわりに

昨日書いた、ぼくがかんがえたさいきょうの Vim のこうせい 2019年 年末版とは別に Vim をモダンな IDE にする為の設定を説明しました。人によっては、Vim の設定管理方法が僕とは違うが IDE 機能は欲しいと思われる方もいるだろうと思いましたので、別記事で書かせて頂きました。

昨今、僕が Vim に望んでいるのはこういった氾濫してしまった設定ファイル類をプラグイン等により減らす方向に向かって欲しいという事です。これらの整理なくしては、次の時代に対応できなくなってしまいます。できればプラグイン固有の設定すら書かなくても良い時代になる事を願っています。

最後に、これとは別に僕が「Go 言語の編集でこれだけは外せない」と思っている物を後日書きたいと思います。

さて、あと数時間で 2019 年も終わりです。今年も色々な方にお世話になり、色々な活動をする事ができたと思っています。皆さまに感謝しつつ、来年もまた皆さんのお役に立てられる情報を発信して行けたらと思います。来年も宜しくお願い致します。

Posted at by




はじめに

以下の記事では、僕の Vim の構成について記述しています。本来はこの記事で vim-lsp の導入方法と私的 Go 編集環境について書こうと思っていましたが、あまりにも長くなってしまったので別途書く事にしました。僕は Windows と Linux しか使わないので、皆さんの環境で使うとうまく動かない可能性があります。また僕は最新の Vim 8 しか使いません。古めの Vim を使いません。neovim も使いません。それらをお使いの方はうまく動かない可能性があります。ご了承下さい。なお設定ファイルの配置スタイルは完全に僕個人の趣味ですので必ずしも僕の構成が正しい訳ではありません。

ぼくのかんがえたさいきょうの Vim こうせい

Vim の設定は vimrc に記述するのですが、その設定方法には「汚くさせない」ための工夫が必要だと思っています。以下は僕が行っている「vimrc を綺麗に保つ方法」です。

vimrc に記述する内容は大きく分けて以下の物があります。

  1. Vim 本体の機能のデフォルト値を変更する設定
  2. プラグインの読み込み
  3. ユーザ固有のマッピングやコマンドの定義
  4. プラグイン固有の設定
これは上記の順で書くのが良いと思います。幾らか例外はありますが、おおよそ上記の流れで書くのが良いと思います。今回、僕の vimrc を公開はしませんが、おおよそこの流れに沿っていると思って下さい。なぜ公開しないのかというと、日々スタイルを模索してどんどん変わっていく為、今回ここで紹介した物が僕の vimrc だと広まって欲しくないからです。また好き嫌いはあると思うのですが、僕は最近は分割 vimrc 形式を使っています。 vimfiles
├─vimrc
├─gvimrc
├─autoload/
├─plugged/
└─_config/

autoload には後で述べる vim-plug の plug.vim しか入っていません。上記のリストの内、1と2は vimrc および gvimrc に記述しています。残りは全て _config というフォルダ内に、各機能毎に分けて記述しています。以下は僕の vimrc の概要です。

" Vim 本体の機能のデフォルト値を経項する設定
setglobal cmdheight=2
setglobal laststatus=2

... 略 ...

setglobal fileformat=unix
setglobal formatoptions+=mb

if !has('win32') && !has('win64')
  setglobal shell=/bin/bash
endif

if exists('&termguicolors')
  setglobal termguicolors
endif

if exists('&completeslash')
  setglobal completeslash=slash
endif

let g:no_gvimrc_example=1
let g:no_vimrc_example=1

let g:loaded_gzip               = 1
let g:loaded_tar                = 1
let g:loaded_tarPlugin          = 1
let g:loaded_zip                = 1
let g:loaded_zipPlugin          = 1
let g:loaded_rrhelper           = 1
let g:loaded_vimball            = 1
let g:loaded_vimballPlugin      = 1
let g:loaded_getscript          = 1
let g:loaded_getscriptPlugin    = 1
let g:loaded_netrw              = 1
let g:loaded_netrwPlugin        = 1
let g:loaded_netrwSettings      = 1
let g:loaded_netrwFileHandlers  = 1
let g:did_install_default_menus = 1
let g:skip_loading_mswin        = 1
let g:did_install_syntax_menu   = 1
"let g:loaded_2html_plugin       = 1

let g:mapleader = '\'
let g:maplocalleader = ','

" git commit 時にはプラグインは読み込まない
if $HOME != $USERPROFILE && $GIT_EXEC_PATH != ''
  finish
end

" Windows の場合は必要なパスを追加しておく
if has('win32')
  let $PATH='c:\dev\vim;c:\msys64\mingw64\bin;c:\msys64\usr\bin;'
  \ .'c:\Program Files\Java\jdk1.8.0_221\bin;'.$PATH
endif

" プラグインの読み込み
let g:plug_shallow = 0

call plug#begin('~/.vim/plugged')

Plug 'thinca/vim-ambicmd'
Plug 'thinca/vim-openbuf'
Plug 'thinca/vim-quickrun'

... 略 ...

call plug#end()

" 各種設定の読み込み
call map(sort(split(globpath(&runtimepath, '_config/*.vim'))){->[execute('exec "so" v:val')]})

これを $HOME/.vim、Windows であれば %USERPROFILE%\vimfiles の中に vimrc というファイル名で置いています。便宜上、vimfiles を Windows のシンボリックリンクを使って .vim にリンクさせています。

気を付けるべきはこの状態を常に保つ事です。これは vimrc だけでなく .emacs.el 等にも言える事ですが、「ちょっと今だけ」と vimrc に手を加えた設定は、おそらく数か月後もそのままになってしまう可能性があるからです。綺麗な vimrc を保ち続けたいのであれば、この形は崩さない事です。

またなぜ僕がこの形にしているかというと、機能単位またはプラグイン単位で設定ファイルを消せるからです。必要なくなったプラグインの設定が vimrc に残ったままだと、どんどん汚くなってしまいます。必要に応じて消していくのも、vimrc を綺麗に保つ秘訣です。これは僕が2015年から SoftwareDesign の連載「Vim の細道」を書く上で毎月 vimrc を継ぎ足ししているうちに、どうしても vimrc が汚くなってしまった問題を解決する為に行き着いた方法です。

Vim 本体の機能のデフォルト値を変更する設定

ここで僕が言える事は「これらの設定をばらまかない事」。これらの設定は今後どんな新しいプラグインが出てきたとしても変わらない設定であるはずです。

プラグインの読み込み

僕は vim-plug というプラグインマネージャを使っています。他のプラグインマネージャを使っている方もいると思います。ちなみに僕も vim-plug に満足している訳ではなく、現在は minpac に移行しようか検討中です。

ユーザ固有のマッピングやコマンド定義

これ以降は、ユーザ固有のマッピングやコマンド定義、プラグイン固有の設定、の2つとなりますが、これらは個別の設定ファイルに書いています。例えば僕は _config ディレクトリの下に以下の様に配置しています。 000-mappings.vim
001-filetype.vim
002-commands.vim
003-path.vim
102-autofmt.vim
104-conda.vim
105-ctrlp.vim
106-emmet.vim
107-lightline.vim
200-lsp.vim
201-languageclient-nvim.vim
202-vsnip.vim
204-quickrun.vim
300-vim.vim
301-java.vim
400-memolist.vim
401-openbrowser.vim
402-previm.vim
403-termdebug.vim
404-tohtml.vim
405-twitvim.vim

おおよそ番号体系があって、000番台が Vim 本体に関する物、100番台が全体に関わるプラグインの設定、200番台以降がプラグインの設定ですがその中でもグローバルに適用される物、300番台がファイルタイプに関連する物、400番台がアプリケーションに関する物、となっています。この番号体系は完全に守れている訳ではないです。

ユーザ固有のコマンド定義では、コマンドの実行可否を利用するのが良いと思います。例えば外部コマンド foo が存在しない場合にユーザ定義コマンドを定義したくない場合には以下の様にファイルの先頭に書きます。

if !executable('foo')
  finish
endif

プラグイン固有の設定

上記のリストで、番号が飛んでいるのに気付いたかもしれません。以前は使っていたけれど不要になったので消した為です。これにより、プラグインを足したり消したりする際にゴミが残ってしまう問題を解決しました。

これらの設定で注意する事は、プラグインの有無をチェックする事です。プラグインをチェックする為には、globpath 関数を使ってスクリプトの存在を確認する方法を使います。

例えば vim-lsp の設定ファイル 200-lsp.vim の先頭には以下の様に書いています。

if empty(globpath(&rtp, 'autoload/lsp.vim'))
  finish
endif

こうする事で、vimrc の Plug 行をコメントアウトしても正常に Vim が起動できる様になります。例えば何かの拍子に環境が変わり、プラグインが正常に動作しなくなった場合には、そのプラグインを読み込みを停止してしまえば合わせて設定も無効になってくれる、という仕組みです。

おわりに

以上が、次第に汚くなりがちな vimrc を綺麗に保つために僕が行き着いた方法です。個人的な趣味ですので、皆さんには合わないかもしれませんが、幾らかは盗んで頂ける物があるかもしれません。
Posted at by



2019/11/30


以前、mruby から TensorFlow Lite を扱う為の mrbgems、「mruby-tflite」を書きました。

Big Sky :: MRuby の TensorFlow Lite バインディングを書いた。

以前 TensorFlow Lite の Go バインディングを書いたのだけど Big Sky :: TensorFlow Lite の Go binding を書いた。 Google launche...

https://mattn.kaoriya.net/software/lang/c/20190417102631.htm

KaoriYa さんに Coral EdgeTPU をプレゼントして頂いたので、mruby-tflite を Coral EdgeTPU 対応する為の mrbgems を書きました。

GitHub - mattn/mruby-tflite-edgetpu
https://github.com/mattn/mruby-tflite-edgetpu

元の mruby-tflite とはソースを分離してあります。build_config.rb に以下の様に追加して頂くだけで EdgeTPU を扱える様になります。

conf.gem :github => 'mattn/mruby-tflite'
conf.gem :github => 'mattn/mruby-tflite-edgetpu'
conf.gem :github => 'kjunichi/mruby-webcam'
conf.gem :github => 'qtkmz/mruby-gd'

mruby-tflite と mruby-tflite-edgetpu を連携するには以下の様にインタプリタに対してオプション設定する必要があります。

#! ./bin/mruby

# オプションを生成
options = TfLite::InterpreterOptions.new

# Coral EdgeTPU のデバイス一覧から 0 番目を delegate として割り付け
options.add_delegate TfLite::EdgeTPU.new(TfLite::EdgeTPU.devices[0])

model = TfLite::Model.from_file 'mobilenet_ssd_v2_coco_quant_postprocess_edgetpu.tflite'
interpreter = TfLite::Interpreter.new(model, options)
interpreter.allocate_tensors
input = interpreter.input_tensor(0)
output2 = interpreter.output_tensor(1)
output3 = interpreter.output_tensor(2)
wanted_width = input.dim(1)
wanted_height = input.dim(2)
wanted_channel = input.dim(3)
data = Array.new(wanted_height * wanted_width * wanted_channel, 0)
labels = {}
File.read('coco_labels.txt').lines.each do |x|
  kv = x.split('  ')
  labels[kv[0].to_i] = kv[1].strip
end

cam = Webcam.new(ARGV[0]||0)
cam.set_size(wanted_width, wanted_height)
cam.each(true) {|img|
  decoded = GD::Image.new_from_jpeg_data(img)
  (0...wanted_width).each do |x|
    (0...wanted_height).each do |y|
      pixel = decoded.get_pixel(x, y)
      offset = (y * wanted_width + x) * wanted_channel
      data[offset..offset+2] = [decoded.red(pixel), decoded.green(pixel), decoded.blue(pixel)]
    end
  end
  decoded.destroy
  input.data = data
  interpreter.invoke
  result = []
  output3.data.each_with_index do |v, i|
    next if v < 0.6
    result.push([v, output2.data[i]])
  end
  result.sort{|a, b| b[0] <=> a[0] }.take(5).each_with_index do |v, i|
    s = v[0]
    i = v[1]
    puts "#{labels[i]} #{i} #{s}"
  end 
  puts "---"
}

現状、mruby には OpenCV の様に加工した MAT を表示できる mrbgems が存在しない為、kjunichi さんが作っておられる mruby-webcam で画像を取り込み、qtkmz さんの mruby-gd で画像を解析し、そこに何が映っているのかを端末に出力しているだけですが、今後 OpenCV の様な mrbgems が登場すれば mruby でもリアルタイムで画像を解析し、認識したオブジェクトに枠や名称を描画する事も出来る様になるかもしれません。

mruby-tflite-edgetpu
Posted at by