Quantcast
Browsing Latest Articles All 25 Live
Mark channel Not-Safe-For-Work? (0 votes)
Are you the publisher? or about this channel.
No ratings yet.
Articles:

永遠の Vim 初級者の、Vim の使い方

イントロダクション

どうも、毎年ゆるふわな記事を Vim Advent Calendar に投稿させていただいている、永遠の初級者 Vim ユーザーです。

2016 年 : e が押せないのは誰のせい?
2017 年 : こんな私でも Vim が好きと言いたい

今年は、そんなライトな Vim ユーザーは Vim をどうやって使っているか、どんな機能を使っているか(どんな機能を使っていないか)を雑にお伝えしたいと思います。

狙い

Vim 入門者の方には Vim 入門として若干お役に立つかもしれませんが、個々の機能を詳しく解説しているわけではないのでそうでもありません。
Vim 上級者の方には、初級者のつまづきポイントがわかって普及や教育の参考になるかもしれません。知らんけど。

よく使うもの

基本的なやつ

b, c, d, f, g, h, i, j, k, l, n, p, q, r, u, v, w, x, y
0, $, ~, %, /, *, @

各々のコマンドについて細かく見ていこうと思っていたんですけど1、きりがないことに気づいてやめました。

コマンド類

保存・終了

:q, :q!, :w, :w filename, :wq, :e filename

他にも ZZ とか :x とかいろいろあるらしいですが、私は全く使いません。いろいろ調べてそういう結論になったはず。

その他

set paste とか vimgrep とかはたまに使っているような気がします。他にもあるかもしれませんが、ちょっとすぐには思い出せないです。履歴も確認しましたけどほとんど wehelp で埋め尽くされていました。って、help 使ってるやん。

テキストオブジェクト

これは使わないと、何のために Vim 使ってるんだって話になるくらい重要な機能だと個人的には思います。
テキストオブジェクトを初めて知ったときのあの感動は忘れられないです。

バッファ

一度終了してから別のファイルを開き直したり、(Vim のウィンドウじゃなくて物理的?な)ウィンドウやコンソールを何枚も開けたりするがバカバカしくなってきたとき、人は自然とバッファにたどり着くんじゃないでしょうか。

ウィンドウ

複数のファイルを並べて見たいけど、(Vim のウィンドウじゃなくて物理的?な)ウィンドウやコンソールを何枚も開けたりするがバカバカしくなってきたとき、人は自然とウィンドウにたどり着くんじゃないでしょうか。

モードライン

常用しているというわけではないし、実際それほど使いどころもないですが、特定の場面では重宝しています。

使わない(使えない)もの

基本的なやつ

e, m, o, s, t, z
^, ., ?

e2年前に記事にしたとおり使えていません。
os も使えていないです。A[Enter] とかが手癖になっているっぽい。
^ は(この記事を書き出すまでは) 0 と同じだと思っていたのですが、^ のヘルプを見ると

To the first non-blank character of the line.

^ は行頭ではなく、その行の最初の非空白文字に移動するのですね・・・。
これは明日からチャレンジしてみよう。

t, m, z は未だに何者なのかよく分かっていません。って、m って mark なのか。私、メモをとったこと自体を忘れるが故にメモを取らないタイプの人間なので、マーク使わない(使えない)んですよねぇ。

コマンド実行

:! なのですが、なんと私、これが使えないんですよ。何かコマンドを実行したくなったときは Ctrl+z で抜けるか、tmux などでコンソールを分割しています。
エディタに実行結果が出るのがなぜか好きではないのですよね。(便利なこともあるとは分かっていても。)IDE を使うときでも、IDE 付属のコンソールは全く使いませんし・・・。

タブ

バッファやウィンドウは使っていても、タブは使えません。そもそもまだ概念を理解できていないです。

プラグインとか

全く使っていません。去年の記事参照。

まとめ

結局、文字が快適に入力できればそれでいいっていう感じなんだと思います。私の場合。


  1. 例えば gggG はよく使ってるけどそれ以外は多分使ってないとか。 

VimをCUIコマンドに組み込む/dotfiles晒し

はじめに

こいついつも.vimrc弄ってんなと言われてはや数年、今年も弄ってます。

今回は二本立てになっています、ちなみにWindowsでKaoriya版のGVimを使ってますので、ご自身の環境への読み替えをお願いします。

VimをCUIコマンドに組み込む

僕は開発中のビルドにQuickRunの他に素のコマンドプロンプトもよく利用しているのですが、といいますかまずはコマンドプロンプトでVimに依存しない開発環境を整えて、それをVimの開発に落とし込むという手順を踏んでいるのですが、そうするとコマンドプロンプトとVimの連携もしたくなってくるかと思いますので、対応できるようにしました。

成果物はこちらです、コマンドプロンプト上で各種言語のLinterやビルドのエラーメッセージにVimのerrorformatを適用してフォーマットを統一し、fzf(pecoの親戚みたいなの)でエラー箇所を選択し、GVimで開くためのツールキットです。
wordijp/mycommands - (GitHub)

この中で言語別のerrorformatの適用にVimをCUIコマンドとして呼び出しています。

デモ

demo.gif

CUIコマンドとして呼び出している部分

この呼び出し部分もCUIコマンドにしています、batファイルで書かれていますが、Windows上の色々不便な部分1を解消するためにRustで書かれたコマンド経由で呼び出すようにしています。
こんな事するんなら最初からLinux環境使えという言葉はやめてくださいしんでしまいます

下記がそのソースです。

@rem internal\vim-loclist.bat

@echo off
@rem エラー一覧をQuickFixロケーションリストへ変換する

if "%1" == "" (
  echo "usage) vim-loclist <filetype>"
  exit /B 0
)

vim - -es +"source $VIM/vimrc" +"set nonumber" +"set filetype=%1" +":lgetbuffer" +":lopen" +:%%p +:q! +:q! | tail +2 | grep -v -E "^\|\|\s*$"

エラーメッセージをパイプ経由でこのコマンドに渡す事を前提としており、QuickFix経由で言語別のerrorformatを適用し標準出力に流します、errorformatは言語別に設定が違い自動判別も難しいため、filetypeだけは引数で任意に指定する必要があります。

こうすると、PHPの場合は

php -l | vim-loclist php | fzf(or peco)

C++の場合は

make 2>&1 1>nul | vim-loclist cpp | fzf(or peco)

という風にerrorformat適用後の一覧の選択ができ、さらに後ろにGVimで開くコマンドを追加すればコマンドプロンプトでのエラー結果をGVimで開けます。

ちなみにエラー結果をGVimの起動引数に変換するのもCUIコマンドにしています。
※shはMSYS2で対応しています。

# internal/vim-arg.sh
cat - | \
    sed -u -E 's/([^|]+)\|([0-9]+)( col [0-9]+)?\|.+$/+\2 \1/'

参考

dotfiles晒し

今年、Language Server Protocol(以下LSP)が登場し、コーディングツールキットに革命の波が押し寄せてきました、なのでLSPに対応した.vimrc含めdotfilesを晒していこうかと思います。

wordijp/dotfiles-win - (GitHub)
※PHP用のQuickRun設定では一つ目に紹介したmycommands内のコマンドを呼び出しています。

Kaoriya版Vim用の.vimrcですが、ファイル群を%USERPROFILE%(C:/Users/user)に置けば済むようにしています、makeはMake for Windows前提です。

> @rem インストール(要Make for Windows)
> make install

この設定は下記の各種言語にて統一された操作感を持つ事を目的としています、頻繁に言語をまたいで開発しているためVimが手放せません。2

  • C/C++
  • Rust
  • PHP
  • Ruby
  • Python
  • Go
  • JavaScript

なお、各種言語で静的構文解析ジャンプ・コード補完・Linter/ビルドのために、LSPの他にValloric/YouCompleteMe - (GitHub)(以下YCM)、w0rp/ale - (GitHub)fatih/vim-go - (GitHub)racer-rust/vim-racer - (GitHub)なども利用しています、将来的にはLSPに一本化されるとは思うのですが、まだまだ発展途上のため各種プラグインを併用した状態になっています。

例えば、静的構文解析ジャンプは次のようにしてます、めんどくさい。

" .vimrc
" 静的構文解析ジャンプ
let g:go_def_mapping_enabled = 0 "自前でマッピング
nnoremap <F12> :call <SID>defJump()<CR>
nnoremap <C-]> :call <SID>defJump()<CR>
nnoremap <C-h> :vsp<CR>:call <SID>defJump()<CR>
nnoremap <C-k> :split<CR>:call <SID>defJump()<CR>
function s:defJump()
  if &ft == 'go'
    :GoDef
  elseif &ft ==# 'cpp' || &ft ==# 'php' || &ft ==# 'ruby'
    " 実装へジャンプ
    :call LanguageClient#textDocument_definition()
  elseif &ft ==# 'rust'
    :execute "normal \<Plug>(rust-def)"
  elseif &ft ==# 'javascript' || &ft ==# 'javascript.jsx' || &ft ==# 'typescript' || &ft ==# 'python'
    :YcmCompleter GoToDefinition
  else
    :exe("tjump ".expand('<cword>'))
  end
endfunction

PHPのLSP対応

WindowsでPHPのLSPを動かすにはfelixfbecker/php-language-server - (GitHub)を使うのですが、こちらはWindowsでは標準入出力経由では動きません、Tcp経由にする必要があります。
VSCodeのプラグインはそのようにしてるのですがVim用は探す限り見つかりませんでした、なのでTcp経由で接続するVim用プラグインを自作しています。
wordijp/LanguageServer-php-tcp-neovim - (GitHub)

" .vimrc
" Tcp経由のphp-language-serverインストール
Plug 'wordijp/LanguageServer-php-tcp-neovim', {
  \ 'do': 'bash ./install.sh && composer install && composer run-script parse-stubs'
  \ }

上記の制約により標準入出力のみのprabirshrestha/vim-lsp - (GitHub)は使うことが出来なくなります、今のところはautozimu/LanguageClient-neovim - (GitHub)しかないかと思います。

denite

大きそうな設定なのでこちらも記載。
速度と利便性を兼ね合いした結果、ファイル検索はfindより高速と評判のsharkdp/fd - (GitHub)を、grepはUTF-8・Shift_JIS両対応で速度のバランスが良いmonochromegane/the_platinum_searcher(通称PT) - (GitHub)を利用しています。
除外は検索時点で省くのが最速だろうって事で直に設定しています。

" .vimrc
" denite
" find source
" NOTE: 除外もこっち
call denite#custom#var('file_rec', 'command',
  \ ['fd', '.', '-HI', '--type', 'f',
  \ '-E', '.git',
  \ '-E', 'vendor',
  \ '-E', 'node_modules',
  \ '-E', 'target',
  \
  \ '-E', '*.bak',
  \ '-E', '*.o',
  \ '-E', '*.obj',
  \ '-E', '*.pdb',
  \ '-E', '*.exe',
  \ '-E', '*.bin',
  \ '-E', '*.dll',
  \ '-E', '*.a',
  \ '-E', '*.lib',
  \ '-E', '.gitignore',
  \ '-E', '.*.*',
  \ ])
call denite#custom#source('file_rec', 'matchers', ['matcher_fuzzy'])

" grep source
" NOTE: ptでutf8、sjis両対応
call denite#custom#var('grep', 'command',
  \ ['pt', '--nogroup', '--nocolor', '--smart-case', '--hidden',
  \ '--ignore', '.git',
  \ '--ignore', 'vendor',
  \ '--ignore', 'node_modules',
  \ '--ignore', 'target',
  \
  \ '--ignore', '*.bak',
  \ '--ignore', '*.o',
  \ '--ignore', '*.obj',
  \ '--ignore', '*.pdb',
  \ '--ignore', '*.exe',
  \ '--ignore', '*.bin',
  \ '--ignore', '*.dll',
  \ '--ignore', '*.a',
  \ '--ignore', '*.lib',
  \ '--ignore', '.gitignore',
  \ '--ignore', '.*.*',
  \ ])
call denite#custom#var('grep', 'default_opts', [])
call denite#custom#var('grep', 'recursive_opts', [])
call denite#custom#source('grep', 'matchers', ['matcher_fuzzy'])

ショートカットキーは

" .vimrc
" ctrlp like
nnoremap <silent> <C-p> :<C-u>Denite file_rec<CR>
" grep(選択単語)
nnoremap + :<C-u>Denite -buffer-name=search -no-empty grep -input=<C-R><C-W>
" grep
nnoremap <silent> ;g :<C-u>Denite -buffer-name=search -no-empty grep<CR>
" 検索
nnoremap <silent> ;/ :<C-u>Denite -buffer-name=search -auto-resize line<CR>
" 閉じたバッファをまた開く
nnoremap <silent> ;r :<C-u>Denite -buffer-name=search -resume<CR>
" 次へ
nnoremap <silent> ;n :<C-u>Denite -buffer-name=search -resume -immediately -select=+1<CR>
" 前へ
nnoremap <silent> ;p :<C-u>Denite -buffer-name=search -resume -immediately -select=-1<CR>

終わりに

