fix: dfeat: command parameters
This commit is contained in:
parent
91d46dea75
commit
28066c7fff
|
|
@ -45,16 +45,16 @@ export const defaultCommands: Record<string, MdCommanderCommand> = {
|
|||
roll: {
|
||||
command: "roll",
|
||||
description: "掷骰子 - 支持骰池、修饰符和组合",
|
||||
options: {
|
||||
formula: {
|
||||
option: "formula",
|
||||
parameters: [
|
||||
{
|
||||
name: "formula",
|
||||
description: "骰子公式,如 3d6+{d4,d8}kh2-5",
|
||||
type: "string",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
handler: (args) => {
|
||||
const formula = args.formula || "1d6";
|
||||
const formula = args.params.formula || "1d6";
|
||||
const { rollSimple } = useDiceRoller();
|
||||
const result = rollSimple(formula);
|
||||
return {
|
||||
|
|
@ -68,16 +68,19 @@ export const defaultCommands: Record<string, MdCommanderCommand> = {
|
|||
|
||||
// ==================== 工具函数 ====================
|
||||
|
||||
export function parseInput(input: string): {
|
||||
export function parseInput(input: string, commands?: Record<string, MdCommanderCommand>): {
|
||||
command?: string;
|
||||
params: Record<string, string>;
|
||||
options: Record<string, string>;
|
||||
incompleteOption?: string;
|
||||
incompleteParam?: { index: number; value: string };
|
||||
} {
|
||||
const result: {
|
||||
command?: string;
|
||||
params: Record<string, string>;
|
||||
options: Record<string, string>;
|
||||
incompleteOption?: string;
|
||||
incompleteParam?: { index: number; value: string };
|
||||
} = {
|
||||
params: {},
|
||||
options: {},
|
||||
};
|
||||
|
||||
|
|
@ -87,14 +90,23 @@ export function parseInput(input: string): {
|
|||
const parts = trimmed.split(/\s+/);
|
||||
let i = 0;
|
||||
|
||||
// 获取命令
|
||||
if (parts[0] && !parts[0].startsWith("-")) {
|
||||
result.command = parts[0];
|
||||
i = 1;
|
||||
}
|
||||
|
||||
// 获取命令的参数定义
|
||||
const cmd = result.command ? commands?.[result.command] : undefined;
|
||||
const paramDefs = cmd?.parameters || [];
|
||||
let paramIndex = 0;
|
||||
|
||||
// 解析参数和选项
|
||||
while (i < parts.length) {
|
||||
const part = parts[i];
|
||||
|
||||
if (part.startsWith("--")) {
|
||||
// 选项 --key=value 或 --key value
|
||||
const eqIndex = part.indexOf("=");
|
||||
if (eqIndex !== -1) {
|
||||
const key = part.slice(2, eqIndex);
|
||||
|
|
@ -106,13 +118,38 @@ export function parseInput(input: string): {
|
|||
result.options[key] = parts[i + 1];
|
||||
i++;
|
||||
} else {
|
||||
result.incompleteOption = key;
|
||||
// 未完成的选项
|
||||
}
|
||||
}
|
||||
} else if (part.startsWith("-") && part.length === 2) {
|
||||
// 短选项 -k value
|
||||
const key = part.slice(1);
|
||||
if (i + 1 < parts.length && !parts[i + 1].startsWith("-")) {
|
||||
result.options[key] = parts[i + 1];
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
// 位置参数
|
||||
if (paramIndex < paramDefs.length) {
|
||||
const paramDef = paramDefs[paramIndex];
|
||||
result.params[paramDef.name] = part;
|
||||
paramIndex++;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
// 检查是否有未完成的位置参数
|
||||
if (paramIndex < paramDefs.length) {
|
||||
const lastPart = parts[parts.length - 1];
|
||||
if (lastPart && !lastPart.startsWith("-")) {
|
||||
result.incompleteParam = {
|
||||
index: paramIndex,
|
||||
value: lastPart,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +168,7 @@ export function getCompletions(
|
|||
}));
|
||||
}
|
||||
|
||||
const parsed = parseInput(trimmed);
|
||||
const parsed = parseInput(trimmed, commands);
|
||||
|
||||
if (!parsed.command || !commands[parsed.command]) {
|
||||
const commandCompletions = Object.values(commands)
|
||||
|
|
@ -148,20 +185,39 @@ export function getCompletions(
|
|||
}
|
||||
|
||||
const cmd = commands[parsed.command!];
|
||||
if (!cmd || !cmd.options) return [];
|
||||
if (!cmd) return [];
|
||||
|
||||
if (parsed.incompleteOption) {
|
||||
const option = cmd.options[parsed.incompleteOption];
|
||||
if (option?.type === "enum" && option.values) {
|
||||
return option.values
|
||||
.filter((v) => parsed.incompleteOption && v.startsWith(parsed.incompleteOption))
|
||||
// 检查是否需要参数补全
|
||||
const paramDefs = cmd.parameters || [];
|
||||
const usedParams = Object.keys(parsed.params);
|
||||
|
||||
// 如果还有未填的参数,提供参数值补全
|
||||
if (paramDefs.length > usedParams.length) {
|
||||
const paramDef = paramDefs[usedParams.length];
|
||||
if (paramDef.type === "enum" && paramDef.values) {
|
||||
const currentValue = parsed.incompleteParam?.value || "";
|
||||
return paramDef.values
|
||||
.filter((v) => v.startsWith(currentValue))
|
||||
.map((v) => ({
|
||||
label: v,
|
||||
type: "value",
|
||||
description: paramDef.description,
|
||||
insertText: v,
|
||||
}));
|
||||
}
|
||||
// 其他类型的参数,显示提示
|
||||
if (parsed.incompleteParam) {
|
||||
return [{
|
||||
label: `<${paramDef.name}>`,
|
||||
type: "value",
|
||||
description: `${paramDef.type}${paramDef.required !== false ? " (必填)" : ""}: ${paramDef.description || ""}`,
|
||||
insertText: "",
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
// 选项补全
|
||||
if (!cmd.options) return [];
|
||||
|
||||
const usedOptions = Object.keys(parsed.options);
|
||||
return Object.values(cmd.options)
|
||||
|
|
@ -208,6 +264,10 @@ export interface UseCommanderReturn {
|
|||
updateCompletions: () => void;
|
||||
acceptCompletion: () => void;
|
||||
commands: Record<string, MdCommanderCommand>;
|
||||
historyIndex: () => number;
|
||||
setHistoryIndex: (v: number) => void;
|
||||
commandHistory: () => string[];
|
||||
navigateHistory: (direction: 'up' | 'down') => void;
|
||||
}
|
||||
|
||||
export function useCommander(
|
||||
|
|
@ -220,6 +280,10 @@ export function useCommander(
|
|||
const [selectedCompletion, setSelectedCompletion] = createSignal(0);
|
||||
const [isFocused, setIsFocused] = createSignal(false);
|
||||
|
||||
// 命令历史
|
||||
const [commandHistory, setCommandHistory] = createSignal<string[]>([]);
|
||||
const [historyIndex, setHistoryIndex] = createSignal(-1);
|
||||
|
||||
const commands = { ...defaultCommands, ...customCommands };
|
||||
|
||||
// 更新 help 命令的选项值
|
||||
|
|
@ -233,17 +297,17 @@ export function useCommander(
|
|||
const input = inputValue().trim();
|
||||
if (!input) return;
|
||||
|
||||
const parsed = parseInput(input);
|
||||
const parsed = parseInput(input, commands);
|
||||
const commandName = parsed.command;
|
||||
const cmd = commands[commandName!];
|
||||
|
||||
let result: { message: string; type?: "success" | "error" | "warning" | "info" };
|
||||
let result: { message: string; type?: "success" | "error" | "warning" | "info"; isHtml?: boolean };
|
||||
|
||||
if (!cmd) {
|
||||
result = { message: `未知命令:${commandName}`, type: "error" };
|
||||
} else if (cmd.handler) {
|
||||
try {
|
||||
result = cmd.handler(parsed.options);
|
||||
result = cmd.handler({ params: parsed.params, options: parsed.options });
|
||||
} catch (e) {
|
||||
result = {
|
||||
message: `执行错误:${e instanceof Error ? e.message : String(e)}`,
|
||||
|
|
@ -263,6 +327,11 @@ export function useCommander(
|
|||
};
|
||||
|
||||
setEntries((prev) => [...prev, newEntry]);
|
||||
|
||||
// 添加到命令历史
|
||||
setCommandHistory((prev) => [...prev, input]);
|
||||
setHistoryIndex(-1); // 重置历史索引
|
||||
|
||||
setInputValue("");
|
||||
setShowCompletions(false);
|
||||
|
||||
|
|
@ -285,7 +354,7 @@ export function useCommander(
|
|||
if (!comp) return;
|
||||
|
||||
const input = inputValue();
|
||||
const parsed = parseInput(input);
|
||||
const parsed = parseInput(input, commands);
|
||||
|
||||
let newValue: string;
|
||||
if (comp.type === "command") {
|
||||
|
|
@ -296,24 +365,58 @@ export function useCommander(
|
|||
.map(([k, v]) => `--${k}=${v}`)
|
||||
.join(" ");
|
||||
newValue = `${base} ${existingOptions}${existingOptions ? " " : ""}${comp.insertText}`;
|
||||
} else {
|
||||
const optKey = parsed.incompleteOption;
|
||||
if (optKey) {
|
||||
} else if (comp.type === "value") {
|
||||
// 参数值补全
|
||||
const cmd = parsed.command ? commands[parsed.command] : null;
|
||||
const paramDefs = cmd?.parameters || [];
|
||||
const usedParams = Object.keys(parsed.params);
|
||||
|
||||
if (paramDefs.length > usedParams.length) {
|
||||
// 当前参数的补全
|
||||
const base = parsed.command || "";
|
||||
const otherOptions = Object.entries(parsed.options)
|
||||
.filter(([k]) => k !== optKey)
|
||||
const existingParams = Object.values(parsed.params).join(" ");
|
||||
const existingOptions = Object.entries(parsed.options)
|
||||
.map(([k, v]) => `--${k}=${v}`)
|
||||
.join(" ");
|
||||
newValue = `${base} --${optKey}=${comp.insertText}${otherOptions ? " " + otherOptions : ""}`;
|
||||
newValue = `${base} ${existingParams}${existingParams ? " " : ""}${comp.insertText}${existingOptions ? " " + existingOptions : ""}`;
|
||||
} else {
|
||||
newValue = input;
|
||||
}
|
||||
} else {
|
||||
newValue = input;
|
||||
}
|
||||
|
||||
setInputValue(newValue.trim());
|
||||
setShowCompletions(false);
|
||||
};
|
||||
|
||||
const navigateHistory = (direction: 'up' | 'down') => {
|
||||
const history = commandHistory();
|
||||
if (history.length === 0) return;
|
||||
|
||||
let newIndex = historyIndex();
|
||||
if (direction === 'up') {
|
||||
// 向上浏览历史(更早的命令)
|
||||
if (newIndex < history.length - 1) {
|
||||
newIndex++;
|
||||
}
|
||||
} else {
|
||||
// 向下浏览历史(更新的命令)
|
||||
if (newIndex > 0) {
|
||||
newIndex--;
|
||||
} else {
|
||||
// 回到当前输入
|
||||
setInputValue("");
|
||||
setHistoryIndex(-1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setHistoryIndex(newIndex);
|
||||
// 从历史末尾获取命令(最新的在前)
|
||||
setInputValue(history[history.length - 1 - newIndex]);
|
||||
};
|
||||
|
||||
return {
|
||||
inputValue,
|
||||
entries,
|
||||
|
|
@ -330,5 +433,9 @@ export function useCommander(
|
|||
updateCompletions,
|
||||
acceptCompletion,
|
||||
commands,
|
||||
historyIndex,
|
||||
setHistoryIndex,
|
||||
commandHistory,
|
||||
navigateHistory,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,18 @@ customElement<MdCommanderProps>(
|
|||
}
|
||||
}
|
||||
|
||||
// 补全未打开时,使用上下键浏览历史
|
||||
if (e.key === "ArrowUp" && !commander.showCompletions()) {
|
||||
e.preventDefault();
|
||||
commander.navigateHistory("up");
|
||||
return;
|
||||
}
|
||||
if (e.key === "ArrowDown" && !commander.showCompletions()) {
|
||||
e.preventDefault();
|
||||
commander.navigateHistory("down");
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
commander.handleCommand();
|
||||
|
|
|
|||
|
|
@ -10,14 +10,29 @@ export interface MdCommanderProps {
|
|||
export interface MdCommanderCommand {
|
||||
command: string;
|
||||
description?: string;
|
||||
parameters?: MdCommanderParameter[];
|
||||
options?: Record<string, MdCommanderOption>;
|
||||
handler?: (args: Record<string, any>) => {
|
||||
handler?: (args: {
|
||||
params: Record<string, string>;
|
||||
options: Record<string, string>;
|
||||
}) => {
|
||||
message: string;
|
||||
type?: "success" | "error" | "info" | "warning";
|
||||
isHtml?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MdCommanderParameter {
|
||||
name: string;
|
||||
description?: string;
|
||||
type: MdCommanderOptionType;
|
||||
required?: boolean;
|
||||
default?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
values?: string[];
|
||||
}
|
||||
|
||||
export type MdCommanderOptionType =
|
||||
| "string"
|
||||
| "number"
|
||||
|
|
|
|||
Loading…
Reference in New Issue