ファジーファインダープラグイン ddu.vim の拡張方法について
始めに
最近はプラグイン開発も落ち着いており、細々と ddx.vim
の開発を進めていたりします。
他になにかするべきではないかと考えていると、「ddc.vim
や ddu.vim
のプラグインの作り方記事があるとユーザーが増えるのではないか」という意見をもらいました。
確かに、調べてみても ddc.vim
や ddu.vim
の設定について書いてあるものはあっても拡張方法を解説している記事は見当たりません。
ドキュメントには書いているのですが、あれはどちらかというとリファレンスでしょう。
前回は ddc.vim
の拡張方法について解説したので、今回は ddu.vim
の拡張方法について解説します。
ddu.vim プラグインの基本
ddu.vim
は denops.vim
を用いて開発されており、TypeScript で拡張することが可能です。
TypeScript で拡張できるということの利点は、型を用いることで保守性が高まることです。
他にもライブラリが豊富なのも Web API を扱うには便利でしょう。
最近は Deno
から npm のライブラリを呼び出すことができ、よりパワフルな環境となりつつあります。
unite.vim
は vimrc 上でインラインに source を定義でき、ddu.vim
よりも source を作るのは簡単でしたが Vim script の複雑なプログラムは保守が大変でした。
ddu.vim
用のプラグインは denops/
以下の特定の場所にファイルを配置することで自動的にロードされます。
手動でプラグインを登録することはできません。これは、Vim, neovim の Vim script, Lua プラグインと同じです。
最初は手動で登録することもできるようにしていたのですが、それで便利になることがなく手動登録が面倒なだけだったので廃止しました。
source の作成方法
それではまず、ddu.vim
の簡単な source
を作成してみることにしましょう。
プラグインのディレクトリに以下の denops/@ddu-sources/sample.ts
のファイルを作成します。
import {
type Context,
type DduOptions,
type Item,
type SourceOptions,
} from "jsr:@shougo/ddu-vim@~6.1.0/types";
import { BaseSource } from "jsr:@shougo/ddu-vim@~6.1.0/source";
import { type ActionData } from "jsr:@shougo/ddu-kind-file@~0.9.0";
import type { Denops } from "jsr:@denops/core@~7.0.0";
import * as fn from "jsr:@denops/std@~7.1.0/function";
import { join } from "jsr:@std/path@~1.0.2/join";
type Params = Record<never, never>;
export class Source extends BaseSource<Params> {
override kind = "file";
override gather(args: {
denops: Denops;
options: DduOptions;
sourceOptions: SourceOptions;
sourceParams: Params;
input: string;
}): ReadableStream<Item<ActionData>[]> {
return new ReadableStream({
async start(controller) {
const dir = await fn.getcwd(args.denops) as string;
const tree = async (root: string) => {
let items: Item<ActionData>[] = [];
try {
for await (const entry of Deno.readDir(root)) {
const path = join(root, entry.name);
items.push({
word: path,
action: {
path: path,
},
});
}
} catch (e: unknown) {
console.error(e);
}
return items;
};
controller.enqueue(
await tree(dir),
);
controller.close();
},
});
}
override params(): Params {
return {};
}
}
プラグインの型情報は ddu_vim
のリポジトリから取得する必要があります。
BaseSource
を継承して Source を作成します。gather()
はアイテムを返す関数です。ここではカレントディレクトリにあるファイルを返しています。
gather()
では非同期でアイテムを追加できるようにするため、 ReadableStream
を用いる必要があります。
ReadableStream
の controller
にアイテムを追加することでリアルタイムに反映されます。アイテムの追加が終了したら controller
を close します。
それぞれのアイテムは Item
型となっています。Item
型については :help ddu-item-attributes
を参照してください。
options
にはユーザーのオプション (:help ddu-options
)、sourceOptions
には source 共通のオプション(:help ddu-source-options
)、sourceParams
は source 固有のオプションです。
input
はユーザーの絞り込み文字列が渡されます。
source
では絞り込みを行わずに、該当する全てのアイテムを返却するようにします。絞り込みを行うのは filter
の役目だからです。
source
はアイテムの kind
を指定することが可能です。ここでは file
を指定しており、file
のアクションを実行できるアイテムを生成することが分かります。
file
kind
のアクションを実行するために、アイテムの action
でアクションを実行するための情報をセットする必要があります。
action
には ActionData
型の値をセットする必要があります。ActionData
の中身は kind
によって異なるので kind
のリポジトリから型定義を取得する必要があります。
このプラグインを試すための最小構成は例えば以下のようになります。~/src/ddu-samples
が今回作成したプラグインのディレクトリです。
set nocompatible
set rtp+=~/work/denops.vim
set rtp+=~/work/ddu.vim
set rtp+=~/work/ddu-ui-ff
set rtp+=~/work/ddu-kind-file
set rtp+=~/src/ddu-samples
call ddu#custom#patch_global(#{
\ ui: 'ff',
\ sources: [#{name: 'sample', params: {}}],
\ kindOptions: #{
\ file: #{
\ defaultAction: 'open',
\ },
\ },
\ })
autocmd FileType ddu-ff call s:ddu_my_settings()
function! s:ddu_my_settings() abort
nnoremap <buffer> <CR>
\ <Cmd>call ddu#ui#ff#do_action('itemAction')<CR>
endfunction
call ddu#start({})
filter の作成方法
次に ddu.vim
の filter
を作成します。filter
とは matchers
, sorters
, converters
に指定するもので、それぞれのインタフェースは共通となっています。
プラグインのディレクトリに以下の denops/@ddu-filters/sample.ts
のファイルを作成します。
import {
type DduItem,
type SourceOptions,
} from "jsr:@shougo/ddu-vim@~6.1.0/types";
import { BaseFilter } from "jsr:@shougo/ddu-vim@~6.1.0/filter";
import type { Denops } from "jsr:@denops/core@~7.0.0";
type Params = {
foot: string;
};
export class Filter extends BaseFilter<Params> {
// deno-lint-ignore require-await
override async filter(args: {
denops: Denops;
sourceOptions: SourceOptions;
filterParams: Params,
input: string;
items: DduItem[];
}): Promise<DduItem[]> {
for (const item of args.items) {
item.display = item.word + args.filterParams.foot;
}
return Promise.resolve(args.items);
}
override params(): Params {
return {
foot: "hoge",
};
}
}
プラグインの型情報は ddu_vim
のリポジトリから取得する必要があります。
BaseFilter
を継承して Filter
を作成します。filter()
は加工後のアイテムを返す関数です。ここでは word
に filterParams.foot
を結合したものを display
にセットしています。
それぞれの補完候補は Item
型となっています。Item
型については :help ddu-item-attributes
を参照してください。
filterParams
は filter 固有のオプションです。
input
は絞り込み文字列が渡されます。items
は全体のアイテムが格納されています。
このプラグインを試すための最小構成は例えば以下のようになります。~/src/ddu-samples
が今回作成したプラグインのディレクトリです。
set rtp+=~/work/denops.vim
set rtp+=~/work/ddu.vim
set rtp+=~/work/ddu-ui-ff
set rtp+=~/work/ddu-kind-file
set rtp+=~/src/ddu-samples
call ddu#custom#patch_global(#{
\ ui: 'ff',
\ sources: [#{name: 'sample', params: {}}],
\ sourceOptions : #{
\ _ : #{
\ converters: ['sample'],
\ }
\ },
\ kindOptions: #{
\ file: #{
\ defaultAction: 'open',
\ },
\ },
\ })
autocmd FileType ddu-ff call s:ddu_my_settings()
function! s:ddu_my_settings() abort
nnoremap <buffer> <CR>
\ <Cmd>call ddu#ui#ff#do_action('itemAction')<CR>
endfunction
call ddu#start({})
kind の作成方法
ddu.vim
では独自の kind を追加することが可能です。アクションは kind に紐付いており、独自の kind を追加することで独自のアクションを実行することができます。
プラグインのディレクトリに以下の denops/@ddu-kinds/sample.ts
のファイルを作成します。
import {
type ActionArguments,
type ActionFlags,
type DduItem,
type PreviewContext,
} from "jsr:@shougo/ddu-vim@~6.1.0/types";
import { BaseKind } from "jsr:@shougo/ddu-vim@~6.1.0/kind";
import type { Denops } from "jsr:@denops/core@~7.0.0";
import * as fn from "jsr:@denops/std@~7.1.0/function";
import * as vars from "jsr:@denops/std@~7.3.0/variable";
export type ActionData = {
path: string;
};
type Params = Record<never, never>;
export class Kind extends BaseKind<Params> {
override actions: Record<
string,
(args: ActionArguments<Params>) => Promise<ActionFlags>
> = {
echo: async (args: { denops: Denops; items: DduItem[] }) => {
for (const item of args.items) {
console.log(item.action.path);
}
return Promise.resolve(ActionFlags.None);
},
};
override async getPreviewer(args: {
denops: Denops;
item: DduItem;
actionParams: unknown;
previewContext: PreviewContext;
}): Promise<Previewer | undefined> {
const action = args.item.action as ActionData;
if (!action) {
return undefined;
}
return {
kind: "buffer",
path: action.path,
};
}
override params(): Params {
return {};
}
}
BaseKind
を継承して Kind
を作成します。actions
には kind
のアイテムのアクションを定義します。ここでは echo
アクションを定義しています。
ActionData
にはアイテムに含まれるアクションの固有情報を定義します。
items
はアクションの実行対象となるアイテムが格納されています。アイテムが複数選択されていた場合に対応するためにリストとなっています。
アクションは ActionFlags
を返却します。それぞれの ActionFlags
の意味は以下です。
-
ActionFlags.None
: アクションの実行後、UI を閉じる。 -
ActionFlags.RefreshItems
: アクションの実行後、UI のアイテムを再取得する。 -
ActionFlags.Redraw
: アクションの実行後、UI を再描画する。 -
ActionFlags.Persist
: アクションの実行後、UI を閉じない。
ちなみに、kind
のデフォルトアクションはユーザーが指定するので kind
側で指定できません。
kind
は getPreviewer()
により独自の previewer
を指定することが可能です。
previewer
は UI の preview
アクションの挙動を制御するための機能です。
previewer
は以下のどれかを返さないといけません。それぞれの型の詳細は https://deno.land/x/ddu_vim@v2.2.0/types.ts
を参照してください。
-
TerminalPreviewer
: アイテムを:terminal
で特定コマンドの実行によりプレビューする -
BufferPreviewer
: バッファもしくはパスに紐付くアイテムをプレビューする -
NoFilePreviewer
: バッファに紐付かないアイテムをプレビューする
この kind
を source
側で使用するには以下のように source
の kind
として指定する必要があります。
export class Source extends BaseSource<Params> {
override kind = "sample";
...
}
このプラグインを試すための最小構成は例えば以下のようになります。~/src/ddu-samples
が今回作成したプラグインのディレクトリです。
set rtp+=~/work/denops.vim
set rtp+=~/work/ddu.vim
set rtp+=~/work/ddu-ui-ff
set rtp+=~/work/ddu-kind-file
set rtp+=~/src/ddu-samples
call ddu#custom#patch_global(#{
\ ui: 'ff',
\ sources: [#{name: 'sample', params: {}}],
\ sourceOptions : #{
\ _ : #{
\ converters: ['sample'],
\ }
\ },
\ kindOptions: #{
\ sample: #{
\ defaultAction: 'echo',
\ },
\ },
\ })
autocmd FileType ddu-ff call s:ddu_my_settings()
function! s:ddu_my_settings() abort
nnoremap <buffer> <CR>
\ <Cmd>call ddu#ui#ff#do_action('itemAction')<CR>
endfunction
call ddu#start({})
UI の作成方法
ddu.vim
は UI も拡張可能です。UI を自分で作成することで、アイテムを自由に表示できます。
しかし source
, filter
, kind
と比較すると仕様が複雑である上に需要はあまりないと思うので、今回は解説を省略します。
columns の作成方法
ddu.vim
は表示カラムの拡張もサポートしていますが、こちらは ddu-ui-filer
といった一部の UI の表示でしか使わないものなので解説を省略します。
GitHub sponsors について
ddu.vim
プラグインの開発は GitHub sponsors の皆さんの支援によって行われました。
今後も Github sponsors による支援を受けてテキストエディタ活動を続けていきたいと思っています。
Discussion