凄く属人的な気がしますが助力になれば幸いです。


  1. batファイルの「バッチ ジョブを終了しますか (Y/N)?」を出さなくしたり、SIGPIPEに疑似的に対応したり。 

  2. 流石にPHPに絞ってPHP Storm使えと言われるとグゥの音も出ないです。 

WindowsにNeoVimをセットアップ in 2018

この記事はVim Advent Calendar 2018 3日目の記事です。

Windows10でのneovim-qtとPython環境、プラグイン管理のdeinのインストールと、はまったところの対応方法など。

インストール

本体のインストール

https://github.com/neovim/neovim/wiki/Installing-Neovim#windows

パッケージマネージャのchocolateyscoopが使えますが、まずchocolateyを入れるのが若干手間ですね。
あと、インストールされる場所もユーザーフォルダ内だったりと、若干微妙です。

ということで、自分はビルド済みバイナリを取ってきてProgram Files/Neovimに配置してます。

programfiles.png

Pythonのインストール

必須のプラグインが多いので、併せてインストールします。
Python公式 から .msi インストーラーを取ってきてウィザードをポチポチでいけます。

Pythonの実行環境はなくてもNeoVim自体は動きます。

NeoVimではリモートプラグインという仕組みによって、デフォルトでPython、Node.js、Go、Luaの各言語のプラグインに対応します。

参考: Neovim のリモートプラグインを利用して好きな言語でプラグインを書く blog.syfm

PipからNeoVim用APIをインストール

pip_install_neovim.png

> pip install pynvim

Pythonのパッケージ管理でNeoVim用APIをインストールします。
スクリーンショットでは古いパッケージ名のneovimを指定しています。
ログではneovim.tar.gzファイルとpynvim.tar.gzファイルの2つをダウンロードしてきてるのが確認できます。

VC++14ランタイムが必要です。入ってなければエラーで止まりますので入れてください。
Visual Studio 2015に付属するランタイムです。

Pipパッケージ管理が入ってない場合は、.msiインストーラから修正インストールで行けると思います。

環境変数(パス)の設定

path_env.png

Pythonのインストール時に指定できますが、確認します。
通ってなかったら追記します。

AppData\Local\Programs\Python\Python37-32

AppData\Local\Programs\Python\Python37-32\Scripts

また、neovim-qt.exeへパスを通しておくと、コマンドプロンプト/PowerShellでNeoVimが使えます。

Power ShellからNeoVim

Pythonの実行パスをinit.vimで指定

NeoVimからPythonが実行できるように、init.vimで設定します。

NeoVimで :checkehealthを実行。結果が新規タブで下記のように表示されます。

health#provider#check
========================================================================
## Clipboard (optional)
  - OK: Clipboard tool found: win32yank

## Python 2 provider (optional)

~~~中略~~~

## Python 3 provider (optional)
  - INFO: `g:python3_host_prog` is not set.  Searching for python in the environment.
  - INFO: Multiple python executables found.  Set `g:python3_host_prog` to avoid surprises.
  - INFO: Executable: C:\Users\akiya\AppData\Local\Programs\Python\Python37-32\python
  - INFO: Other python executable: /python
  - INFO: Python3 version: 3.7.0
  - INFO: python-neovim version: 0.3.1
  - OK: Latest python-neovim is installed: 0.3.1


-INFO: g:python3_host_prog is not set. Searching for python in the environment.
- INFO: Executable: C:\Users\akiya\AppData\Local\Programs\Python\Python37-32\python

実行可能なPythonが検出されていますので、AppData\nvim\init.vimでこのパスを指定します。

let g:python3_host_prog = 'C:\Users\akiya\AppData\Local\Programs\Python\Python37-32\python'

:echo has('python3') を実行、1が返ってくれば正しくPythonが使えます。

healthcheck.png

deinのインストール

”Dark powered Vim/Neovim plugin manager”ことdeinを入れます。

公式リポジトリにインストール用シェルスクリプトがありますので、Git Bashから実行するという手もありますが…

ユーザーフォルダに.cache以下のフォルダを作ってinit.vimに設定を書きます。
自分はdeinでdein自身を管理するために、deinが生成するフォルダ構造に合わせています。

dein_folder.png

"dein Scripts-----------------------------
if &compatible
  set nocompatible               " Be iMproved
endif

" Required:
set runtimepath+=~/.cache/dein/repos/github.com/Shougo/dein.vim

" Required:
if dein#load_state('C:\Users\akiya\.cache\dein')
  call dein#begin('C:\Users\akiya\.cache\dein')

  " Let dein manage dein
  " Required:
  call dein#add('C:\Users\akiya\.cache\dein\repos\github.com\Shougo\dein.vim')

  call dein#load_toml( 'C:\Users\akiya\AppData\Local\nvim\dein.toml')

  " Required:
  call dein#end()
  call dein#save_state()
endif

" Required:
filetype plugin indent on
syntax enable

"End dein Scripts-------------------------

rutimepathに、dein.vimフォルダまでのパスを設定。

call dein#begin の引数のパスはrepos フォルダの入っているフォルダのパス。

dein自身をdeinで管理する場合は、
call dein#add('C:\Users\akiya\.cache\dein\repos\github.com\Shougo\dein.vim')
でパスを指定します。

dein#add() でプラグインを指定。

call dein#load_tomlで、.tomlファイルに書いたプラグインの設定を読み込ませることができます。

NeoVimで:call dein#install()すれば、dein#add()で指定したプラグインが入ります。

ちなみにクイックスタート用のシェルスクリプトを実行すると、上記のようなinit.vim貼付用のコードが返ってきます。

プラグイン

大体はVimのプラグインがそのまま動きますので、NeoVimで入れ直したプラグインというと…

Shougoさんのが多いです、足を向けて寝られません。
というか、私はDeopleteを使いたかったのでNeoVimに乗り換えたようなものです。

リントツールはVimで定番のsyntasticがNeoVimに対応していません。
そこでALEを入れています。動作が高速で助かります。

vim-gitgutterがNeoVimでは動かなかったのですが、今GitHub見たら対応してるみたいです。

他、不具合の対処方法やカスタマイズ

init.vimでエラーがあると起動しない

grayout.png

Neovim is taking too long to respond

ちょっとしたタイポでもこのような画面になってしまい、どうにもならなくなります。
init.vimを移動したりファイル名を変更したりして、素のNeoVimが起動するか試しましょう。
:source init.vim でエラーがなくてもダメな場合があります。

---2019年6月 追記---
2019年5月末リリースのNeoVim 0.3.7では、起動後にinit.vimのエラーがコマンドラインに流れるようになりました。

また、swapファイルが残っていてもグレーアウトしてしまうことがあるので、AppData\Local\nvim-data\swapを確認してみてください。

プラグイン管理で動かないのがある

WindowsのUAC(ユーザーアカウント制御)の関係で動かないのがありました。
deinは大丈夫です、plugは去年はダメでした(最新版ではいけそうです)
deinも書き込み権限の要るフォルダは無理なようです。
.cache/deinが無難だと思います。

フォント変更はginit.vim

ユーザーフォルダのAppData\Local\nvim内に ginit.vimを配置してGUI向けの設定を書きます。

Guifont! Migu\ 2M:h14
call GuiWindowMaximized(1)

フォント名に空白が含まれる場合はエスケープします。
画面を最大化して起動もここで指定できます。

ウィンドウサイズも指定できたと思います。

terminalの設定

:terminal でシェルを実行できます。
デフォルトではcmdが起動しますが、PowerShellに変えることもできます。

set shell=powershell.exe
set shellcmdflag=-NoProfile\ -NoLogo\ -NonInteractive\ -Command

また、エスケープを押してもノーマルモードに戻れないので、Escキーにキーアサインを設定します。

tnoremap <silent> <ESC> <C-\><C-n>

WindowsにもOpenSSHの波がようやく到達したので、NeoVimからvagrant sshもできますよ!

最後に

deinのインストールはinit.vimから記憶を頼りに書いたのであやふやな感じになってしまいました。

また、Windowsでの動作は確認していないプラグインも割とあったりします。
OSS全般、Windows対応のリソースは不足気味だそうで、Windowsユーザーの皆さん、ふるってご参加ください。
Issue立てるだけでも十分な貢献になります。

vim初心者から中級者へ! ~vimの面白さに気づくとき~

最近やっとvimの面白さに気づき始めてきたところです。
ぎりぎり中級者に上がれたのでは・・・ということでどんなことをしてきたのかをまとめます。
vimやってみたいけど、vimの良さがいまいちわかっていないし、kとかjで移動ってわかりにくい、と思っている方にこそ読んでいただければと思います。
もしかすると「vimいいじゃん!」ってなるかもです。

先にまとめです

  • vimの練習にはコーディングが一番!
  • ブラインドタッチ必須は本当でした…

vimの良さに気づく前のレベル

  • サーバーにはいったときに仕方なくvim(vi)を使う。
  • 使うのは、iEsc:wq。移動は←→↑↓キー連打。
  • 困ったら:q!でなかったことに。ただしいつも!の位置が分からなくなる。
  • ブラインドタッチは自己流。なので、打ち間違いが多数発生。

vimに興味を持ったけど挫折した

かなり前にvimに興味を持ったのですが、移動がhjklという点で挫折しました。
全然なれなくて挫折しました。
また、vimでメモを取るようにしていたのですが、iを押してあとは書いていくだけの作業になってしまうので、普通のエディタに書くのと変わらないなと思っていました。

一念発起

(きっかけは忘れたのですが)vimできるようになりたいな、という思いからvimを頑張ろうと決意しました。
一度挫折したことは覚えていましたが、これだけ多くの人がvimが好きなわけで、私が知らないことがたくさんあるはず!と思いました。

初心者になるために① ググる

vim 勉強vim 学習vim 初心者 でググりました。
そこで見つけた、たくさんの方々の記事に大変お世話になりました。
ありがとうございました!!!

一部ですが載せさせていただきます。
http://geektrainee.hatenablog.jp/entry/2015/01/18/224443
http://tech.innovation.co.jp/2016/07/20/vim.html
https://qiita.com/hachi8833/items/7beeee825c11f7437f54

ここから先は、このような記事の内容を参考にして進めていきました。

初心者になるために② vimtutor

vimのチュートリアルである、vimtutorをやりました。
vimtutorと打つだけで始められます。
(KaoriYaさん版の場合は、vimを起動してから:Tutorialとコマンドを実行します)

量はそれなりにあるのですが、移動のやり方から、一通りテキストを編集できるようになるところまで含まれています。
これまでiEscしか使ってこなかった私からすると、いろいろなコマンドがあることに衝撃を受けました。
想像以上にいろいろできることを感じ、ちょっとずつ「便利なのではないか・・・?」と思い始めます。

初心者になるために③ ドットインストール

ドットインストールの「vim入門」をやりました。
私の場合はvimtutorを先にやったのですが、ドットインストールが先でも問題ないのではと思っています。
vimtutorで仕入れた知識で足りていなかったところを補いました。

ここまでで、何とかvimを使って操作ができるようになりました。
そしてここからは中級者への道へと進みます。

中級者になるために① コーディングをvimで

コードをvimで書き始めます。
これが個人的にとてもよかった!
最初に書いたように、メモを取るだけだと、iを押してあとは普通に入力していくことになるのでvimの面白みがあんまりわかっていませんでした。
しかし、コードを書いてみると、「これできるのめっちゃ便利!」が増えていきます。
必然的にvimのコマンドも覚えていきます。
さらに必然的に「これってどうやるんだろう?」という感じで新しいコマンドも調べて覚えていきます。

中級者になるために② vimチートシートを手書きで作る

チートシートを手書きで作ってみました。
(個人的に「書く」ことで覚えられるタイプというのもあるのですが)定着につながったと思います。
また、いろいろなチートシートをググって、知らなかったコマンドを見つけるのも楽しかったです。
zf(選択範囲を折りたたむ)はこれで見つけました。

中級者になるために③ vimrc

チュートリアルをやったり、コードを書き始めたときはあえてvimのカスタマイズをしていませんでした。
まずは基本を知りたいと思っていたからです。
また、最初にたくさんの方のvimrcを見ましたが、設定の内容を見てもいまいち便利さがわかっていなかったというのもあります。
ですが、ここまでくると「挿入モードからのEscが面倒くさい」とか「:nohが面倒くさい」というのがわかってきます。
ということで、vimのカスタマイズを行いました。
とくに便利と思ったのは上にも書いた2つです。

" インサートモードで jj で ESC
inoremap jj <Esc>
" Escの2回押しでハイライト消去
nnoremap <Esc><Esc> :nohlsearch<CR><ESC>

中級者になるために④ 実践vimを読む

良書と噂の実践vimを読みます。
だいぶ操作も分かってきているため、かなりすらすらと感動しながら読めます。
この本を読んで「なるほど!」と思えた時に、「中級者になれたかな‥?」と思いました。

ブラインドタッチ

いろんな記事に「vimをするならブラインドタッチは必須」とありました。
ブラインドタッチできていなかったのですが、はやる思いを止められず、vimの世界へ飛び込みました。
そしてvimをやればやるほどブラインドタッチができないのがもどかしくてしょうがなくなりました。
コマンドは覚えていてもそのキーがさくっと押せないのがものすごくもどかしいです。
そしてブラインドタッチを練習し始めてちょっとたつと、vimでの編集速度もあがりました。

