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

popup windowsで簡易ファイラーを作る

$
0
0

ども、ゴリラです。
今年もやってまいりましたVimアドベントカレンダーです。

今日はpopup windowを使ってファイル一覧をpopup windowで選択して開くプラグインを作ってみようと思います。
プラグイン作ってみたい方は、ぜひ読んでみてください。意外とプラグインは簡単に作れます。

はじめに

まずどんな機能を作るかを整理します。本記事は次の機能を実装することをゴールにします。

  1. 指定パスのファイル一覧をpopup windowに表示
  2. 選択したファイルを開く(分割、タブ、カレントバッファ)

ディレクトリ構成

プラグインのディレクトリ構成は以下にします。一般的にautoload配下にメインの処理関数を定義して、
pluginでメイン処理の関数を呼び出すコマンドを定義します。

autoloadに定義した関数は、コマンドで呼び出されるまでロードされないようになっています。
これは、Vimの起動時間に影響を与えないようにするためです。

popupfiles.vim/
├── autoload
│   └── popupfiles.vim
└── plugin
    └── popupfiles.vim

ディレクトリの置き場

プラグインマネージャーを使う方が多くいると思います、プラグイン開発を行うとき筆者はよくpackages機能を使用します。
packpath配下にstartディレクトリを作成してそこにディレクトリを置くことで、Vim起動時にプラグインを読み込んでくれます。

ちなみに、筆者の場合は次のようになっています。

/Users/skanehira/.vim/pack/plugins/start/popupfiles.vim

詳しく知りたい方は:h packagesでヘルプを引いてみてください。

ファイル一覧をpopup windowに表示

ファイル一覧を取得

まずはカレントディレクトリ配下のファイルを一覧の配列を返す関数を用意します。
コードはautoload/popupfiles.vimに記述します。

" パスのセパレーターを取得lets:sep= fnamemodify('.',':p')[-1:]function!s:get_files(path) abort
    " `expand()`はパスをフルパスに展開" 例えば`~/`は`/home/gorilla`に展開letp= expand(a:path)let entries =[]" `readdir`は指定したディレクトリ内の要素を配列で返すforfin readdir(p)" `.`から始まるディレクトリとファイルをスキップiff[0]is# '.'
            continue
        endiflet file_path =p.s:sep.fif isdirectory(file_path)let entries +=s:get_files(file_path)elseif file_path[0]is# '.'let file_path = file_path[2:]endifcall add(entries, file_path)endifendforreturn entries
endfunction

popup windowを作る

次にpopup windowを作る関数を定義します。popup windowを作る関数はいくつかありますが、今回はpopup_menuを使用します。popup_menuを使用することで要素の選択、操作を簡単に実装できます。

こちらも、コードはautoload/popupfiles.vimに記述します。

function! popupfiles#files() abort
    call popup_menu(s:get_files('.'),{})endfunction

ここで1つポイントです。popupfiles#files()という関数名はpluginで呼び出すためにautoloadのルールに従って命名しています。
autoloadの命名ルールはfilename#funcname()になっています。こうすることで、どのファイルのどの関数を呼び出すかVim側が判断できるようになるからです。

popup windowを呼び出す

メインの処理ができたので、次にpluginpopupfiles#files()を呼び出すコマンドを次のように定義します。

" プラグインを再度ロードしないようにガードif exists('g:loaded_popupfiles')finishendifletg:loaded_popupfiles=1" vi互換の設定を一旦退避lets:save_cpo=&cpo
set cpo&vim" コマンドの定義
command! PopupFiles call popupfiles#files()" 互換設定を戻すlet&cpo =s:save_cpo
unlet s:save_cpo

一度Vimを再起動するか、:source plugin/popupfiles.vim:source autoload/popupfiles.vimでファイルを読み込むかしてください。

適用後:PopupFilesコマンドを実行すると次のようにpopup windowにファイル一覧が表示されるはずです。

image.png

これでpopup windowにファイル一覧を表示させることができました。
ちなみに、popup_menuではデフォルトで次のキーが利用できます。

キー説明
j次の要素
k前の要素
enter/ space要素を選択
x / esc / ctrl-cキャンセル

では、次に選択したファイルを開くキーバインドを追加していきましょう。

選択したファイルを開く

