@shutootaki/gwm
Version:
git worktree manager CLI
179 lines • 6.57 kB
JavaScript
/**
* トークン解析ロジック
* シェルから渡されたトークン列を解析して補完コンテキストを特定
*/
import { findCommand, findSubcommand } from '../definition.js';
/**
* オプション名かどうか判定
*/
function isOptionLike(token) {
return token.startsWith('-');
}
/**
* コマンドからオプションを検索
*/
function findOption(command, optionName) {
if (!command?.options) {
return undefined;
}
return command.options.find((opt) => opt.names.includes(optionName));
}
/**
* トークン列を解析してコンテキストを返す
* @param tokens トークン列(gwm 自体は除く)
* @param cword 現在補完中のトークンインデックス
*/
export function parseTokens(tokens, cword) {
const context = {
completedOptions: [],
positionalArgs: [],
currentToken: cword >= 0 && cword < tokens.length ? tokens[cword] : '',
cursorPosition: 'subcommand',
hasRemoteFlag: false,
};
// 空の場合はサブコマンド補完
if (tokens.length === 0) {
return context;
}
let tokenIndex = 0;
let pendingOptionForValue;
// 最初のトークンがサブコマンドかどうか判定
const firstToken = tokens[0];
if (!isOptionLike(firstToken)) {
context.command = findCommand(firstToken);
tokenIndex = 1;
}
// コマンドが見つからなければサブコマンド補完
if (!context.command) {
context.cursorPosition = 'subcommand';
return context;
}
// 2番目のトークン以降を解析
while (tokenIndex < tokens.length) {
const token = tokens[tokenIndex];
const isCurrentToken = tokenIndex === cword;
// 前のオプションが値を待っている場合
if (pendingOptionForValue) {
if (!isCurrentToken) {
// 値が入力された
context.completedOptions.push(...pendingOptionForValue.names);
}
else {
// 現在のトークンがオプション値
context.currentOptionNeedingValue = pendingOptionForValue;
context.cursorPosition = 'optionValue';
return context;
}
pendingOptionForValue = undefined;
tokenIndex++;
continue;
}
// サブコマンドの可能性をチェック(completion コマンドの場合)
if (!context.subcommand &&
context.command.subcommands &&
!isOptionLike(token)) {
const sub = findSubcommand(context.command, token);
if (sub) {
context.subcommand = sub;
tokenIndex++;
continue;
}
}
// オプションの場合
if (isOptionLike(token)) {
// --option=value 形式の処理
const eqIndex = token.indexOf('=');
const optName = eqIndex > 0 ? token.slice(0, eqIndex) : token;
// アクティブなコマンド(サブコマンドがあればそちらを優先)
const activeCommand = context.subcommand || context.command;
const option = findOption(activeCommand, optName);
if (option) {
// -r/--remote フラグを追跡
if (option.names.includes('-r') || option.names.includes('--remote')) {
context.hasRemoteFlag = true;
}
if (option.takesValue) {
if (eqIndex > 0) {
// --option=value 形式 → 値まで入力済み
context.completedOptions.push(...option.names);
}
else if (isCurrentToken) {
// オプション自体を補完中
context.cursorPosition = 'option';
return context;
}
else {
// 次のトークンが値
pendingOptionForValue = option;
}
}
else {
// フラグオプション
context.completedOptions.push(...option.names);
}
}
tokenIndex++;
continue;
}
// 位置引数の場合
if (!isCurrentToken) {
context.positionalArgs.push(token);
}
tokenIndex++;
}
// 最終的なカーソル位置を決定
if (cword >= tokens.length || tokens[cword] === '') {
// 新しいトークンを開始
if (pendingOptionForValue) {
context.currentOptionNeedingValue = pendingOptionForValue;
context.cursorPosition = 'optionValue';
}
else if (context.command?.subcommands &&
context.command.subcommands.length > 0 &&
!context.subcommand) {
// サブコマンドを持つコマンドで、まだサブコマンドが確定していない場合
context.cursorPosition = 'subcommand';
}
else {
// 位置引数かオプションか判定
const activeCommand = context.subcommand || context.command;
if (activeCommand?.args && activeCommand.args.length > 0) {
const argIndex = context.positionalArgs.length;
if (argIndex < activeCommand.args.length) {
context.cursorPosition = 'positional';
}
else {
context.cursorPosition = 'option';
}
}
else {
context.cursorPosition = 'option';
}
}
}
else {
const currentToken = tokens[cword];
if (isOptionLike(currentToken)) {
context.cursorPosition = 'option';
}
else if (cword === 0) {
context.cursorPosition = 'subcommand';
}
else if (context.command?.subcommands &&
!context.subcommand &&
cword === 1) {
context.cursorPosition = 'subcommand';
}
else {
context.cursorPosition = 'positional';
}
}
return context;
}
/**
* アクティブなコマンドを取得(サブコマンドがあればそちらを優先)
*/
export function getActiveCommand(context) {
return context.subcommand || context.command;
}
//# sourceMappingURL=parser.js.map