以上です。
こんな感じでvimを使ってきました。
これからもガンガン使っていこうと思います!

はじめてのゔぃむすくりぷと

はじめに

VimConf 2018最高でしたね。
テンションが上がりすぎて、この感動を共有しないわけにはいかないと英語でまとめを書いてしまったので、アドカレのネタがVimConf記事から変更になりました。
Vim歴10ヶ月の若輩者ですが、どうぞよろしくお願いします。

写経

きっかけはVimConf2018のAlisueさんのセッションでした。
Effective Modern Vim scripting

Modernとか、そういう言葉に弱いです。
最高のVim Script Tutorialな気配がしたのとDark Vimmerへの道を踏み出すために写経しました。

以下、写経して思ったこと

  • autoload配下のpathと関数名が対応してる規約面白い
  • source guardやfunction!など何のために必要なのか調査しないと分からないイディオムが多い
  • Vital.vim、めっちゃ便利
  • 公式のplugin manager、欲しすぎる
  • Vim script、思った以上にモダンに書ける
  • Vim script、pythonっぽさを感じる
  • Promise、デバッグが難しい

ゔぃむすくりぷつ

FizzBuzz

定番です。これ以上のネタを思いつかなかった。

function! amake#fizzbuzz() abort
  for i in range(1, 100)
    call s:judge_fizzbuzz(i)
  endfor
endfunction

function! s:judge_fizzbuzz(i) abort
    if a:i % 15 == 0
      echo "FizzBuzz"
    elseif a:i % 3 == 0
      echo "Fizz"
    elseif a:i % 5 == 0
      echo "Buzz"
    else
      echo a:i
    endif
endfunction

Linter

普段はALEに任してしまっています。せっかくの機会なので、Linterをamakeの写経に足していく形で実装しました。
JS限定で:wするたびにエラーをQuickFixに出してくれます。
こちらを参考に実装。

  • autoload/amake.vim
function! amake#eslint()
  let result = split(system('yarn eslint ' . bufname("")), '\n')
  let errors = []
  for r in result
    if r =~ '\<error\>' && r !~ 'Command failed with exit code'
      let info = { 'filename': bufname("") }
      let splited_error_line = split(r, 'error')
      let lnum_col = split(splited_error_line[0], ':')
      let info.lnum = trim(lnum_col[0])
      let info.col = trim(lnum_col[1])
      let info.text = trim(splited_error_line[1])
      call add(errors, info)
    endif
  endfor
  call setqflist(errors, 'r')
  cwindow
  silent! doautocmd QuickFixCmdPost make
endfunction

autocmd! BufWritePost *.js :call amake#eslint()

Peek 2018-12-04 21-15.gif

ErrorだけでWarningが拾えてないとか同期処理なのででかいファイルをlintするとしばらく固まるとかまだまだな部分は多いですが、一応、動くものになりました。

:h <something>が早い上に、分かりやすいというのは衝撃的な事実でした。

普段は、詰まったらGitHubのドキュメント->ダメならissueなりstackoverflowなりを漁るので、公式のヘルプが丁寧かつ分かりやすく書いてあるのは最高かな?と思いました。
やはり、Vimは最高だった。

レポジトリです。
https://github.com/sasurau4/vim-amake

最後に

本当は、BufferとかText Editorっぽいものを盛り込んで自作プラギンを作りたかったんですが、
Vim力と愛が足りませんでした。でも、quickfix使えたので満足しました。
Dark Vimmerになるために精進します。

Vim 保存時のタイポで']'が作られてしまうことを防ぐ

はじめに

この記事はVim Advent Calendar 2018 6日目の記事です。

私はタイプミスが多いです。
よくやらかしてしまうのが、ファイル保存時の:w:w]とタイポしてしまい、
カレントディレクトリに]という不要なファイルを作ってしまうことです。

色々試したり、教えてもらったりして]を作らない(個人的には)ベストな方法に
辿りついたので備忘録もかねて以下に記載していきます。
(タイプミスをなくせという批判は無視)

1. autocmdを使う

Vimにはモード変更や、ファイルオープンなどのイベントをフックし、自動でコマンドを実行する仕組みが存在します。
以下ではファイル保存前(BufWritePreイベント発生後)にファイル名をチェックし、
不適切なファイル名(])であればファイル保存を無効にしています。

augroup vimrc_augroup
  " expand('<amatch>:t') でファイル名
  autocmd!
  autocmd BufWritePre * if expand('<amatch>:t') == ']' | throw "oops!!" | endif
augroup END

一見するとうまくいっているように見えるかもしれませんが、
throw "oops!!"という部分が綺麗ではありません。

現状のVimでは自動コマンドの停止(今回で言うところのファイル保存無効化)を行う術がないようで、
ワークアラウンドとして、throwを使い例外を投げて無理やり処理を止めていますが、
throwした後も以下のようなエラーメッセージが表示され、とても美しくありません
20181205-203458.png
もっと綺麗な方法がないか調べてみましたが、
ググったりヘルプを見てみても別の方法が全く分からなかったので、
VimのSlackで質問してみたところ、以下2つの方法を教えていただきました。

2. tyru/stoptypofile.vim を使う

実は自分が解決したい問題にドンピシャなプラグインtyru/stoptypofile.vimが既に存在しました。

このプラグインでは:w]だけでなく、:w hogehoge]等保存時のファイル名の末尾に特定のキーワードが付いた場合、
その名前で保存してよいかを確認してれます。
20181205-214112.png
デフォルトでは[]/という3つのキーワードを使ってチェックしていますが、
g:stoptypofile_autocmd_charsの設定を変えることで任意のキーワードに変更することできます。

また、特定のプラグインでは特別なバッファ名(例: [qfreplace], fugitive://, etc..)
を使用しているケースがありますが、チェック対象から除外したい文字列はg:stoptypofile#ignore_patternに指定しておくことができます。(正規表現で指定可能)

不便だと感じたことは大抵誰かが解決ずみですね。素晴らしい世の中。
私は以下に記載するもう1つの方法で問題を解決したのでこちらのプラグインを使っているわけではないのですが、
stoptypofile.vimを使えばかなり柔軟にタイポを検出できるので、お勧めです。

3. 短縮入力(Abbreviations)をタイポ制御に使う

結論から記載すると、以下をvimrcで設定するだけで解決することができます。

cnoreabbrev w] w

とっても簡単ですね。私はこれを自分のvimrcに記載しています。

ちょっとだけ説明すると、Vimには短縮入力を行う機能が備わっています。

" '#in'入力後に非キーワード文字(<Space>, <CR>等)を押すと '#include' に展開する設定
inoreabbrev <buffer> #in #include

この短縮機能をつかってコマンドラインモードでのw]wに展開しているだけです。
もし:w]とタイポしてしまってもちゃんと:wに展開してくれるので、意図した通りにファイルを保存することができます。

最後に

augroup vimrc_augroup
  autocmd!
  autocmd BufWritePre * if expand('<amatch>:t') == ']' | throw "oops!!" | endif
augroup END
" ↓
cnoreabbrev w] w

修正前よりも修正後のほうが綺麗に短く記載することができました。
満足。

自作プラ銀 ver.2018

VimAdventCalendar2018 12/7 の記事です。

前回はRubyの入力補完にはこのようなものがありますという説明もしたように想います。

想えば、Vimのプラ銀作者というのは雲の上のような存在でした。

それがTwitterなどのSNSで教えていただき、かなり攻略できたので、

入力補完のその先をまとめて書いていきたいと想います。

ruby-dictionary3を作り、[D]と表示できるまで、設定をGitHubに乗せてましたね。

この辞書はVimだけでなく、Emacsのauto-completeにも表示できることがわかりました。

私はrobeと共に使用しています。重複しますが、独自定義できるのは、やはり便利です。

この辞書は、neocompleteやdeopleteなどに対応しています。

レポジトリに記述方法を書き記しました。

その後、私はVim/Neovim用のdeopleteを利用するBignyanco(ねこだるま)という、

ruby-dictionary3の単語の羅列を、プラ銀内で完結する、プラグインを書きました。

Rubyの単語追加は、自給自足できるようになりました。増えれば、自分で追加するというものです。

Vimのプラ銀を書きたいと常々思っていたので、

想いは形になると、念は力を持つと、やる気こそ能力だと、

そういう考えでいます。人によりけりですが、私はそんな人です。

ねこだるまのスクリーンショットをdeopleteで追加もしてもらえました。

あと、Tea_Coffee(java)、bistro(html)、も作りました。

deopleteが動作するPython3では、リスト型であれば、単語を追加できるようです。

数学や算数をするときに、暗算などの式を書かずに答えを書く習慣はありませんか?

私は:UpdateRemotePluginsというのが長いので、vim-com(ショートコマンダー)という、コマンドを短縮するプラ銀も作りました。

私にはとても便利な機能です。:のあと↑キーとかで表示されますが、履歴が消えるとまた入力しなおすのはつらいですね。

さらに、自分が作っているシェルzinbeijettは、

:!engine オプション 検索文字列 正規表現または単語

結果が表示できることがデバッグでわかりました。

これも、単語検索で利用すると捗るかもしれません。もちろん、zinbeiも動作しました。

2018年はVim/Neovim/Emacsで使えるツールをいろいろ作りました。

今年の振り返りと長文に付き合っていただきありがとうございます。

[リンク]

ruby-dictionary3

※ GitHubのレポジトリ、記事の参照先。

Bignyanco

※ deopleteの本家から、抜粋。掲載されている動画。

動画ねこだるま

Tea_Coffee

※ GitHubのレポジトリ、記事の参照先。

bistro

※ GitHubのレポジトリ、記事の参照先。

vim-com

※ GitHubのレポジトリ、記事の参照先。

zinbeijett

※ GitLabのレポジトリ、自分管理。

zinbei

※ GitLabのレポジトリ、自分管理。

zinbeiはSJISです。Windowsを想定しています。

zinbei2や、zinbei0などもあります。

ライセンス上、リンクは省略。

自作のMITライセンスの作成物をまとめました。

おや、最後まで読んでくれましたね。では、もうひとつ自作レポジトリの紹介。

build_ijaas

このビルドを使えば、IntelliJとVimの連携をすることができます。

もちろん、IntelliJはレポジトリのREADMEにあるようにバージョンは古めです。

設定もそこに書いてあります。

シンプルに、IntelliJでプラグインを追加、.vimrcに記述を追加。

SyntaxCheckとTea_Coffeeで立派なJava用Vim開発環境ができますよ。

※ 設定通りならばIntelliJを起動した状態のまま(編集先のファイルを開いて)、

※ Vimを開いて(IntelliJで開いたファイルを)編集します。

※ Neovimはできなかったはずです。

※ WindowsもUnixも動作確認済みです。

今年もありがとうございます。来年もよろしくお願いします < org

vimrc 弄りに捧げた時間を測ってくれるプラグインを作りました

はじめに

 この記事は Vim Advent Calendar 2018 の 8 日目の記事です。Vim を本格的に使うようになった今年 1 年の話と、自分の勉強のために作ったプラグインの話です。前半はポエムなので、興味ない方は "そしてふと思う" あたりまで飛んでください。

Vim との出会い

 遠い昔、社会人になって暇すぎて Vine Linux でサーバのお勉強をやっていた頃が初めての Vim との出会いです。その頃の印象は、"ただただ使いにくいエディタ" でした。そもそもモードという概念のうまみが理解できていなかったですし、覚えることが多すぎて直感的に操作できていなかったためだと思います。設定ファイルを書き換えるときに最低限の操作ができていたくらいでした。

再会

 Vim との再会は、仕事でいろいろとお世話になっていた U.S. のエンジニアが日本に出張に来たときでした。とても仕事のできる彼が颯爽とエディタを開いたときの光景と、緑地の上に置かれた "V" のアイコン (gVim) が視界に焼き付いています。ただその当時もそれが Windows 上で動く Vim だとは認識していませんでした。その後しばらくして、「なんかあの緑 V のアイコンを使っている人はすごい人が多いな」と思って正体を調べたところで気が付いたといった感じです。

