Deniteとは?
まずは軽くDeniteについて紹介します。
Vim8/Neovimのプラグインの一つです。
Uniteの後継プラグインです。
「ある一覧から特定のデータを検索してアクションを実行する」ことができます。
例えば標準機能である、「ファイル一覧からファイルを検索、選択して開く」です。
このファイル一覧のことをソースと言います。
Deniteはこのソースなどを自分で開発し追加することができます。
ここまではUniteもDeniteも同じですね。
UniteはVim scriptで実装されており、速度が出ないという問題がありました。また複雑化によりメンテが困難になったという話を聞いたことがあります。
そこでDeniteが生まれました。コア機能はPythonで実装されており、理論上Uniteより高速に動作するようになったようです。
またUniteは開発が終了しておりDeniteを使用することが勧められているようです。
Note: Active development on unite.vim has stopped. The only future changes will be bug fixes.
Please see Denite.nvim.
以下は有志で紹介されている外部プラグインの一覧です。
https://github.com/Shougo/unite.vim/wiki/unite-plugins
https://github.com/Shougo/denite.nvim/wiki/External-Sources
ここに記載されていないものもありますが、DeniteとUniteを比べるとDeniteは外部プラグインの開発がまだまだ進んでいません。
そこでDeniteのソースの開発方法を軽く説明しようと思います。
ソースの作り方
DeniteのソースはPython3で書きます。私はPythonについて然程詳しくありません。
なので今回はPythonについての説明などは省きます。
またDeniteのソースコード自体はあまり読んでいないので、曖昧な箇所があります。
確実でない箇所について「思います。」と表現していますので予めご了承ください。
ソースを作るにあたって
Deniteでは、
- 何かの一覧を取得する
- 検索・選択する
- アクションを実行する
のが基本でした。まずは何の一覧を取得して、どういうアクションがしたいのか決めましょう。
今回はALEの結果を一覧として使いたいとします。
ALEは非同期にlintを走らせるプラグインです。
アクションはlintの警告などがあった箇所にジャンプするにします。
:Denite ale
と実行したいのでソース名はale
とします。
調査
少し調べた結果、ALEの結果を取得するメソッドがale#engine#GetLoclist
だということが分かりました。引数はバッファ番号でした。
結果にはバッファ番号、行番号、カラム、結果のテキスト、タイプなどが入っています。
検索対象は「結果のテキスト」にしてジャンプ先は「バッファ番号、行番号、カラム」としましょう。
また、このアクションをDeniteで実現するにはfile kindが良さそうという事が分かりました。(:help denite-kind-file
)
kindについては後で簡単に説明します。
準備
:help denite-create-source
を参考に作っていきます。
さてファイル構成を見てみましょう。
.
├── rplugin/
│ └── python3/
│ └── denite/
│ └── source/
│ └── ale.py
├── LICENSE
└── README.md
LICENSEやREADMEは必要に応じて配置してください。
rplugin/python3/denite/source/*.py
に開発したいソースのファイルを配置します。ale.py
がソースを定義しているファイルです。
ソース名と同一のファイル名にすると分かりやすいでしょう。
では、コーディング。
ソースにはBaseクラスがあり、これを継承する必要があります。
https://github.com/Shougo/denite.nvim/blob/1fd1217f928721255d7c671205e35a4b9e3d6d4f/rplugin/python3/denite/source/base.py
クラス名はSource
にすると自動で読み込んでくれるのでSource
にしました。
# -*- coding: utf-8 -*-
from .base import Base
class Source(Base):
__init__
/ コンストラクタ
ソースの初期化メソッドになります。
ソースをDeniteに登録する時に呼ばれると思っています。
def __init__(self, vim):
super().__init__(vim)
self.name = 'ale'
self.kind = 'file'
親のコンストラクタを呼ぶ必要があります。(デフォルト値の設定がされます)
またself
に設定して効果があるものに、name
(必須), kind
, syntax_name
, max_candidates
などがあります。
詳しくは:help denite-source-attribute-__init__
の辺りを参照してください。
name
はソース名です。ale
を設定しました。
これで:Denite ale
と呼び出す事ができます。
kind
にはkind名かKindクラスを指定します。kindを指定するとそのkindが持つactionを実行したりできます。デフォルトはbase
だったと思います。
kindもソースと同じ様に独自実装することができますが、今回は紹介しません。
詳しく知りたければ:help denite-create-kind
を参照してください。
今回はALEの結果の一覧からファイルを開いて警告なりが出ている箇所にジャンプしたいので、file
を設定しました。file kind
のデフォルトのアクションはopenです。
ファイルを開くという事になりますが、行やカラムを指定することで開いた後にジャンプすることも可能です。
on_init
ソースの初期化時に実行されます。Deniteコマンドが実行されたときに呼ばれると思います。
似たものにon_close
があります。
def on_init(self, context):
context['__bufnr'] = str(self.vim.call('bufnr', '%'))
contextはコンテキスト情報をディクショナリで保持しています。
主な情報として、input
, args
, candidates
, event
などがあります。
詳細は:help denite-notation-{context}
を参照してください。
コマンド実行時のバッファ番号を取得したかったので、contextに保持させています。
尚、元々使われているkeyと被らないように__
をprefixとして付けるようにしています。
gather_candidates / _convert
gather_candidates
は候補一覧を返すメソッドです。
候補一覧はリストで、リストの中身(候補情報)はディクショナリです。
ALEの情報を取得し、候補一覧に変換(_convert
)し、それを返しています。
def gather_candidates(self, context):
loclist = self.vim.call('ale#engine#GetLoclist', context['__bufnr'])
return [self._convert(loc) for loc in loclist]
def _convert(self, info):
abbr = '[%d:%d] %s' % (info['lnum'], info['col'], info['text'])
return {
'word': info['text'],
'abbr': abbr,
'action__path': self.vim.call('bufname', info['bufnr']),
'action__line': info['lnum'],
'action__col': info['col']
}
候補情報にはword
(必須), abbr
が設定できます。
word
は表示・検索対象です。abbr
を設定するとこちらを優先して表示します。
ただしabbr
は検索対象にはなりません。
また、候補情報にはアクションに必要な情報を付加する事ができます。(action__xxx
)
詳しくは:help denite-source-attribute-gather_candidates
や:help denite-candidate-attributes
を参照してください。
調査済みのale#engine#GetLoclist
をvim.call
で呼ぶ事で結果を取得できます。
このまま返してもDeniteがうまく処理できないので、_convert
で候補情報に変換します。
最初はtextだけあればいいかと思っていたんですが、行・カラムの番号があると分かりやすいのでフォーマットしてabbrに設定しました。
ところでkindはfileでしたね。
:help denite-kind-file
に書いてありますが、action__path
が必須で、action__line
やaction__col
を指定することで、その箇所にジャンプすることができます。
ALEの結果情報から設定しています。
成果物
できあがったものはこちらです。
# -*- coding: utf-8 -*-
from .base import Base
class Source(Base):
def __init__(self, vim):
super().__init__(vim)
self.name = 'ale'
self.kind = 'file'
def on_init(self, context):
context['__bufnr'] = str(self.vim.call('bufnr', '%'))
def gather_candidates(self, context):
loclist = self.vim.call('ale#engine#GetLoclist', context['__bufnr'])
return [self._convert(loc) for loc in loclist]
def _convert(self, info):
abbr = '[%d:%d] %s' % (info['lnum'], info['col'], info['text'])
return {
'word': info['text'],
'abbr': abbr,
'action__path': self.vim.call('bufname', info['bufnr']),
'action__line': info['lnum'],
'action__col': info['col']
}
比較的簡単にできましたね。
という訳で、皆さんも何かDeniteのソースを作ってみましょう!
基本構成はUniteのソースと変わらないので、Uniteのソースを移植してみてもいいかもしれません。