183 lines
5.2 KiB
TypeScript
183 lines
5.2 KiB
TypeScript
import type { MdCommanderCommandMap, CompletionItem } from "../types";
|
|
|
|
export interface ParsedCommand {
|
|
command?: string;
|
|
params: Record<string, string>;
|
|
options: Record<string, string>;
|
|
incompleteParam?: { index: number; value: string };
|
|
}
|
|
|
|
/**
|
|
* 解析命令行输入
|
|
*/
|
|
export function parseInput(input: string, commands?: MdCommanderCommandMap): ParsedCommand {
|
|
const result: ParsedCommand = { params: {}, options: {} };
|
|
const trimmed = input.trim();
|
|
if (!trimmed) return result;
|
|
|
|
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("--")) {
|
|
const eqIndex = part.indexOf("=");
|
|
if (eqIndex !== -1) {
|
|
result.options[part.slice(2, eqIndex)] = part.slice(eqIndex + 1);
|
|
} else {
|
|
const key = part.slice(2);
|
|
if (i + 1 < parts.length && !parts[i + 1].startsWith("-")) {
|
|
result.options[key] = parts[i + 1];
|
|
i++;
|
|
}
|
|
}
|
|
} else if (part.startsWith("-") && part.length === 2) {
|
|
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) {
|
|
result.params[paramDefs[paramIndex].name] = part;
|
|
paramIndex++;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
// 检查是否有未完成的位置参数
|
|
if (paramIndex < paramDefs.length) {
|
|
const lastPart = parts[parts.length - 1];
|
|
if (lastPart && !lastPart.startsWith("-") && parts.length > 1) {
|
|
result.incompleteParam = { index: paramIndex, value: lastPart };
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 获取自动补全建议
|
|
*/
|
|
export function getCompletions(input: string, commands: MdCommanderCommandMap): CompletionItem[] {
|
|
const trimmed = input.trim();
|
|
|
|
// 空输入时返回所有命令
|
|
if (!trimmed || /^\s*$/.test(trimmed)) {
|
|
return Object.values(commands).map((cmd) => ({
|
|
label: cmd.command,
|
|
type: "command",
|
|
description: cmd.description,
|
|
insertText: cmd.command,
|
|
}));
|
|
}
|
|
|
|
const parsed = parseInput(trimmed, commands);
|
|
|
|
// 命令补全
|
|
if (!parsed.command || !commands[parsed.command]) {
|
|
return Object.values(commands)
|
|
.filter((cmd) => cmd.command.startsWith(parsed.command || ""))
|
|
.map((cmd) => ({
|
|
label: cmd.command,
|
|
type: "command",
|
|
description: cmd.description,
|
|
insertText: cmd.command,
|
|
}));
|
|
}
|
|
|
|
const cmd = commands[parsed.command];
|
|
if (!cmd) return [];
|
|
|
|
const paramDefs = cmd.parameters || [];
|
|
const usedParams = Object.keys(parsed.params);
|
|
|
|
// 判断是否正在输入最后一个参数
|
|
const isTypingLastParam = paramDefs.length === usedParams.length &&
|
|
trimmed.length > 0 &&
|
|
!trimmed.endsWith(" ") &&
|
|
!trimmed.split(/\s+/)[trimmed.split(/\s+/).length - 1].startsWith("-");
|
|
|
|
// 参数补全
|
|
if (paramDefs.length > usedParams.length || parsed.incompleteParam || isTypingLastParam) {
|
|
let paramIndex = parsed.incompleteParam?.index ?? (isTypingLastParam ? paramDefs.length - 1 : usedParams.length);
|
|
const paramDef = paramDefs[paramIndex];
|
|
if (!paramDef) return [];
|
|
|
|
let currentValue = parsed.incompleteParam?.value || "";
|
|
if (isTypingLastParam && !parsed.incompleteParam) {
|
|
const parts = trimmed.split(/\s+/);
|
|
currentValue = parts[parts.length - 1] || "";
|
|
}
|
|
|
|
// 模板补全
|
|
if (paramDef.templates) {
|
|
return paramDef.templates
|
|
.filter((t) => t.label.toLowerCase().includes(currentValue.toLowerCase()))
|
|
.map((t) => ({
|
|
label: t.label,
|
|
type: "value",
|
|
description: t.description,
|
|
insertText: t.insertText,
|
|
}));
|
|
}
|
|
|
|
// 枚举补全
|
|
if (paramDef.type === "enum" && paramDef.values) {
|
|
return paramDef.values
|
|
.filter((v) => v.startsWith(currentValue))
|
|
.map((v) => ({
|
|
label: v,
|
|
type: "value",
|
|
description: paramDef.description,
|
|
insertText: v,
|
|
}));
|
|
}
|
|
|
|
// 其他类型显示提示
|
|
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)
|
|
.filter((opt) => !usedOptions.includes(opt.option))
|
|
.map((opt) => ({
|
|
label: `--${opt.option}`,
|
|
type: "option",
|
|
description: opt.description,
|
|
insertText: `--${opt.option}=${opt.type === "boolean" ? "" : ""}`,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* 根据结果类型获取 CSS 类名
|
|
*/
|
|
export function getResultClass(type?: "success" | "error" | "warning" | "info"): string {
|
|
switch (type) {
|
|
case "success": return "text-green-600";
|
|
case "error": return "text-red-600";
|
|
case "warning": return "text-yellow-600";
|
|
default: return "text-blue-600";
|
|
}
|
|
}
|