入門

 ほぼ同時期に、周りに Vim を使っている人がいない職場に変わってしまったのですが、今年の 2 月頃一人 Vim の門を叩きました。その後はいろいろ触ってみて、その豊富な機能とカスタマイズ性にのめりこんで行った感じです。特に感動した機能としては、

  • ci( (要するにoperator + motion/text-objects)
  • v_g_CTRL-A
    • 連番が一発で作れるあれ
  • registers
    • 繰り返ししたり、クリップボードとして使ったり、検索結果を貼り付けられたり、所望の操作を定義できたり
  • terminal
    • ウィンドウとしてたくさん開けたり、テキストを Vim 内でヤンクできたり

といったところでしょうか。もしご存じなくて興味のある方は :help 各キーワード で見てみてください。機能が豊富すぎて、便利な機能でも一回使ったくらいではなかなか覚えきれないです。。
 後は mapping とか options とかをカスタマイズして vimrc に書き込んでいく楽しさですね。どんどん使いやすくなっていくのが楽しいというか。技術屋さんって業務と直接関係ないカスタマイズとか、ツールとか作っているときが一番わくわくしますよね。

Vim を通して広がった世界

勉強会やカンファレンスなど

 自分の普段の業務は組み込み系なんですが、組み込み系はどちらかというととても閉じた世界だと思っています。Web から得られる情報も限られているし、自分たちの仕事の中身をさらけ出すこともしないし、勉強会といって社外に出て行く風潮も自分の周りにはあまりありませんでした。自分も不勉強だったので技術系の勉強会やカンファレンスがあることもほとんど知りませんでした。
 ですが、Vim の情報を得たいがためだけに長年なぜか避けていた Twitter を始めてから、そのあたりの情報が飛び込んでくるようになりました。そのおかげで今年は VimConf 2018 にも参加することができました (そのときの日記)。なかなか都合が付かず行けていないのですが、そのうち Vim 関連のイベントにも参加したいと思っています。なにせ周りに Vim について話せる人がいないので。。
 あとは 娘を Vimmer にしてみんなに見てもらったりもしました。

翻訳のお手伝い

 別の点では、Vim のドキュメントがとても充実していることに驚きました。仕事上ではドキュメントが充実している現場に出会ったことなどそうそうなくて、「こんなにドキュメントがきちんと書かれているなんてなんと素晴らしいんだ!」と感動してしまったんです。最近では少しでも contribution できればと、Vim のヘルプの日本語訳のお手伝いもさせていただいております。
 興味ある方はこちらをどうぞ -> https://github.com/vim-jp/vimdoc-ja-working

そしてふと思う

「あれ、自分ちょっと vimrc 弄りすぎじゃね?」

 たった数秒の、直感と合わなかった操作とか手間とかを無くすために vimrc をいじり始め、自分の Vim 力が低いせいで膨大な時間を費やしてしまう。。楽しいんですけど。たまに (費やした時間 > 一生 Vim を使ったとして改善できた時間) になっていることもある気がします。果たしてこれはトータルの生産性的にはどうなんだと思うわけです。なんか午前中業務の進捗あったっけ?みたいな。楽しいんですけど。

そこで vimrc 弄りに費やしてしまった捧げた時間を測ってくれるプラグインを作りました

 vimrc 弄りを抑制する効果はあまり期待できませんが、ひとまずどれだけの時間がかかっているのかが分かるようにしたいと思った次第です。あと、せっかくなら自分の勉強にとプラグイン化もしたいなと思いました。

 この日は確か eval.jax で escape() のヘルプを読みながら、vimrc 弄りをやっていた日だと思います。あと usr_41.jax では <Plug> の勉強をしてました。
 やっていることはそんなに複雑ではなくて、filetype が 'vim' とか 'help' とかのファイルに対して、BufEnter や BufLeave などの autocommand-events を見て時間を測っているだけです。それをログに残しておいて、コマンドを叩くとログから累積時間を計算するようになっています。
 自分の勉強のためという目的もあり、思いついてからあまり類似のものがあるかどうかも調べずに着手したので、もし似たような感じのプラグインをご存知でしたら教えていただけると嬉しいです。参考にしたいと思います。気が付いたのは @uochan さんが作られている以下のものでしょうか。こちらはグラフィカルでとても綺麗ですね。。

勉強になったこと

 プラグインを作って公開したいと思うとそれなりに理解しないといけないことが増えて、また自分の Vim 力 + α が上がったように思います。

  • オブジェクト指向っぽいことができる
  • いろんな組み込み関数の知識が増えた
  • autoload
  • autocmd 対象ファイルの filetype を取得する方法 (思いのほか苦労した。。。)
  • 辞書のリストを sort する方法
  • ライセンスの種類
  • plugin のテスト
  • Travis CI の基礎

などなど。

やり残したこととか課題とか

  • BufEnter から始まらないケースも結構ある
    • 今はエントリポイントが BufEnter しかないので他のイベントも見たほうがいいかもしれない
  • 起動しっぱなしの場合を考えて、定期的に吐き出しをしたい
    • 会社だとターミナルを起動しっぱなしで帰ることが多いのでちゃんと測れない
    • 日付をまたいだ瞬間をうまく検出できる?
    • イベント発生時に、経過時間と現在時間から日をまたいだことは分かるか
    • そのときにログを 2 日分に分けて出力すればよさそう
  • 年単位の処理になると結構遅いかも
  • 他にも *.vim という名前のリポジトリのファイルはすべて含めるとか?
  • vimrc 弄りって英語で何と言ったら雰囲気が伝わるんだろう?

 興味が湧いた方は是非使ってみてください。Vim に捧げた時間を見ながら満足感に浸っていただければ幸いです。

Happy Vimming!

 以上、今年の Vim 活のまとめでした。今後も自分なりに Vim 道を邁進していく所存であります。みなさんもよい Vim Life を!

Vim8 向けプラグインで Neovim 対応した話

External article

vimconfのujihisaさんの発表を解説します

External article

fキーをnoremap可能にするfmap.vim

今回の主題 :point_down:

begin.jpg

事前知識 (noremap, fFtT)

Hi, メリークリスマス :snowman:

皆さんnoremapしてますか?
例えばnnoremapはnormalモードに対するキーマッピングを定義する機能で、以下のような機能を実現できました。

" <Space>h, j, k, lでwindowを上下左右に移動する
nnoremap <Space>h <C-w>h
nnoremap <Space>j <C-w>j
nnoremap <Space>k <C-w>k
nnoremap <Space>l <C-w>l
" 各レジスタの内容を表示する
nnoremap <silent> q: :<C-u>register<CR>
--- レジスタ ---
""   ような
"0   Vim Advent Calendar 2018ボツ記事(vital.vimで関数型プログラミング入門)^J
"1   :+1: ^J
"2   ## まとめ^J)"
...

またvimは標準でnormalモードのfキー、F, t, Tキーへのマッピングを提供します。
例えばあなたが:point_down: の ^ の上(c)にカーソルを乗せている場合

a b c d e
    ^

feキーを押下するとこの :point_down: 前方のeの位置に移動し

a b c d e
        ^

Fbを押したならこの後方のbの位置に

a b c d e
  ^

teなら前方のeの手前に

a b c d e
       ^

Taなら後方のbの手前に移動します。

a b c d e
 ^
  • f{char}: 後方の{char}に移動
  • F{char}: 後方の{char}に移動
  • t{char}: 前方の{char}の手前に移動
  • T{char}: 前方の{char}の手前に移動

もちろんtextobjとしても使用できます。

a b c d e
    ^

:arrow_down:
dfd
:arrow_down:

a b  e
    ^

話はもう一度noremapに戻って……

nnoremapはnormalモードに対するキーマッピングの定義で、他には以下の*noremapコマンドがあります。

_ :inoremap: insertモード用
_ :vnoremap: virtual + selectモード用
_ :cnoremap: cmdモード用
_ :tnoremap: terminal
_ :noremap: 全部用

あれ……fnoremapがないよ?

概要

fmap.vimは:FNoremapコマンドを提供します :flushed:

例えば以下のように設定することで

nmap f <Plug>(fmap-forward-f)
nmap F <Plug>(fmap-backward-f)
nmap t <Plug>(fmap-forward-t)
nmap T <Plug>(fmap-backward-t)
" 全角括弧の開き
FNoremap p

normalモードの fp キーでこのような :point_down: カーソルジャンプをすることができます!

a ( b c d ) e
     ^

:arrow_down:

a ( b c d ) e
           ^

標準のfFtTを潰したくない場合は<leader>f (FtT) にマッピングすると便利です。

nmap <leader>f <Plug>(fmap-forward-f)
nmap <leader>F <Plug>(fmap-backward-f)
nmap <leader>t <Plug>(fmap-forward-t)
nmap <leader>T <Plug>(fmap-backward-t)

事例

fmap.vimはfFtTでひらがな、カタカナ、句読点等を打つのが面倒だったために作られました。
ですので日本語記事の執筆において、特に助けになります。
任せてください。

例えばこんなMarkdownのコードがあったとします。

にこ「ええ! よく『関数型プログラミングにおいて再帰を直に書く必要はなく、畳込みを用いることができる』っていう言論があるけれど、Semigroupを使えば畳み込みすら直に扱わなくていいってことよ」

1行がとっても長くて、特定の場所にジャンプしにくいですね。
行頭から「畳込みを用いることができる」の「畳」にジャンプしたい場合はf(IME切り替え)たたみ(変換)(Enter)(IME切り替え)もしくはf(IME切り替え)、(Enter)(IME切り替え)という長ったらしいストロークが必要です。

これが普通のMarkdown(※)ならの後に改行を入れてもいいのですが

にこ「ええ!
よく『関数型プログラミングにおいて再帰を直に書く必要はなく、
畳込みを用いることができる』っていう言論があるけれど、
Semigroupを使えば畳み込みすら直に扱わなくていいってことよ」

Qiita Flavor Markdownはなぜかコードの改行に対して実際の改行 (<br/>) を入れてしまうので、そのようにしたくない場合にその手は使えません。

※ CommonMarkのこと

ここでFNoreMap , 、を用いると、f,lのみでジャンプすることが可能になります。
その後fmap.vimはvim標準の;をコロしませんので、f,;lでSemigroupの「S」に飛ぶこともできます。

あるいはfmap.vimはデフォルトで'miで平仮名の「み」にジャンプするマッピングが有効になっているので、「畳」に飛ぶためにf'mihhしてもいいでしょう。

まとめ

fmap.vimはfFtTの後のキーに対してマッピングを定義するためのプラグインです。

日本語記事の執筆活動の支援を目的に作られました。
ただし機能はもっと一般的なものですので、日本語以外の言語でも役立つでしょう。

よければ使ってね :metal::flushed::metal:

end.jpg

Gitのログや差分表示を快適にするdenite-gitdiff

みなさんGitの操作どうしていますか?
CUIでやる人GUIでやる人Vim内でやる人、様々かと思います。
私は、CLIもしくは、vim-fugitive、Gitログ確認は、agit.vim利用しています。
denite-gitを利用しようと思っていますが、まだ使いこなせてないです。

作ろうと思った経緯

現在のブランチって、指定ブランチと比べてどのファイル変更したんだっけ?
このコミットって、どのマージブランチだったっけ?
このマージコミットってどういうコミットがあったんだろ?
って気軽に確認したいことが多々ありました。
GitHubなどであれば、検索すればよいのですが、わざわざ開くの面倒なので、
いつも過ごしているVimから確認できるようにした方が圧倒的に便利です。

上記を解決するために、denite.nvimの拡張プラグイン
denite-gitdiff
を作ってみました!

よく使いそうな操作を紹介します。

gitdiff_file

現在のブランチと指定ブランチとの差分ファイル一覧を表示させます。
差分のファイル一覧が出た状態で、差分をみたいファイルに対して、openvdiffアクションなどを実行すると、
差分が表示されます。(vim-fugitiveプラグイン依存)
下写真は、Neovimmasterブランチとv0.3.1タグを比較し、
どのファイルが変更されたっけ?を表したものです。
gitdiff_file-demo

gitdiff_log

現在のブランチと指定ブランチとの差分の、コミットログを表示させます。(GitHubのPRのコミットログみたいなやつです)
ターゲットを指定しなかった場合は、現在ブランチのログを表示します。

下記のようなコマンドで、現在開いているファイルだけのコミットログの表示もできます。

:Denite gitdiff_log::::`expand('%:p')`

コロン多すぎになっちゃった

branch_log アクション

このコミットってどのマージブランチだったんだろ?っていうときに使います。
そのブランチコミットログ一覧を表示させます。(GitHubのPRのコミットログ+マージコミットって感じです)
例えば、下記PRを例にしてみます。
https://github.com/neovim/neovim/pull/9322

gitdiff_branchlog-demo

merge_log アクション

このマージコミットってどんなコミットログだったんだろ?ってときに利用します。

さいごに

今後、非同期処理に切り替えてみようかなと思っています。
細かい部分の修正とかとか機能追加とかとかやっていきます。
ぜひぜひ使ってみてください。Windowsで動作確認していないので、動くかわからないです。(すんません。。。
Vim内でのGit生活をより良いものにしてきたいですね!

Vimのexecute()と組み合わせて便利なコマンド

External article

Vimのセッション機能を使う

Vim Advent Calendar 2018 14日目 代打記事です。

みなさん、vimにセッション機能があるのはご存知でしょうか?
便利そうなので使ってみようと思っていますが、せっかくなのでVim scriptの勉強も兼ねてコマンド作ってみました。

fzf.vimを使うとこんな感じになります。
image.png

以下のコードをvimrcに貼ればそのまま使えるとおもいます。

[2018-12-22] 追記

プラグイン化しました。
また、記事中のソース若干変更は入っています。
https://github.com/skanehira/vsession

セッションファイル保存先

~/.vim/sessions/配下にセッションファイルを保存します。
ディレクトリがなければ作るようにしています。

" session path
let s:session_path = expand('~/.vim/sessions')

if !isdirectory(s:session_path)
    call mkdir(s:session_path, "p")
endif

セッションの保存

:SaveSession test1.vimという感じで、セッションファイル名を指定して保存します。
同じファイル名を指定すると上書きされるのでご注意。

" save session
command! -nargs=1 SaveSession call s:saveSession(<f-args>)
function! s:saveSession(file)
    execute 'silent mksession!' s:session_path . '/' . a:file
endfunction

セッションの復元

:LoadSession ~/.vim/sessions/test1.vimという感じで、セッションファイルのパスを指定して読み込みます。

" load session
command! -nargs=1 LoadSession call s:loadSession(<f-args>)
function! s:loadSession(file)
    execute 'silent source' a:file
endfunction

fzf.vim版はこちらです。

command! FloadSession call fzf#run({
\  'source': split(glob(s:session_path . "/*"), "\n"),
\  'sink':    function('s:loadSession'),
\  'options': '-m -x +s',
\  'down':    '40%'})

