@shutootaki/gwm
Version:
git worktree manager CLI
168 lines • 5.67 kB
JavaScript
/**
* __complete の本体(候補生成)
* シェル補完から呼び出される
*/
import { completionDefinition } from '../definition.js';
import { parseTokens, getActiveCommand } from './parser.js';
import { getProviderCandidates, } from '../providers/index.js';
/**
* 候補を生成
* @param tokens トークン列(gwm 自体は除く)
* @param cword 現在補完中のトークンインデックス
* @param shell シェルの種類
* @returns 候補文字列(1行1候補、タブ区切りで説明)
*/
export async function generateCandidates(tokens, cword, shell) {
const context = parseTokens(tokens, cword);
const candidates = [];
switch (context.cursorPosition) {
case 'subcommand':
await collectSubcommandCandidates(context, candidates);
break;
case 'option':
collectOptionCandidates(context, candidates);
break;
case 'optionValue':
await collectOptionValueCandidates(context, candidates);
break;
case 'positional':
await collectPositionalCandidates(context, candidates);
break;
}
// プレフィックスでフィルタリング
const prefix = context.currentToken.toLowerCase();
const filtered = candidates.filter((c) => c.value.toLowerCase().startsWith(prefix));
// 出力フォーマット
return formatCandidates(filtered, shell);
}
/**
* サブコマンド候補を収集
*/
async function collectSubcommandCandidates(context, candidates) {
// completion コマンドのサブコマンド
if (context.command?.name === 'completion' && !context.subcommand) {
for (const sub of context.command.subcommands || []) {
if (!sub.hidden) {
candidates.push({
value: sub.name,
description: sub.description,
});
}
}
return;
}
// トップレベルのサブコマンド
for (const cmd of completionDefinition.commands) {
if (cmd.hidden)
continue;
candidates.push({
value: cmd.name,
description: cmd.description,
});
// 別名も追加
for (const alias of cmd.aliases || []) {
candidates.push({
value: alias,
description: cmd.description,
});
}
}
}
/**
* オプション候補を収集
*/
function collectOptionCandidates(context, candidates) {
const activeCommand = getActiveCommand(context);
if (!activeCommand?.options) {
return;
}
for (const opt of activeCommand.options) {
for (const name of opt.names) {
// 既に使用済みのオプションはスキップ
if (context.completedOptions.includes(name)) {
continue;
}
candidates.push({
value: name,
description: opt.description,
});
}
}
// グローバルオプションも追加
for (const opt of completionDefinition.globalOptions || []) {
for (const name of opt.names) {
if (context.completedOptions.includes(name)) {
continue;
}
candidates.push({
value: name,
description: opt.description,
});
}
}
}
/**
* 引数定義から候補を収集する共通ヘルパー
*/
async function collectCandidatesFromArg(arg, candidates) {
if (arg.staticValues) {
candidates.push(...arg.staticValues.map((val) => ({ value: val })));
return;
}
if (arg.providers) {
const results = await Promise.all(arg.providers.map(getProviderCandidates));
candidates.push(...results.flat());
}
}
/**
* オプション値候補を収集
*/
async function collectOptionValueCandidates(context, candidates) {
const arg = context.currentOptionNeedingValue?.valueArg;
if (arg)
await collectCandidatesFromArg(arg, candidates);
}
/**
* 位置引数候補を収集
*/
async function collectPositionalCandidates(context, candidates) {
const activeCommand = getActiveCommand(context);
const argIndex = context.positionalArgs.length;
const arg = activeCommand?.args?.[argIndex];
if (arg) {
await collectCandidatesFromArg(arg, candidates);
}
else if (activeCommand?.name === 'add' && context.hasRemoteFlag) {
// add コマンドで -r/--remote が指定されている場合はリモートブランチを候補に
candidates.push(...(await getProviderCandidates('remoteBranchesOrigin')));
}
}
/**
* 候補をフォーマット
* @param candidates 候補リスト
* @param _shell シェルの種類(将来のシェル固有フォーマット対応用に予約)
*/
function formatCandidates(candidates, _shell) {
// 重複除去しつつフォーマット("value\tdescription" 形式)
const seen = new Set();
return candidates
.filter((c) => !seen.has(c.value) && seen.add(c.value))
.map((c) => (c.description ? `${c.value}\t${c.description}` : c.value))
.join('\n');
}
/**
* __complete コマンドのエントリーポイント
*/
export async function runComplete(shell, cword, tokens) {
try {
const output = await generateCandidates(tokens, cword, shell);
if (output) {
process.stdout.write(output + '\n');
}
}
catch {
// エラー時は何も出力しない(補完が壊れないように)
process.exit(0);
}
}
//# sourceMappingURL=complete.js.map