はじめに
unite.vim に替わる新しいプラグインとして,denite.nvimが開発されている. まだまだ開発途上ではあるものの,じわじわとunite.vimから移行する人が見受けられる. denite.nvimはneovimだけでなく,Vim 8.0以降であれば利用可能であるのも魅力の1つであるだろう.
そこで,(ゴミ)プラグイン作者の一人として,denite-sourceの作成方法について記したいと思う.
source / kind作成
サンプルとして作るもの
何よりもまず,実例を示すのが早いだろう. サンプルとして,以下のようなsourceを作成してみることにしよう.
- sourceの引数で指定されたディレクトリ(複数指定可)のファイル一覧を表示
- sourceの引数が指定されなかった場合はホームディレクトリのファイル一覧を候補として表示
- 選択したファイルの行数を
print()
する
ファイル構成
以下の構成でプラグインのディレクトリを作成する.
今回は denite-source のみのプラグインであるため, plugin/
も autoload/
も存在しない.
同じものは GitHub: koturn/vim-denite-sampleにも置いてある.
vim-denite-sample/ | +-.gitignore | +-README.md | +-LICENSE | +-rplugin/ | +-python3/ | +-denite/ | +-kind/ | | | +-sample.py | +-source/ | +-sample.py
コード
vim-denite-sample/rplugin/pyhton3/denite/source/sample.py
本体同梱のsourceを真似て,sourceを作成する.
まず, from .base import Base
のようにBaseクラスをインポートし,それを継承して, Source
クラスを作成する.
Source
クラスには必要なメソッドを実装するとよい.
特に, gather_candidates
メソッドは候補取得のためのメソッドなので,必ず実装することになる.
# -*- coding: utf-8 -*- # FILE: sample.py # AUTHOR: koturn <jeak.koutan.apple@gmail.com> # License: MIT License from .base import Base import glob import itertools import os class Source(Base): def __init__(self, vim): # このvimという引数はVimとPythonで相互にやりとりするためのインタフェース # :help pyth を参照すれば,おおよそのことが記述してある super().__init__(vim) # Denite xxx の xxx に相当する部分 self.name = 'sample' # kind名を指定 self.kind = 'sample' def on_init(self, context): ''' このsourceが指定されて起動されたときに呼び出される. 元のバッファのファイルタイプの取得などに利用する. 今回は必要ないので,とりあえずechomsgしておく ''' # print() は :echomsg と同等 print('on_init') # filetypeなどの値は,以下のように context のキーとして生やす # selfのメンバにするのはよくない # context['__filetype'] = self.vim.eval('&filetype') def on_close(self, context): ''' Denite終了時に呼び出される. ''' # コンストラクタ以外では,self.vim を参照して,Vimインタフェースを利用する print('on_close') def gather_candidates(self, context): ''' 候補の取得を行う関数 ''' # sourceの引数context['args']はユーザがDenite実行時に以下のように : 区切りで指定する # Denite sample:arg1:arg2:arg3 dirpaths = ['~'] if len(context['args']) == 0 else context['args'] print(dirpaths) candidates = filter(lambda path: os.path.isfile(path), itertools.chain.from_iterable( map(lambda dirpath: glob.glob(os.path.expanduser(dirpath) + '/*'), dirpaths))) # 辞書を返す # word: 候補欄に表示される文字列 # アクション側で利用するための情報を増やしたい場合は 'action__xxx' # という名前のキーを利用すること return list(map( lambda candidate: { 'word': candidate}, candidates))
vim-denite-sample/rplugin/pyhton3/denite/kind/sample.py
denite-kindはアクション部分を記述するものである.
denite-kindに関しても,sourceと同様に from .base import Base
として,Base
クラスをインポートする.
そして,それを継承した Kind
クラスを作成し,必要なメンバ,メソッドを実装する.
メンバの中で特に重要なのが default_action
であり,これは <CR>
を押下したときのアクションに相当する.
# -*- coding: utf-8 -*- # FILE: sample.py # AUTHOR: koturn <jeak.koutan.apple@gmail.com> # License: MIT License from .base import Base class Kind(Base): def __init__(self, vim): super().__init__(vim) # kindの名前.この名前をsourceで指定することにより,関連付けられる self.name = 'sample' # デフォルトのアクション(候補上で<CR>を押下したときに実行するアクション)を指定する. # self.default_action = 'xxx' と指定すると,このクラスのメソッド: # action_xxx() が呼び出される self.default_action = 'sample' def action_sample(self, context): ''' コンストラクタで指定したように,デフォルトのアクションを記述する関数 ''' for target in context['targets']: filepath = target['word'] print(filepath + ' has ' + str(sum(1 for line in open(filepath, encoding='utf-8'))) + ' lines') def action_preview(self, context): ''' 候補上にカーソルが移動する度に呼び出される関数 :Denite -auto-preview ... のように -auto-preview オプションを付加すると,previewモードになる 付加しない場合でも,以下のキーを押下することでpreview動作を行うことは可能 insertモード: <C-v> normalモード: p return Trueとしないと,deniteが終了する ''' # context['targets'] に source側のgether_candidatesの返り値が格納されている filepath = context['targets'][0]['word'] print(filepath + ' has ' + str(sum(1 for line in open(filepath, encoding='utf-8'))) + ' lines') return True
サンプルの使い方
以下のように起動すると,ホームディレクトリのファイル一覧が表示される.
選択したファイルの行数が print()
に渡されるので, :message
等で確認するとよい.
:Denite sample
次のように,sourceの引数とディレクトリを渡すと,ホームディレクトリではなく,そのディレクトリのファイル一覧が表示される.
:Denite sample:~/Desktop
引数を複数指定することも可能だ.
:Denite sample:~/Desktop:~/Documents/
テンプレート
最低限のdenite-sourceを実装するのであれば,以下のようなテンプレートを用意しておくと楽になるだろう.
on_init
や on_close
については,必ずしも利用するわけではないので,コメントアウトしてある.
denite-source のテンプレート
# FILE: xxx.py # AUTHOR: xxx <xxx.yyy.zzz@gmail.com> # License: MIT license from .base import Base class Source(Base): def __init__(self, vim): super().__init__(vim) self.name = 'xxx' self.kind = 'xxx' # def on_init(self, context): # TODO # def on_close(self, context): # TODO def gather_candidates(self, context): # TODO: Following code is a sample candidates = ['apple', 'banana', 'cake'] return list(map( lambda candidate: { 'word': candidate}, candidates))
denite-kind のテンプレート
# FILE: sample.py # AUTHOR: xxx <xxx.yyy.zzz@gmail.com> # License: MIT license from .base import Base class Kind(Base): def __init__(self, vim): super().__init__(vim) self.name = 'xxx' self.default_action = 'xxx' def action_sample(self, context): # TODO # def action_preview(self, context): # TODO
余談
実は,unite.vimにもdeite-sourceが実装され,unite-sourceをdeniteから利用することが可能になった. 以下のようにして,deniteからuniteを利用する.
" xxx がunite-source名
:Denite unite:xxx
このため,既にプラグインにunite-sourceを実装している場合は,改めて同等のdenite-sourceを実装し直す必要はないということだ. しかし,ユーザとしてはdenite-sourceも提供されていた方が気持ちが良いと思われる.
ちなみに,denite-sourceはPythonであるため,当然バイトコードが生成される.
そのため, .gitignore
に,
*.py[cod]
と追記しておくのがよいだろう.
おまけ
実はちょっとした自作プラグインに取り込んである.
:Denite mplayer
とすることで,デフォルトでは ~/
以下のファイルを再帰的に表示し,mplayer
を用いて,選択したファイルを再生するようになっている.
デフォルトディレクトリは g:mplayer#default_dir
を設定することで変更可能であるし,
:Denite mplayer:~/Music/
のように指定してもよい.
また,プレビュー機能にも対応しているため,
:Denite mplayer:~/Music/ -auto-preview
とすることで,Deniteバッファ上で,カーソル移動の度にカーソル下のファイルを再生できる.
このように,ちょっとした自作プラグインに導入することで,格段に便利になる.
まとめ
この記事では,最も単純なdenite-sourceの作成方法を示した. プラグイン作成初心者のdenite-source作成の足掛かりとなれば幸いである.
ただし,あくまで,最も初歩的な作成方法について述べただけであり,触れていない項目の一例として matcherやsorter,converterといった話もある. それらの機能についてはdocやdenite.nvim自体のソースコードを参照して欲しい.
なお,deniteは開発が活発なプラグインであるため,数ヶ月後にはこの記事の内容が古いものとなっている可能性は十分にある.