セッションの削除

:DeleteSession ~/.vim/sessions/test1.vimという感じで、削除します。

" delete session
command! -nargs=1 DeleteSession call s:deleteSession(<f-args>)
function! s:deleteSession(file)
    call delete(expand(a:file))
endfunction

fzf.vim版はこちらです。

command! FdeleteSession call fzf#run({
\  'source': split(glob(s:session_path . "/*"), "\n"),
\  'sink':    function('s:deleteSession'),
\  'options': '-m -x +s',
\  'down':    '40%'})

キーマッピング

こんな感じでマッピングしたら楽です。

nnoremap <Leader>se :SaveSession 
nnoremap <Leader>lse :FloadSession<CR>

参考記事

https://thinca.hatenablog.com/entry/20100201/1265009821
https://qiita.com/shinshin86/items/6e6cbdb77cb59b87d21f
http://nanasi.jp/articles/code/io/file-path.html

最後に

初めてVim script書いたので、おかしなところがありましたら教えて頂けると助かります。
ちなみに、vimのセッションプラグインはありました。

使ってないのですが、良さげなので興味ある方はどうぞー

https://github.com/Shougo/unite-session
https://github.com/xolox/vim-session

Vimのキーマッピングに迷わないための俺々キーマッピングルールをご紹介します

tl;dr

  • キーマッピングはぶっちゃけ好み
  • 私好みのキーマッピングはこうですよという話
  • お前のキーマッピングは間違っている的なレスお待ちしております

おしながき

キー マッピング対象
<Space> 標準コマンドのPrefix
,(カンマ) <Leader>割当用かつ言語系以外の共通プラグインのPrefix
\\(バックスラッシュ) <LocalReader>割当用かつ言語別プラグインのPrefix
<C-j> FuzzyFinder(denite.nvim, fzf.vim)のPrefix
<C-n>, <C-p> QuickFixの移動(:cnext, :cprevious)を割当
H, L ^をマップ
K 言語毎のドキュメント参照コマンド割当
s vim-sandwich

上記表がすべてなのですが、
一応解説を以下に記載しました。暇つぶしにどうぞ

  • デフォルトキーマップ解説
    • 1文字右へ移動
  • ルール
    • プラグインのコマンドを割当しない
    • 標準コマンドのショートカットを割当する
    • 標準キーバンドの打ちづらいキーを割当する
    • 自作関数の割当する
  • # .vimrcのリロード
    nnoremap <Space>s :source $HOME/.vimrc<CR>
    # ファイル保存のショートカット
    nnoremap <silent> <Space>w :<C-u>w<CR>
    # <c-^>のショートカット
    nnoremap <Space><Space> <c-^>
    
    # QuickFixのトグル
    function! ToggleQuickfix()
        let l:nr = winnr('$')
        cwindow
        let l:nr2 = winnr('$')
        if l:nr == l:nr2
            cclose
        endif
    endfunction
    nnoremap <script> <silent> <Space>f :call ToggleQuickfix()<CR>
    

,(カンマ)

  • デフォルトキーマップ解説
    • f*と押した場合にカーソル位置から右の*に移動するが、,(カンマ)はそれを左方向に繰り返す
  • ルール
    • <Leader>として扱う
    • 上記<Space>に該当するコマンドは割当しない
    • プラグインのコマンドのPrefixとして用いる
    • プラグラミング言語毎のプラグインのコマンドを割当しない
    • vim-quickrun

      nnoremap <Leader>r :<C-U>QuickRun<CR>
      xnoremap <Leader>r gv:<C-U>QuickRun<CR>
      
    • nerdtree

      nnoremap <silent> <Leader>t :<C-u>NERDTreeToggle<CR>
      nnoremap <silent> <Leader>f :<C-u>NERDTreeFind %<CR>
      
    • tagbar

      nnoremap <Leader>o :<C-u>TagbarToggle<CR>
      
    • caw.vim

      " Add comment to beginning line(consider whitespace)
      nnoremap <Leader>c <Plug>(caw:hatpos:toggle)
      vnoremap <Leader>c <Plug>(caw:hatpos:toggle)
      " Add comment to beginning line(ignore whitespace)
      nnoremap <Leader>, <Plug>(caw:zeropos:toggle)
      vnoremap <Leader>, <Plug>(caw:zeropos:toggle)
      
    • open-browser.vim

      nnoremap <Leader>b <Plug>(openbrowser-smart-search)
      vnoremap <Leader>b <Plug>(openbrowser-smart-search)
      

(バックスラッシュ)

  • デフォルトキーマップ解説
    • キーマップ拡張のために空けてある
  • ルール
    • <LocalLeader>として扱う
    • 上記,(カンマ)に該当するコマンドは割当しない
    • プラグラミング言語毎のプラグインのコマンドを割当する
    • autocmdを用いて、各言語毎にキーマップが切り替わるようにする
    • vim-go

      augroup GoCommands
          autocmd!
          autocmd FileType go nnoremap <silent><LocalLeader>r  <Plug>(go-run)
          autocmd FileType go nnoremap <silent><LocalLeader>b  <Plug>(go-build)
          autocmd FileType go nnoremap <silent><LocalLeader>tt <Plug>(go-test)
          autocmd FileType go nnoremap <silent><LocalLeader>tf <Plug>(go-test-func)
          autocmd FileType go nnoremap <silent><LocalLeader>ts :<C-u>GoTests
          autocmd FileType go nnoremap <silent><LocalLeader>ta :<C-u>GoTestsAll
          autocmd FileType go nnoremap <silent><LocalLeader>m  <Plug>(go-imports)
          autocmd FileType go nnoremap <silent><LocalLeader>i  <Plug>(go-install)
          autocmd FileType go nnoremap <silent>K               <Plug>(go-doc)
          autocmd FileType go nnoremap <silent><LocalLeader>d  <Plug>(go-doc-browser)
          autocmd FileType go nnoremap <silent><LocalLeader>R  <Plug>(go-rename)
          autocmd FileType go nnoremap <silent><LocalLeader>c  <Plug>(go-coverage-toggle)
          autocmd FileType go nnoremap <silent><LocalLeader>n  <Plug>(go-referrers)
          autocmd FileType go nnoremap <silent><LocalLeader>a  <Plug>(go-alternate-edit)
          autocmd FileType go nnoremap <silent><LocalLeader>e  <Plug>(go-iferr)
          autocmd FileType go nnoremap <silent><LocalLeader>p  <Plug>(go-implements)
      augroup END
      
    • jedi-vim

      let g:jedi#goto_command = "<C-]>"
      let g:jedi#goto_assignments_command = "<Localleader>g"
      let g:jedi#goto_definitions_command = ""
      let g:jedi#documentation_command = "K"
      let g:jedi#usages_command = "<Localleader>n"
      let g:jedi#rename_command = "<Localleader>R"
      
    • vim-lsp

      augroup PylsCommands
          autocmd!
          autocmd FileType python nnoremap <C-]> :<C-u>LspDefinition<CR>
          autocmd FileType python nnoremap K :<C-u>LspHover<CR>
          autocmd FileType python nnoremap <LocalLeader>R :<C-u>LspRename<CR>
          autocmd FileType python nnoremap <LocalLeader>n :<C-u>LspReferences<CR>
      augroup END
      

  • デフォルトキーマップ解説
    • 1行下へ移動
  • ルール
    • FuzzyFinderプラグインのPrefix
    • <C-j>の後続のキーはCtrlとの同時押しキー(<C-某>)とする
    • denite.nvim

      " Current direcotry files
      nnoremap <silent> <C-j><C-p> :<C-u>Denite file/rec/git -highlight-mode-insert=Search<CR>
      " Buffer files
      nnoremap <silent> <C-j><C-b> :<C-u>Denite buffer -highlight-mode-insert=Search<CR>
      " Outline current file
      nnoremap <silent> <C-j><C-o> :<C-u>Denite outline -highlight-mode-insert=Search<CR>
      " Reccent file
      nnoremap <silent> <C-j><C-r> :<C-u>Denite file_old -highlight-mode-insert=Search<CR>
      " Help tag
      nnoremap <silent> <C-j><C-h> :<C-u>Denite help -highlight-mode-insert=Search<CR>
      
    • fzf.vim

      " Gather files recursive and nominates all file names under the search directory
      nnoremap <silent> <C-j><C-j> :<C-u>Files<CR>
      " Git Status
      nnoremap <silent> <C-j><C-g> :<C-u>GFiles?<CR>
      " Ctags index project
      nnoremap <silent> <C-j><C-]> :<C-u>Tags<CR>
      " My cli setting
      nnoremap <silent> <C-j><C-v> :<C-u>Files ~/.dotfiles<CR>
      

,

  • デフォルトキーマップ解説
    • <C-n>のデフォルトは1行下へ移動
    • <C-p>のデフォルトは1行上へ移動
  • nnoremap <silent> <C-p> :<C-u>cp<CR>
    nnoremap <silent> <C-n> :<C-u>cn<CR>
    

QuickFixの上下移動はかなり使用頻度が高いのに:cn:cpは3打と長い。長過ぎる。
そこへいくと<C-n>のデフォルトは下移動<C-p>のデフォルトは上移動とj,kの動作ともろかぶりである。
こんなにうちごろなキーを重複した操作にしておくなんてとんでもない。

H,L

  • デフォルトキーマップ解説
    • Hはスクリーン上端に移動
    • Lはスクリーン下端に移動
  • noremap H ^
    noremap L $
    

デフォルトは使えるっちゃ使えるのですが
行頭、行末移動に比べたら使用頻度は雲泥の差なのでいいかなと。

本来の行頭移動(先頭の非空白文字列へ移動)の^(ハット)と行末移動の$(ダラー)はどうにも押しづらいのです。
できるだけデフォルトに慣れたほうがいいのですが、SHIFT+数字キーって指辛い。

ですが、
左に移動するのを強化する的な意味でHが行頭移動、
右に移動するのを強化する的な意味でLが行末移動、
は意味的にもしっくりくる気がしている。

しかし、このキーバインドに慣れるとサーバのVimで誤爆しまくること請け合いです。

K

  • デフォルトキーマップ解説
    • カーソル下のワードに対してmanコマンド実行し、Vim上に表示する
    • 今回記事書いてて初めて知った
  • ルール
    • 上記デフォルトの拡張を言語毎に最適なコマンドに割当する
    • vim-go

      augroup GoCommands
          autocmd!
          ...
          autocmd FileType go nnoremap <silent>K               <Plug>(go-doc)
          ...
      augroup END
      

s

  • デフォルトキーマップ解説
    • カーソル下1文字を削除してインサートモードへ

vim-sandwichのREADMEに書いてある割当がsなので従っています。それだけです。はい。

ものすごく参考になる記事

最近作ってるNeoVimプラグインの話です

External article

VimをWindows(MSVC)でビルドしてみようか!

External article

Windows 環境での自分の Vim 環境の立ち上げ方 (2018/12 版)

この記事は Vim Advent Calendar 2018 の 18 日目の記事です。

昨日は、 dohq さんの WindowsでVimのビルドしてみようか! でした。
VimConf2018 の動画を見て、自分もビルドしたかったのでとてもタイムリーでした。

関係あるのかないのか、このページでは Windows 環境で新規に Vim をセットアップする話をします。

Goal

自分が Windows な環境に新しく Vim をセットアップするケースでのやり方をまとめます。
この方法以外にも色々な方法がありますが、以下の点で良いと考えています。

  • 新しい Windows 環境に簡単に自分の Vim を立ち上げれる
  • (netupvim により) 簡単に vim/vim-win32-installer 版の最新の Vim に追従できる

やり方

netupvim をインストールして vim/vim-win32-installer 版の Vim をインストールする

netupvim は Windows 用の Vim (香り屋版) をネットワーク経由で更新、修復、もしく はインストールするためのプログラムです。
https://github.com/koron/netupvim

netupvim は、 Vim (香り屋版) をインストール/更新するためのプログラムですが、設定によっては vim/vim-win32-installer 版 をインストール/更新することができます。
netupvim を使ってうれしい部分は、簡単に最新版に追従出来る事です。

以下からダウンロードして、適当なディレクトリに解凍してください。

この状態で UPDATE.bat を実行すると、カレントに Vim (香り屋版) がインストールされますが、今回は vim/vim-win32-installer 版 が使いたいので以下を実行して netupvim.ini を作成してから実行します。

$ echo source = "vim.org" > netupvim.ini