popup_menuでは第1引数に要素、第2引数にオプションを渡すことができます。
もう一度コードを見てみましょう。第2引数は{}は空のオブジェクトになっています。
このオブジェクトにオプションを定義していきます。

function! popupfiles#files(path) abort
    call popup_menu(s:get_files(a:path),{})endfunction

popup windowにはfilterというオプションがあります。
このオプションではキー入力を受け付けて何かしらの処理を行う関数を指定することができます。

このフィルター関数、デフォルトではwinidkeyの2つの引数を受け取ります。
フィルター関数の定義と呼び出しは次のようになります。

function! popupfiles#files() abort
    call popup_menu(s:get_files('.'),{                \'filter':function('s:popup_files_filter')                \})endfunctionfunction!s:popup_files_filter(winid, key) abort
    " 入力キーに応じた処理" それ以外は通常のpopup_filter_menuに渡すreturn popup_filter_menu(a:winid,a:key)endfunction

ここで1つポイントですが、function()で関数への参照を取得して、filterオプションに渡す必要があります。

また、popup_filter_menuというVimの組み込み関数がありますが、これはfilterが何も指定されないときにデフォルトで使用されるフィルター関数になっています。この関数を使用することでjkといったキーバインドの操作が出来るようになります。

しかし、このまま実装しようとすると、
フィルター関数に選択したファイルが渡ってこないため、ファイルを特定できないという問題があります。
そこで、Vim scriptのPartialという仕組みを使用して必要な情報をフィルター関数の引数に追加します。
function()の第2引数に配列を渡すことで、その中身が関数の引数として先頭に順に追加されます。

function! popupfiles#files() abort
    letfiles=s:get_files('.')let ctx ={                \'idx':0,                \'files':files,                \}call popup_menu(files,{                \'filter':function('s:popup_files_filter',[ctx])                \})endfunctionfunction!s:popup_files_filter(ctx, winid, key) abort
    " 入力キーに応じた処理" それ以外は通常のpopup_filter_menuに渡すreturn popup_filter_menu(a:winid,a:key)endfunction

idxfilesから選択したファイルを取り出すために必要な配列番地の情報です。

これで必要な情報をフィルター関数内で扱うことができるので、あとはキー入力に応じてファイルを開く処理を実装すれば良いです。

今回は、次のキーバインドを実装することにします。

キー説明
enterカレントバッファに開く
ctrl-v水平分割で開く
ctrl-x垂直分割で開く
ctrl-tタブページで開く

実装したコードが次になります。

function!s:popup_files_filter(ctx, winid, key) abort
    " 入力キーに応じた処理ifa:keyis# 'j'ifa:ctx.idx < len(a:ctx.files)-1leta:ctx.idx =a:ctx.idx +1endifelseifa:keyis# 'k'ifa:ctx.idx >0leta:ctx.idx =a:ctx.idx -1endifelseifa:keyis# "\<cr>"returns:open_file(a:winid,'e',a:ctx.files[a:ctx.idx])elseifa:keyis# "\<c-v>"returns:open_file(a:winid,'vnew',a:ctx.files[a:ctx.idx])elseifa:keyis# "\<c-x>"returns:open_file(a:winid,'new',a:ctx.files[a:ctx.idx])elseifa:keyis# "\<c-t>"returns:open_file(a:winid,'tabnew',a:ctx.files[a:ctx.idx])endif" それ以外は通常のpopup_filter_menuに渡すreturn popup_filter_menu(a:winid,a:key)endfunctionfunction!s:open_file(winid, open,file) abort
    call popup_close(a:winid)
    execute a:opena:filereturn1endfunction

これで簡易ファイラーの実装は以上になります。

まとめ

いかがだったでしょうか?合計で100行くらいで簡易ファイラーを実装できるVim script、便利ですね。
これをきっかけにプラグインを作ってみようってなってくれたら嬉しいなと思っています。

本記事のソースコードはリポジトリに置きましたので、全体像を掴めていない方は覗いてみてください。

https://github.com/skanehira/popupfiles.vim

なお、本記事はVim scriptの基本構文について触れていないので、詳しく知りたい方は:h scriptでヘルプを参照してください。


Viewing all articles
Browse latest Browse all 5608

Trending Articles



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