denite.nvimでプレビュー機能付きのソースを作る
はじめに
denite.nvimにはユーザーが作ったソースを動的に読み込む仕組みがあり、簡単に機能を追加することができます。
そのようなソースの作成において、Vim/Neovimのターミナル機能を使ったプレビューができるようになったので紹介します。
本機能は筆者がPRを出して、Shougoさんに取り込んでいただいたものです。
機能の説明
例えば、git log
を一覧表示できるようなソースを作ることを考えます。
このとき、コミットの変更内容をプレビューできると便利そうです。
しかし、git diff
などで変更内容を得られたとしても、それを色付きでVimのバッファに出力するのは大変です。
gitの出力ならば、Vimのシンタックスファイルがあるのでまだ簡単ですが、それでもプレビューウィンドウの処理とか色々なことを考えなければならず面倒です。
今回新たに、そうした面倒なことを考えずに簡単に外部コマンドを使ったプレビュー機能を追加できるようになりました。
背景
最近deniteに、batを使ったプレビュー機能が追加されました。
これまでのVimのバッファを使ったプレビューでも十分便利なのですが、Denite file/rec -auto-action=preview
のように自動プレビューを有効にした場合に、読み込まれていないファイルにはVimのシンタックスハイライトが効かなかったり、効いたとしてもシンタックスは比較的重い処理なので、カーソルを素早く動かしたときに動きがカクついてしまうということがありました。
それに対して、batによるプレビューはVimのターミナル機能を使っており、非同期でファイルの内容を色付きで表示してくれます。
これにより、カーソルを早く動かしてもストレスなくプレビューすることができるようになりました。
外部コマンドによるプレビューは、非同期で表示ができて便利ですし、batだけでなく他にも様々な用途に使えるのではないかと思い、より汎用的な使い方ができるようにPRを出してみました。
使用例
外部コマンドによるプレビューの具体例として、git log
のソースを作ってみます。
sourceの定義
以下はgit log
の出力をdenite
で表示する最小構成のコードです。
gitのディレクトリの中にVimのカレントディレクトリがあれば、ログの一覧を表示します。
import subprocess
from denite.base.source import Base
from denite.util import Nvim, UserContext, Candidates
from denite.base.kind import Base as KindBase
class Source(Base):
def __init__(self, vim: Nvim) -> None:
super().__init__(vim)
self.vim = vim
self.name = "git/log"
# self.kind = Kind(vim)
def gather_candidates(self, context: UserContext) -> Candidates:
candidates: Candidates = []
cwd = self.vim.call('getcwd')
output = subprocess.run(['git', 'log', '--pretty=oneline',
'--abbrev-commit', '--', '.'],
stdout=subprocess.PIPE, cwd=cwd)
items = output.stdout.decode().split('\n')
if not items:
return []
for item in items:
candidates.append(
{
"word": item,
"__obj": item.split(' ')[0],
}
)
return candidates
このファイルを(&runtimepath)/rplugin/python3/denite/source/git/log.py
に保存すると、Denite git/log
でコミットのオブジェクト名とコミットメッセージが見られるようになります。
kindの定義
次にgit/log
ソースに対して、kind
を定義します。今回はdefault_action
であるopen
と、本題のpreview
アクションを追加します。
class Kind(KindBase):
def __init__(self, vim: Nvim) -> None:
super().__init__(vim)
self.name = 'gitdiff'
self.default_action = 'open'
def action_open(self, context: UserContext) -> None:
target = context['targets'][0]
self.vim.command("Gina show {}".format(target['__obj']))
def action_preview(self, context: UserContext) -> None:
target = context['targets'][0]
diff_cmd = ['git', 'diff', target['__obj'] + '^!']
self.preview_terminal(context, diff_cmd, 'preview')
open
についてはgina.vimを使いました。
preview
はとてもシンプルで、プレビューするためのコマンドをself.preview_terminal
に渡せば良いです。
3つ目の引数はアクションの名前です。action_preview_bat
であれば、preview_bat
になります。
ハイライトも追加した完成版はこちらです。
denite git/log source
import subprocess
from denite.base.source import Base
from denite.util import Nvim, UserContext, Candidates
from denite.base.kind import Base as KindBase
GITLOG_OBJ_SYNTAX = (
'syntax match {0}_obj '
r'/^\s\S*/ '
)
GITLOG_OBJ_HIGHLIGHT = (
'highlight default link {0}_obj Statement'
)
class Source(Base):
def __init__(self, vim: Nvim) -> None:
super().__init__(vim)
self.vim = vim
self.name = "git/log"
self.kind = Kind(vim)
def highlight(self) -> None:
self.vim.command(GITLOG_OBJ_SYNTAX.format(self.syntax_name))
self.vim.command(GITLOG_OBJ_HIGHLIGHT.format(self.syntax_name))
def gather_candidates(self, context: UserContext) -> Candidates:
candidates: Candidates = []
cwd = self.vim.call('getcwd')
output = subprocess.run(['git', 'log', '--pretty=oneline',
'--abbrev-commit', '--', '.'],
stdout=subprocess.PIPE, cwd=cwd)
items = output.stdout.decode().split('\n')
if not items:
return []
for item in items:
candidates.append(
{
"word": item,
"__obj": item.split(' ')[0],
}
)
return candidates
class Kind(KindBase):
def __init__(self, vim: Nvim) -> None:
super().__init__(vim)
self.name = 'gitdiff'
self.default_action = 'open'
def action_open(self, context: UserContext) -> None:
target = context['targets'][0]
self.vim.command("Gina show {}".format(target['__obj']))
def action_preview(self, context: UserContext) -> None:
target = context['targets'][0]
diff_cmd = ['git', 'diff', target['__obj'] + '^!']
self.preview_terminal(context, diff_cmd, 'preview')
使ってみる
こんな感じで使えます。
動画内で実行しているコマンドはこちらです。
Denite git/log -auto-action=preview -vertical-preview -no-auto-resize -preview-height=100 -winheight=100 -preview-width=100
おわりに
今回実装したgit/log
のように、簡単なコードで実用的なdeniteのソースを作ることができます。
deniteはtelescope.nvimやfzf-preview.vimに比べると、一見地味に見えるかもしれませんが、プレビュー機能も充実しています。
私のdotfilesでは今回のgitの他にも色々なソースを作っています。参考になれば幸いです。
Discussion