この状態で UPDATE.bat を実行すると vim/vim-win32-installer 版 がダウンロードされます。
初回の UPDATE.bat 実行後は、再度 UPDATE.bat を実行することでその時点で最新の vim/vim-win32-installer 版 に更新することができます。

vimrc / gvimrc を置く

github.com から自分の vimrc / gvimrc を落としてきてそれらしい場所に置きます。

:help vimrc によると MS-Windows は以下のいずれかに置く必要があることがわかります。
自分は後述の volt が $HOME/vimfiles 以下を使うので $HOME/vimfiles/vimrc に置いています。

  • $HOME/_vimrc
  • $HOME/vimfiles/vimrc
  • $VIM/_vimrc

なお、 $HOME$VIM がどこか分からない場合は、 vim / gvim から以下を入力すると確認できます。

:echo $VIM

volt でプラグインをインストールする

volt は Go で作成された Vim8 用のプラグインマネージャです。
色々な機能がありますが、ここでは新しい環境にプラグインをインストールするために使います。
以下からダウンロードできます。

解凍して PATH の通る場所に置いた後は、以下を実行して自分が使用するプラグインをインストールします。
例えば自分の場合は、以下を実行します。
fatih/vim-go をインストールする時は管理者権限が必要なので注意が必要です。

volt get tpope/vim-fugitive
volt get itchyny/lightline.vim
volt get hotchpotch/perldoc-vim
volt get fatih/vim-go
volt get ctrlpvim/ctrlp.vim
volt get sago35/mark.vim
volt get sago35/molokai
volt get sago35/yankrev.vim
volt get cocopon/vaffle.vim
volt get vim-jp/vimdoc-ja
volt get prabirshrestha/async.vim
volt get prabirshrestha/vim-lsp

まとめ

ということで、 Windows 環境での自分の Vim 環境の立ち上げ方 (2018/12 版) を書きました。
Windows な人は参考になる場合もあるかもしれません。

おまけ

↑ で色々書いていますが、実際はコマンドラインのみにしたいので以下のやり方で実施しています。
Go 1.9 or later の環境があることが前提となるので、該当している場合はこちらの方が良いかもしれません。

$ go get -u -v github.com/koron/netupvim
$ go get -u -v github.com/vim-volt/volt
$ go get -u -v github.com/mattn/sudo

上記をインストールした後、 Vim をインストールしたいフォルダを作ってそこから以下を実行します。
netupvim を実行すると、カレントフォルダに Vim が展開されます。
fatih/vim-go を実行する時は、 sudo を忘れずに。

$ echo source = "vim.org" > netupvim.ini
$ netupvim
$ volt get tpope/vim-fugitive
$ volt get itchyny/lightline.vim
$ volt get hotchpotch/perldoc-vim
$ volt get ctrlpvim/ctrlp.vim
$ volt get sago35/mark.vim
$ volt get sago35/molokai
$ volt get sago35/yankrev.vim
$ volt get cocopon/vaffle.vim
$ volt get vim-jp/vimdoc-ja
$ sudo volt get fatih/vim-go

Vim を更新する時は以下を実行します。

$ netupvim

コマンドラインで完結するので、 batch なりで自分のセットアップスクリプトを書いておくと便利です。

初心者から見たvim上達について

Vim Advent Calendar 2018 19日目の記事です。

こんにちわ

今年の夏終わり頃に本格的にvimを使い始めて、すっかりvimにハマったので初心者ですが

  • vimの良いところ
  • vimの上達への道のり

について、自分の考えを書いていこうと思います。
なぜvimが良いのか、どこが良いのか気になる方にとっては少しメリットを知っていただき、
vimを始めたばっかりで、他の人はどのようにvim力を鍛えているのか参考になれたらと思っています。

なお、vimの上達は編集速度が向上するという意で書いています。

ちなみに、自分のレベルはこちらの記事の判定基準でいうと大体8くらいかなという感覚です。

vimの素晴らしいところ

個人的に使っていて、vimのここは良いところを紹介していきます。

テキスト編集がとても楽

コーディングしているとき、"Hello World"というような"で囲った文字の中身だけを変えたい時はありませんか?
テキストオブジェクト/オペレーター/モーションを用いることで、その中身を一発で消したあとにinsert modeに切り替えることができます。

ci.gif

他にも"'[{などで囲ったの中身だけor記号も含めをカット・コピーできたり、
複数行をまとめカット・コピーしたりできます。

上記のように、テキスト編集をサクッとできるのがとても魅力に感じました。
これでvimにハマったと言っても過言ではないくらいです。
しかも標準搭載です。半端ないです。

オペレーターなどついてはこちらの記事に概要が書かれているので、
知らない方はぜひ読んでみてください。

カーソルの移動が楽

カーソルをいかに早く目的地に持っていけるかがコーディングする上でとても大事と思っています。
vimでは入力モードとコマンドモードがあり、コマンドモードでは
- hjkl
- w/W
- b/B
- e/E
- 0
- $
などを用いてカーソルを移動させることが一般的にだと思います。

上記とは別で、以下の4つのカーソルの移動手段があります。

  1. {count}+j/kで現在行から上下に{count}行分移動する
  2. f{char}で現在の列から{char}が最初に現れる列まで移動する
  3. :{count}/{count}Gで指定した行まで移動

個人的に1と2をよく使います。
1のように現在行からの差分だけ移動はvimrcにset relativenumberを追記して使っています。

ただ、盲目的に1と2を使うわけではなく、
ケースに合わせて使うのが一番良いです。

一行上に移動するだけならkで十分ですし、一つとなりの単語に移動するならwで十分なので、
場合によって使い分けることはとても大事です。

プラグインが豊富

vimを使う上でプラグインを導入は必須と思っています。
vimの生みの親であるBarm氏はvimconf2018でプラグインは使っていないと言っていたようですが、
どの様にコーディングしているのか気になるところです。

プラグインを導入することで、

  • コーディング時の補完してくれる
  • 編集したいファイルをサクッと開ける
  • vimの見た目をかっこよくできる
  • スニペットを使用できる

などといったメリットがあります。
特にスニペットと補完はコーディングが捗りますので、プログラマにとって重要でしょう。

ただIDEを使っているなら大抵は設定しなくても提供されている機能だったりするので、
ここはIDEメインの方にとってメリットと感じないのかもしれません。

vimでどんなプラグインが人気か、どんなプラグインがあるのかを探したい方はVim Awesomeで検索できるので、
時間がある時に見てみてると幸せになれます。

プラグインについて多くの方が記事を書いてありますので、
自分の開発に役たちそうなモノをググった記事からぜひ見繕って置きましょう。
こちらの記事はおすすめです。

ちなみに、これだけは入れておけと個人的に思っているプラグインは以下になります。

テキスト編集を学べるサービスがある

最近知ってやってみましたが、VimGolfというサービスがあり、こちらは
お題に沿ったテキスト編集をどれだけ少ないキーストロークで実現できるか
を競うコンテストになります。

導入に関してはこちらの記事がわかりやすいです。

こちらのサービスでは、自分にはない発想を知り、それを学び、
コーディングに取り込めるという大きなメリットがあります。

自分より効率良い編集方法を知っている人がたくさんいて、その編集方法を知れるのってすごいなと感心しました。
vimmerの皆さんはぜひやってみてください。
とても勉強になります。

vimの上達への道のり

個人が感じたvimの良いところを書きましたが、
ではvimを使い方を上達させるにどうしたらよいのか?というところを初心者なりの考えを書いていきます。

入力モードとコマンドモードに対する考え方

Windowsなどのテキストエディタを使い慣れている人からしたら、vimのこのコマンドモードというのは慣れないと思います。

  • なぜ入力するのにいちいちモードを切り替えないと行けないのか?
  • コマンドモードのメリットは何だろう?

と最初はよくわりませんでした。

しかし、コマンドモードでのオペレーター・モーション・テキストオブジェクトによるテキスト編集方法を知ってから腑に落ちました。
vimではコマンドモードがあるからこそ、複雑なテキスト編集や他の操作ができるようになっています。

vimの作者やメンテナの方がどのような狙いがあって、このような機能を用意したのはわかりませんが、
自分は上記の様に捉えました。

コマンドモードを用意することで、ただのエディタではなくなり、
より幅広くいろんなことができるようになっているのは間違いないので、すごいなと感心しました。

モードを切り替えないと編集できないではなく、
モードを切り替えることで複雑な編集ができるようになるという捉え方のほうが腑に落ち、
vimを使う意味を見出せるのではないのかなと思います。

これからvim入門しようとする方はこのように考えてみてはどうでしょうか?

オペレーター・モーション・テキストオブジェクトを積極的に使う

vimのメリットについて書いた通り、オペレーターなどの組み合わせで複雑なテキスト編集をより簡潔にできます。

特に行の移動に関してはset relativenumberをして{count}j/kによる移動をおすすめです。
これで上下の移動は楽になると思います。

たとえば、例えばこの様に10行上の先頭にhelloだけ書き換えたい場合は10kcwだけでサクッとできます。
そう、vimならね。
k.gif

また、f{char}を使ったほうが早い場合は積極的に使っていきましょう。
例えばgorillaの部分だけ書換えたい場合はfgcwだけでサクッとできます。
そう、vimならね。
f.gif

他のテクニックは脱初心者を目指すなら知っておきたい便利なVimコマンド25選 (Vimmerレベル診断付き)がおすすめです。
細かく纏められていて素晴らしい記事です。

VimGolfで勉強する

ある程度慣れてきたら、今度はVimGolfでより効率がよい編集方法について学ぶことをおすすめします。
ただ、あまりにも複雑でコマンドを打つのが遅くなるようなやり方に関してはコーディングに取り入れる必要はないと思っています。
そこは個人差があるので、自分が複雑過ぎだなと思ったらこういうやり方があるんだって程度にとどめておきましょう。

日本語マニュアルを読む

vim-jpのみなさんが翻訳してくださったマニュアルがあるので、
慣れてきたら、マニュアルを読むとvimの事をもっと知れます。

マニュアルはこちらです。

実践Vim 思考のスピードで編集しよう!を読む

実践Vim 思考のスピードで編集しよう!はかなり評判が良く、購入して読んでいますが、vimの標準機能TIPSがたくさんあります。

vimについて体系的に学びたい方は、ぜひ購入して読んでみてください。
編集に関する考え方について感心できるものばっかりです。

ちなみに自分は読み終える頃にはきっと、
自分も思考スピードで編集できるようになっているだろうと想像しながら読んでいます。

初心者が見ると幸せになる記事たち

とってもためになる記事を先人たちが残してくれているので、こられの記事を読んでvim力を上げて、
より良いvimライフを楽しんでいきましょう。

まとめ

  • vimを使う意義を見出し楽しむ
  • vimを普段から使う
  • キーストロークを減らすための工夫をする
  • 先人たちの残した財産を使う(記事、マニュアルなど)

以上がvim上達のための道筋と考えています。

vimを忘れずに、楽しむ心を持ち、先人に学び、一日はvimから始まりvimで終わる生活していればすぐ慣れます。
これからvimを使ってみようと思う方、挫折した方は諦めず楽しみを探しつつvim生活を満喫していきましょう。

Vimのプラグイン管理 is なにがいい?

はじめに

Vimでプラグイン管理する際、プラグインマネージャーでプラグインを管理することが多いかと思いますが、
さまざまな種類があるので、結局どれを選べばいいのかわからないですよね。
そこで今回は自分なりにまとめてみました。

vim-pathogen

pathogen.vimはvimプラグインの読み込みパスを変更するプラグインです。
~/.vim/bundle/<プラグイン名>/ 以下の各ディレクトリも ~/.vim/ 直下と同じように読み込み
これにより、bundle/ 以下にプラグインごとに別のディレクトリを切って管理をすることができるようになります。

導入方法

pathogen.vim ファイルをローカルに配置します。

$ cd ~/.vim/autoload/
$ wget https://raw.githubusercontent.com/tpope/vim-pathogen/master/autoload/pathogen.vim

~/.vimrcファイルに以下を記述します。

execute pathogen#infect()
filetype plugin indent on
syntax on

Vundle.vim

Vundle は Rails 3 で採用されている、Gem 管理システム Bundler に影響を受けたプラグイン管理システムです。

導入方法

Vundleをインストール

$ git clone https://github.com/gmarik/vundle.git ~/.vim/bundle/vundle

vimrcに以下の記述

set nocompatible              " be iMproved, required
filetype off                  " required

set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()
# 追加するプラグインを記述
Plugin 'VundleVim/Vundle.vim'

call vundle#end()            " required
filetype plugin indent on    " required

neobundle.vim

Vundleにインスパイアされたプラグイン管理システム。Vundleの改良版。

Vundleとの主な違い
・Subversionなどにも対応しているため、vim.orgとgithub.comに縛られない
・プラグインのリビジョンを指定することができる

※dein.vimがでた影響でneobundleのメンテはされてない

導入方法

配置先のディレクトリ作成

$ mkdir -p ~/.vim/bundle

NeoBundleを取得

$ git clone git://github.com/Shougo/neobundle.vim ~/.vim/bundle/neobundle.vim

vimrcに設定

# 以下を追記
set nocompatible
filetype plugin indent off

