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