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

Vimでとのマッピングを使い分けられるようになるまで(Windows)

$
0
0

Vim使いの誰かの参考になればと、初めて筆(キーボード)を執りました。

直面した問題

私はvimで以下のマッピングを定義して、しばらくの間は特に不自由なく使っていました。

inoremap <C-i><Esc>

しかしある時、インサートモードでTabキーを押してもTabが入らないことに気が付きました。

不思議に思い調べてみると、どうやら

  • <Tab>と<C-i>のキーコードは同一である
  • よって<C-i>のマッピングを定義すると、<Tab>キーを押した時にもその機能が呼び出される
  • 結果的に元々の<Tab>の機能を上書きしてしまう

ということらしいのです。

私が調べた所では、LinuxとMacOSでは既にこの問題の解決方法が編み出されていました。

しかし、私の使用環境はWindows。自力で探すしかありません。

解決方法

結論から言えば、解決方法は簡単でした。

当然ですが、<Tab>と<C-i>では、押すキーが異なります。

であれば、<Tab>や<C-i>が押された時、そのキーコードではなく、キーボードのどのキーが押されているかで判別すれば良かったのです。

私が目をつけたのは、Linux版の先駆者様が使っていた、Pythonのctypesというライブラリでした。

ctypes --- Pythonのための外部関数ライブラリ

簡単に言えば、PythonからCのライブラリを操作できるライブラリです。

これを使って呼び出せる関数の中に、GetKeyboardState()という、現在のキーボードの状態を配列に格納してくれる関数がありました。

それを使って書いたコードがこちらです。

tab_ctrli.py
# coding: UTF-8importctypesimportvimuser32=ctypes.WinDLL('user32')# Cのbyte型配列を作るkey_tbl=(ctypes.c_byte*256)()### 戻り値・引数の型を指定するuser32.GetKeyboardState.restype=None# ※最後のカンマは必須user32.GetKeyboardState.argtypes=(ctypes.POINTER(ctypes.c_byte),)# キーボードの状態を配列に格納するuser32.GetKeyboardState(key_tbl)# <Tab>が押されていた場合ifkey_tbl[0x09]&0x80:vim.command(':let g:tabctrli_tab_pushed = v:true')# <C-i>が押されていた場合elifkey_tbl[0x11]&0x80andkey_tbl[0x49]&0x80:vim.command(':let g:tabctrli_tab_pushed = v:false')

※以下のサイトを参考にさせていただきました

実行結果をvimのグローバル変数に入れて返しています。
これはvimとPythonの処理の記述をできるだけ切り離したかったからです。

後はこのスクリプトをVimScriptから実行させて、変数の値で分岐処理をすれば解決…………のはずでした。

新たなる問題

以下のコードを御覧下さい。これが、私が最初に書いたVimScriptです。

letg:tabctrli_tab_pushed=v:null
fu!s:TabctrliMain(mode)"system()などで実行すると、vimモジュールが認識されないpyfile C:\gvim\vim80-kaoriya-win64\cmd\tab_ctrli.pyifg:tabctrli_tab_pushedifa:mode=="i"return"<Tab>"elseifa:mode=="n"return"gt"endifelseifa:mode=="i"return"<Esc>"elseifa:mode=="n"return"gT"endifendifendf

inoremap <expr><C-i><SID>TabctrliMain("i")
inoremap <expr><Tab><SID>TabctrliMain("i")
nnoremap <expr><C-i><SID>TabctrliMain("n")
nnoremap <expr><Tab><SID>TabctrliMain("n")

既にお気づきの方もいらっしゃるかもしれませんが、下部で定義されているインサートモードのマッピングは、どちらも予期した動作をしません(ノーマルモードのマッピングは動きます)。

例えば<C-i>を押した時、
inoremap <expr> <C-i> <SID>TabctrliMain("i")
というマッピングは関数の実行後、
inoremap <C-i> "<Esc>"
と解釈され、実行されます。
そしてその結果、カーソル位置に"<Esc>"という文字列が挿入されます

これに関してはこちらの(map - Vim日本語ドキュメント)1.2項が詳しいのですが、要するにキーではなく、ただの文字列だと扱われてしまうのです。

新たなる問題の解決方法

この問題の解決方法は、キーをUnicodeで指定することでした(こちらに関しても上に載せたリンクに記載されています)。

それを反映させたのが、以下のコードです。

letg:tabctrli_tab_pushed=v:null
fu!s:TabctrliMain(mode)"system()などで実行すると、vimモジュールが認識されないpyfile C:\gvim\vim80-kaoriya-win64\cmd\tab_ctrli.pyifg:tabctrli_tab_pushedifa:mode=="i"" <Tab>のUnicodereturn"\u0009"elseifa:mode=="n"return"gt"endifelseifa:mode=="i"" <ESC>のUnicodereturn"\u001B"elseifa:mode=="n"return"gT"endifendifendf

inoremap <expr><C-i><SID>TabctrliMain("i")
inoremap <expr><Tab><SID>TabctrliMain("i")
nnoremap <expr><C-i><SID>TabctrliMain("n")
nnoremap <expr><Tab><SID>TabctrliMain("n")

これで本当の解決と相成りました。
インサートモードで<C-i>を押すと脱出でき、<Tab>を押すとちゃんとTabが入るようになりました。
動作の遅延もほとんどありません。

動作環境

  • Windows10
  • kaoriya版GVim(ver. 8.0.596)
  • Python3.7.0

Viewing all articles
Browse latest Browse all 5608

Trending Articles



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