if has('vim_starting')
  set runtimepath+=~/.vim/bundle/neobundle.vim
  call neobundle#rc(expand('~/.vim/bundle'))
endif 

NeoBundleFetch 'Shougo/neobundle.vim'

# 入れたいプラグインを追加
NeoBundle 'scrooloose/nerdtree'
NeoBundle 'mattn/emmet-vim''

filetype plugin indent on

プラグインをインストール

:NeoBundleInstall

プラグインをアップデート

:NeoBundleUpdate

プラグインを削除

:NeoBundleClean

vim-plug

NeoVim 互換のマネージャ シンプルな記述が魅力。
性能でいうとneobundleのほうが上だがシンプルさが根強い人気の印象。

導入方法

vim-plugをインストール

$ curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

基本的な記述

call plug#begin()
インストールしたいプラグインのgithubのリポジトリを指定
call plug#end()

dein.vim

neobundle.vim をリプレースするべく新設計のもとに作られた。NeoVim でも動作する

・コマンドはなく、関数のみで構成されている
・プラグインの設定はTOMLで行う。
・読み込み速度が早い。

導入方法

$ mkdir -p ~/.vim/dein/repos/github.com/Shougo/dein.vim
$ git clone https://github.com/Shougo/dein.vim.git \
    ~/.vim/dein/repos/github.com/Shougo/dein.vim

追加するプラグインを記述

if &compatible
  set nocompatible
endif
set runtimepath+=~/.vim/dein/repos/github.com/Shougo/dein.vim

call dein#begin(expand('~/.vim/dein'))

call dein#add('Shougo/dein.vim')
call dein#add('Shougo/vimproc.vim', {'build': 'make'})

call dein#add('scrooloose/nerdtree')

" (中略)

call dein#end()

まとめ

ざっくりと一覧を挙げましたが、筆者の周りはdein.vimを使ってる方が多いかなという印象です。
(私はシンプルな記述が魅力なvim-plugが好きです。)
次々とプラグインマネージャーが出てきて、管理のしやすさや速度も向上していますが、あまりプラグインを入れすぎると、重くなってしまいますので必要最低限のプラグインだけいれればいいかなと思います。

人によって環境は異なってくるので、自分にとってベストなものを選べばいいと思います。
それでは、皆さんステキなVimライフを!

非エンジニアのためのvim

GROOVE Xという会社でエンジニアをやらせてもらってます。ロボット作ってます。
この記事が公開される頃には開発中のロボットが発表されてるはず!

弊社では、エンジニアだけでなくアニメーターと呼ばれる人たちがロボットの振る舞いを自分の手でプログラミングしています。

ほとんどのメンバーはCGアニメーションやゲームCGなどの制作経験者ですが、全員がIDEをつかってpython3を記述しているので、いつでもvimの使い方を説明できるように記事にしておこうと思います。

vimについて

LinuxやMac(Unix系のOS)で主流のエディタviの拡張版のエディタがvimです。
大抵の環境にはviが入っていて、viを扱いやすくしたのがvimになります。
viやvimはコンソール上で実行するコマンドになってますが、別のウィンドウで立ち上げたりできるgvimというのもあります。
gvimは香り屋のが有名です。https://www.kaoriya.net
windowsでも使えるし、マウス操作が最初からできるのでまずはgvimから入るのがオススメ

vimでプログラムを書くメリット

vimは書いたプログラムの編集に特化していて、キーバインド(ショートカットみたいなの)で操作することで、大量のコードを書く手間を減らすことができ、マウスもいらないのでsshでログインした先でもテキストファイルを編集することが簡単にできます。
IDEを使っていると、テキスト選択、スクロールなどはマウスを使ってやらないといけないですが、vimの場合そういった作業を全てキーボードだけで完結させられることができます。

モード

vimには大きくカーソルモードとインサートモードがあります。
編集モードはその名の通り、テキストを編集するためのモードで、プログラムを書くときはこのモードでメモ帳のごとくタイピングをしてプログラムやテキストファイルを編集します。
カーソルモードは、テキストファイルを閲覧するのに特化してますが、コピペしたり、文字列を置換したりすることもできます。

これだけ覚えてとりあえず触ってみて

起動時はカーソルモードで始まる、カーソルモードから抜けるにはiを押す
あとは思うままに編集する

すっごい使いづらいと思う

何が足りないと思うかは素のvimを使ってから色々考えるのがいいです。
例えば、行番号を表示したい場合はカーソルモードで
:set number
と入力する
自動でインデントしたい場合は
:set indent
する
起動時に実行したい場合は~/.vimrcに記述する
そうやってvimの使い方を一つづつ覚えて行くのがvimと仲良くなるコツです。
最初はIDEに戻りたくなると思うけど、使っていくうちにIDEを使うよりも効率が良くなるタイミングが来るはず!

tsuquyomi を魔改造している

External article

Vim に VOICEROID で喋らせた

External article

Vim の構文ハイライトでクリスマスツリー🎄を飾ってメリクリする

External article

Vim script で機械学習

この記事は Vim Advent Calendar 2018 の最終日 25 日目の記事です。昨日は rhysd さんの「Vim の構文ハイライトでクリスマスツリー🎄を飾ってメリクリする」でした。今年も Vim Advent Calendar は完走しました。皆さんお疲れさまでした。

はじめに

昨今 Vim script は目覚ましい進化を遂げ、Vim script からタイマーも実行でき、プロセスを起動して非同期に通信できる様にもなりました。以前の様にコマンドを実行して Vim でのテキスト入力を妨げる事も少なくなってきました。

Vim script が扱える数値も既に64bit化されています。現在 pull-request されている blob 型 も入れば、ほぼ他の言語と同等の機能を得たと言えるでしょう。1

しかしながら世の中のプログラミング言語は機械学習へと足を延ばし、大量のデータを計算する仕組みを実装し始めています。Vim script も遅れを取ってはなりません。

そう思いませんか?

of course

機械学習やるぞ

そこで今回、Vims script で機械学習を扱う為の仕組みを用意しました。外部コマンドは使っていません。また if_python 等の言語拡張も使っていません。Vim script のみで実装しました。

https://github.com/mattn/vim-brain

Vim script で実装したニューラルネットワークになります。中身は Go言語で実装されている goml/gobrain の完全移植版となります。ただし以前ブログで紹介した、goml/gobrain のモデル保存機能も実装してあります。

Golang だけでやる機械学習と画像分類

えっ?Vim script だけで機械学習が出来るんですか?それ超便利だと思いませんか?

excellent

まずは簡単な学習から

使い方は goml/gobrain と変わりません。まずは XOR を学習させてみます。

function! s:test() abort
  call brain#srand(0)

  let l:patterns = [
  \ [[0.0, 0.0], [0.0]],
  \ [[0.0, 1.0], [1.0]],
  \ [[1.0, 0.0], [1.0]],
  \ [[1.0, 1.0], [0.0]],
  \]

  let l:ff = brain#new_feed()
  let l:ff.Init(2, 2, 1)
  call l:ff.Train(l:patterns, 1000, 0.6, 0.4, v:false)
  call l:ff.Test(l:patterns)
endfunction

call s:test()

pattern は2項の配列で構成され、左が入力、右が期待値になります。brain#new_feed() でフィードフォワードを生成し、Init で初期化します。引数はそれぞれ

  • 1入力の値の個数
  • 隠れ層の数
  • 1期待値の値の個数

となります。XOR は2つの入力から1つの期待値を出すので 2 と 1 ですね。

XOR

Train の第一引数に作ったパターンを渡し、イテレーション数 1000、学習レート 0.6、モーメンタムファクタ(活性化係数) 0.4、デバッグ false で実行しています。

このコードですと、Vim script でも2秒程で学習と検証が完了します。余裕ですね。

image.png

結果もちゃんと出ていますね。

Jupyter Notebook でオシャレに機械学習

Vim Advent Calendar その2 の nohararc さんの記事「Jupyter notebookでもVim scriptが書きたい!」で Jupyter Notebook 上で Vim script を実行されておられたのに感銘し、僕もやってみたくなりました。nohararc さんの実装はセルを実行する毎に Vim を起動しているので処理を継続出来ませんでしたので、python 側で Vim を常駐させておき、入力ファイルを監視させつつ出力ファイルを python 側から読み取る実装にしてみました。

Jupyter Notebook で Vim script

Jupyter Notebook で XOR を学習させた際の記録を以下に置いてあります。

https://github.com/mattn/vim-brain/blob/master/machine-learning-with-vim-brain.ipynb

Markdown で説明を書きつつ手順を残せるのでとても人に伝えやすくなりました。今後「Vim script で機械学習やってみたいな」と思われる方が増えると嬉しいですね。

この Vim kernel をインストール出来る様に GitHub に置いておきました。

https://github.com/mattn/vim_kernel

詳しくは README.md を参照下さい。

ちょっと難しい学習

さて話を戻し今度は少し難しい学習をさせてみます。FizzBuzz を食わせて学習し、どう動くかを確かめます。まずは学習に必要な実装を作ります。

let s:bits = has('num64') ? 64 : 32
let s:mask = s:bits - 1
let s:mask32 = 32 - 1

let s:pow2 = [1]
for s:_ in range(s:mask)
  call add(s:pow2, s:pow2[-1] * 2)
endfor
unlet s:_

function! s:lshift(a, n) abort
  return  a:a * s:pow2[and(a:n, s:mask)]
endfunction

function! s:rshift(a, n) abort
  let n = and(a:n, s:mask)
  return n == 0 ? a:a :
  \  a:a < 0 ? (a:a - s:min) / s:pow2[n] + s:pow2[-2] / s:pow2[n - 1]
  \          : a:a / s:pow2[n]
endfunction

function! s:bin(n) abort
  let l:f = repeat([0.0], 8)
  for l:i in range(8)
    let l:f[i] = 0.0 + and(s:rshift(a:n, l:i), 1)
  endfor
  return l:f
endfunction

function! s:dec(v) abort
  let [l:maxi, l:maxv] = [0, 0.0]
  for l:i in range(len(a:v))
    if a:v[l:i] > l:maxv
      let l:maxv = a:v[l:i]
      let l:maxi = l:i
    endif
  endfor
  return l:maxi
endfunction

function! s:teacher(n) abort
  if a:n%15 == 0
    return [1, 0, 0, 0]
  elseif a:n%3 == 0
    return [0, 1, 0, 0]
  elseif a:n%5 == 0
    return [0, 0, 1, 0]
  else
    return [0, 0, 0, 1]
  endif
endfunction

function! s:test() abort
  call brain#srand(0)
  let l:patterns = []
  for l:i in range(1, 100)
    call add(l:patterns, [s:bin(i), s:teacher(i)])
  endfor
  let l:ff = brain#new_feed()
  let l:ff.Init(8, 100, 4)
  call l:ff.Train(l:patterns, 1000, 0.6, 0.4, v:true)
  for l:i in range(1,100)
    let l:r = s:dec(l:ff.Update(s:bin(l:i)))
    if l:r == 0
      echo "FizzBuzz"
    elseif l:r == 1
      echo "Fizz"
    elseif l:r == 2
      echo "Buzz"
    else
      echo l:i
    endif
  endfor
endfunction

call s:test()

仕組みは XOR とほぼ同じですが、FizzBuzz の場合は以下の4つを期待値とします。

  • Fizz
  • Buzz
  • FizzBuzz
  • 数値

検証時にはその値を元に分岐を行います。

image.png

結果は問題ありませんでした。ただ学習に時間が結構掛かります。Intel Core i5、メモリ16GB (ノートPC)の Windows で約1分掛かります。同じコードを golang で書いて実行すると3~4秒なので、単純に Vim script は golang の20倍くらい遅い事になります。2

※もちろん皆さんご存じの通り、学習した範囲ではおおよそ動きますが、そうでないならば15で割り切れるけれど学習した事のない 450 等を食わせても FizzBuzz になる訳ではないのです。学習のさせ方次第ではあります。

モデルを保存できる

FizzBuzz の学習に1分掛かったとしても、学習結果が保存できるなら実用では推論だけで勝負出来ます。golang で同じコードを書いて保存した FizzBuzz 用のモデルファイル(JSON)を用意しました。大きすぎるので以下の Gist に貼り付けてあります。

https://gist.github.com/mattn/e4d8a2009627bda289928e8f370b33f2

※分かりやすい様にフォーマットしました。
※3000行デカすぎる?聞こえませんね

モデルファイルの読み込みには brain#load_model という関数を用意してあります。これにファイル名を指定して JSON ファイルを読み込みます。コードも学習の必要が無くなるのでこんなにスッキリしました。

function! s:test() abort
  let l:ff = brain#load_model('fizzbuzz.json')
  for l:i in range(1,100)
    let l:r = s:dec(l:ff.Update(s:bin(l:i)))
    if l:r == 0
      echo "FizzBuzz"
    elseif l:r == 1
      echo "Fizz"
    elseif l:r == 2
      echo "Buzz"
    else
      echo l:i
    endif
  endfor
endfunction

実行結果も一瞬で表示されます。Vim script サイコー!そう思いませんか?

Happy

まぁまぁ難しい学習

こうなってくると Vim から実用したくなります。例えば以下のソースコードを見て下さい。

require 'open-uri'
open(url) do |file|
  puts file.read
end

プログラマの皆さんならば、これが Ruby のコードだと分かるはずですが Vim は分かりません。ある程度 Vim のファイルタイプ判別もやってくれるのですが、もしファイル名が特徴的でなかったりするとシンタックスハイライトされないのです。一大事ですね。

そこでこの vim-brain を使ってプログラミング言語を判別し、&filetype オプションに設定すべき言語名を推論する仕組みを考えてみました。

プログラミング言語の判別

プログラミング言語の判別には何種類か方法があるのですが、ソースコードからキーワードを抜き出し、キーワードの入力および言語名の期待値から作られるパターンを作り学習させるのが一番簡単な方法と思います。この仕組みは guesslang という Python モジュールでも使用されている実績のある方法です。

※guesslang では TensorFlow を使っているので高速に処理されます。

モデルファイルの生成

モデルは、著名な OSS のソースコードを読みこんでキーワードに分割、全体母数を得た後で再度解析して入力と期待値を構成します。解析は以下の OSS を利用させて頂きました。

言語 解析に使用したOSS
C h2o
C++ OpenCV
Ruby Sinatra
Perl Plack
PHP Laravel
Go Go

ディレクトリを探索し、Ruby, PHP, Perl, C, C++, Go のソースを解析します。学習に与えるパターンは固定個で無ければなりませんので、一旦全てのキーワードを抜き出して母数(全キーワード数、全言語数)を得ます。続けて再度キーワードを含む入力と言語インデックスをエンコードした値を期待値としたパターンを作ります。言語インデックスとは languages.json に含まれる配列の添え字に当たります。

実はこのディレクトリ探索やキーワード抽出と言った前準備から全て Vim script でやろうと試みたのですが、いかんせん膨大なデータを処理する必要があり、なおかつ学習に数日かかる(4時間まで我慢しましたが20イテレーションすら到達できませんでした)見込みである事が分かっています。そこで泣く泣く golang の力を借りました。

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "io/ioutil"
    "log"
    "math/rand"
    "os"
    "path/filepath"
    "regexp"
    "sort"
    "strings"

    "github.com/goml/gobrain"
)

var extMap = map[string]string{
    ".rb":  "ruby",
    ".php": "php",
    ".pm":  "perl",
    ".pl":  "perl",
    ".c":   "c",
    ".cc":  "cpp",
    ".cxx": "cpp",
    ".go":  "go",
}

func removeComment(lang, code string) string {
    if lang == "c" || lang == "cpp" || lang == "go" {
        re := regexp.MustCompile("(?s)//.*?\n|/\\*.*?\\*/")
        code = re.ReplaceAllString(code, "")
    }
    if lang == "perl" || lang == "ruby" {
        lines := strings.Split(code, "\n")
        for i := 0; i < len(lines); i++ {
            if strings.HasPrefix(strings.TrimSpace(lines[i]), "#") {
                lines[i] = ""
            }
        }
        code = strings.Join(lines, "\n")
    }
    return code
}

var allKws = map[string]struct{}{}

var pat = regexp.MustCompile(`\b\w+`)

func keywords(lang, code string, kws []string) []float64 {
    kwf := make([]float64, len(kws))

    words := pat.FindAllString(removeComment(lang, code), -1)
    kc := 0
    for _, v := range words {
        n := find(kws, v)
        if n != -1 {
            kwf[n]++
            kc++
        }
    }
    fmt.Println(words)

    for i := 0; i < len(kwf); i++ {
        if kwf[i] > 0 {
            kwf[i] /= float64(kc)
        }
    }
    return kwf
}

func analyze(name string) bool {
    if strings.Contains(name, "/.") {
        return false
    }
    lang, ok := extMap[strings.ToLower(filepath.Ext(name))]
    if !ok {
        return false
    }

    b, err := ioutil.ReadFile(name)
    if err != nil {
        return false
    }
    for _, v := range pat.FindAllString(removeComment(lang, string(b)), -1) {
        allKws[v] = struct{}{}
    }
    return true
}

func allLanguages() []string {
    l := []string{}
    langs := map[string]struct{}{}
    for _, v := range extMap {
        langs[v] = struct{}{}
    }
    for k := range langs {
        l = append(l, k)
    }
    sort.Strings(l)
    return l
}

func allKeywords() []string {
    ks := []string{}
    for k := range allKws {
        ks = append(ks, k)
    }
    sort.Strings(ks)
    return ks
}

type BasicEncoderDecoder struct {
    labels []string
}

func (c BasicEncoderDecoder) EncodeLabel(label string) []float64 {
    hasil := []float64{}
    for _, l := range c.labels {
        if l == label {
            hasil = append(hasil, 1.0)
        } else {
            hasil = append(hasil, 0.0)
        }
    }
    return hasil
}

func (c BasicEncoderDecoder) DecodeLabel(label []float64) string {
    maxIndex := 0
    maxValue := 0.0
    for i, l := range label {
        if l > maxValue {
            maxIndex = i
            maxValue = l
        }
    }
    return c.labels[maxIndex]
}

func find(a []string, x string) int {
    for i, n := range a {
        if x == n {
            return i
        }
    }
    return -1
}

func saveModel(ff *gobrain.FeedForward) error {
    f, err := os.Create("guesslang.json")
    if err != nil {
        return err
    }
    defer f.Close()
    return json.NewEncoder(f).Encode(ff)
}

func loadModel() (*gobrain.FeedForward, error) {
    f, err := os.Open("guesslang.json")
    if err != nil {
        return nil, err
    }
    defer f.Close()

    ff := &gobrain.FeedForward{}
    err = json.NewDecoder(f).Decode(ff)
    if err != nil {
        return nil, err
    }
    return ff, nil
}

func saveKeywords() error {
    f, err := os.Create("keywords.json")
    if err != nil {
        return err
    }
    defer f.Close()
    return json.NewEncoder(f).Encode(allKeywords())
}

func saveLanguages() error {
    f, err := os.Create("languages.json")
    if err != nil {
        return err
    }
    defer f.Close()
    return json.NewEncoder(f).Encode(allLanguages())
}

func loadKeywords() ([]string, error) {
    f, err := os.Open("keywords.json")
    if err != nil {
        return nil, err
    }
    var keywords []string
    err = json.NewDecoder(f).Decode(&keywords)
    if err != nil {
        return nil, err
    }
    return keywords, nil
}

func loadLanguages() ([]string, error) {
    f, err := os.Open("languages.json")
    if err != nil {
        return nil, err
    }
    var languages []string
    err = json.NewDecoder(f).Decode(&languages)
    if err != nil {
        return nil, err
    }
    return languages, nil
}

func main() {
    flag.Parse()

    kws, _ := loadKeywords()
    langs, _ := loadLanguages()
    ff, _ := loadModel()
    enc := &BasicEncoderDecoder{langs}

    fmt.Println(len(kws), len(langs))
    if len(kws) == 0 || len(langs) == 0 || ff == nil {
        base, err := filepath.Abs(flag.Arg(0))
        if err != nil {
            log.Fatal(err)
        }
        names := []string{}
        err = filepath.Walk(base, func(path string, info os.FileInfo, err error) error {
            if err != nil {
                return err
            }
            if !info.IsDir() && info.Name() != ".git" {
                path = filepath.ToSlash(path)
                if analyze(path) {
                    names = append(names, path)
                }
            }
            return nil
        })
        kws = allKeywords()
        langs = allLanguages()
        enc = &BasicEncoderDecoder{langs}

        visit := map[string]int{}
        for _, l := range langs {
            visit[l] = 0
        }
        rand.Seed(0)
        patterns := [][][]float64{}

        for _, name := range names {
            if strings.Contains(name, "/.") {
                continue
            }
            lang, ok := extMap[strings.ToLower(filepath.Ext(name))]
            if !ok {
                continue
            }
            if visit[lang] > 20 {
                continue
            }
            log.Println(lang, name)
            b, err := ioutil.ReadFile(name)
            if err != nil {
                continue
            }
            kf := enc.EncodeLabel(lang)
            kw := keywords(lang, string(b), kws)
            patterns = append(patterns, [][]float64{
                kw, kf,
            })
            visit[lang]++
        }

        println(len(kws), len(langs), len(patterns))
        ff = &gobrain.FeedForward{}
        ff.Init(len(kws), len(langs), len(langs))
        ff.Train(patterns, 100, 0.6, 0.4, true)
        saveModel(ff)
        saveKeywords()
        saveLanguages()
    }

    input := keywords("", `
require 'sinatra'

get '/' do
  'Hello world!'
end
    `, kws)
    vv := ff.Update(input)
    fmt.Println(enc.DecodeLabel(vv))
}

※golang を使ってもモデルファイルを生成するのに2時間掛かります。

コードの最後で Ruby のコードを判別していますが、問題なく ruby と表示されます。

Vim script でプログラミング言語を判別

生成した JSON ファイルは 38MB もありますが Vim script の JSON の読み込みはC言語の実装です。それほど遅くならない事を期待しながら、別途用意したファイルを読み込み推論してみましょう。

let s:base = fnamemodify(expand('<sfile>') . '/../data', ':p')

function! s:enc(l, n) abort
  let l:f = repeat([0.0], len(a:l))
  for l:i in range(len(a:l))
    if a:l[l:i] == a:n
      let l:f[l:i] = 1.0
    endif
  endfor
  return l:f
endfunction

function! s:dec(v) abort
  let [l:maxi, l:maxv] = [0, 0.0]
  for l:i in range(len(a:v))
    if a:v[l:i] > l:maxv
      let l:maxv = a:v[l:i]
      let l:maxi = l:i
    endif
  endfor
  return l:maxi
endfunction

if !exists('s:kwd')
  let s:kws = json_decode(join(readfile(s:base . '/keywords.json'), "\n"))
endif
if !exists('s:lng')
  let s:lng = json_decode(join(readfile(s:base . '/languages.json'), "\n"))
endif
if !exists('s:ff')
  let s:ff = brain#load_model(s:base . '/guesslang.json')
endif

function! s:keywords(code) abort
  let l:kwf = repeat([0.0], len(s:kws))

  let l:words = []
  call substitute(a:code, '\<\w\+', '\=add(l:words, submatch(0)) == [] ? "" : ""', 'g')
  let l:kc = 0.0

  for l:v in l:words
    let l:n = index(s:kws, l:v)
    if l:n != -1
      let l:kwf[l:n] += 1.0
      let l:kc += 1.0
    endif
  endfor

  for l:i in range(len(l:kwf))
    if l:kwf[l:i] > 0.0
      let l:kwf[l:i] = l:kwf[l:i] / kc
    endif
  endfor
  return l:kwf
endfunction

function! s:test() abort
  let l:input = s:keywords(join(readfile('test.cc'), "\n"))
  let l:r = s:dec(s:ff.Update(l:input))
  echo s:lng[l:r]
endfunction

call s:test()

s:enc:dec はラベル名(プログラミング言語名)をインデックス値としてエンコード/デコードする為の実装、s:keywords は golang で実装した方法と同じ方式でソースコードからキーワードを抜き出す為の実装です。

推論に使用したソースファイルは以下の簡単な C++ のソースファイルです。

#include <iostream>
#include <string>
#include <algorithm>

int
main(int argc, char* argv[]) {
  std::vector<std::string> v;
  return 0;
}

実行すると10秒後に以下の様に表示されます。

cpp

うまく動きました。その他、上記の golang のソース自身や、そのコードの一番下にある ruby のコードも正しく判別できています。やったぜ!ただC言語と PHP を混同する事が結構多いのですが、これはワード単位のランク付けになっているので、今回見付かった PHP のソースコードにC言語のキーワードと似た物が多く含まれていた結果だと思います。これについては今後、定量的な評価の上で調整し、もう少し精度の高いニューラルネットを作ろうと思います。

なお推論に10秒掛かる点に関して「遅すぎて実用に値しない」と思われるかもしれませんが、これを解決する簡単な方法を Vim の作者 Bram Moolennaar 氏が VimConf 2018 で言及されています。

Vim script が遅いと感じたら、速い PC を買って下さい。

尚、この記事を執筆してから気付いたのですが、Vim の記事なのに Vim script を解析に含める事を忘れていました。

まとめ

Vim script からニューラルネットワークを扱う為の仕組みを実装し、XOR と FizzBuzz の学習と推論を、また保存されたモデルファイルを使ってプログラミング言語の判別をやってみました。さらに皆さんでも汎用的に使って頂ける様に vim-brain というプラグインにしました。学習には golang の実装である goml/gobrain を使って頂き、生成したモデルファイル(JSON)を Vim で使って推論する事で、そこそこ実用的な結果1が得られる事が分かりました。今後は vim-brain の高速化、または Vim 本体の高速化に取り組み、いずれは誰でも簡単に Vim script による機械学習を試せる様に精進して参りたいと思います。

尚、前述の blob 型 が Vim 本体に導入された暁には、Vim script のみで画像の特徴抽出や判別も試してみたいと思います。


  1. ほんまか? 

  2. まぁでも誤差ですよね。誤差。(気にしない) 

Browsing Latest Articles All 25 Live