diff --git a/lib/CommandDispatcher.js b/lib/CommandDispatcher.js index 141157b..c6a37f2 100644 --- a/lib/CommandDispatcher.js +++ b/lib/CommandDispatcher.js @@ -147,7 +147,7 @@ class CommandDispatcher { if (child.redirect != null) { const childContext = new CommandContextBuilder(this, source, child.redirect, reader.cursor) const parse = this.#parseNodes(child.redirect, reader, childContext) - context.withChild(parse.context()) + context.withChild(parse.context) return new ParseResults(context, parse.reader, parse.exceptions) } else { const parse = this.#parseNodes(child, reader, context) @@ -176,6 +176,84 @@ class CommandDispatcher { return new ParseResults(contextSoFar, originalReader, errors == null ? new Map() : errors) } + getAllUsage (node, source, restricted) { + const result = [] + this.#getAllUsage(node, source, result, '', restricted) + return result + } + + #getAllUsage (node, source, result, prefix, restricted) { + if (restricted && !node.canUse(source)) return + + if (node.command != null) result.push(prefix) + + if (node.redirect != null) { + const redirect = node.redirect === this.root ? '...' : '-> ' + node.redirect.getUsageText() + result.push(prefix.length === 0 ? node.getUsageText() + CommandDispatcher.ARGUMENT_SEPARATOR + redirect : prefix + CommandDispatcher.ARGUMENT_SEPARATOR + redirect) + } else if (node.getChildren().length !== 0) { + for (const child of node.getChildren()) { + this.#getAllUsage(child, source, result, prefix.length === 0 ? child.getUsageText() : prefix + CommandDispatcher.ARGUMENT_SEPARATOR + child.getUsageText(), restricted) + } + } + } + + getSmartUsage (node, source) { + const result = new Map() + + const optional = node.command != null + for (const child of node.getChildren()) { + const usage = this.#getSmartUsage(child, source, optional, false) + if (usage != null) result.set(child, usage) + } + return result + } + + #getSmartUsage (node, source, optional, deep) { + if (!node.canUse(source)) return + + const self = optional ? USAGE_OPTIONAL_OPEN + node.getUsageText() + USAGE_OPTIONAL_CLOSE : node.getUsageText() + const childOptional = node.command != null + const open = childOptional ? USAGE_OPTIONAL_OPEN : USAGE_REQUIRED_OPEN + const close = childOptional ? USAGE_OPTIONAL_CLOSE : USAGE_REQUIRED_CLOSE + + if (!deep) { + if (node.redirect != null) { + const redirect = node.redirect === this.root ? '...' : '-> ' + node.redirect.getUsageText() + return self + CommandDispatcher.ARGUMENT_SEPARATOR + redirect + } else { + const children = node.getChildren().filter(c => c.canUse(source)) + if (children.length === 1) { + const usage = this.#getSmartUsage(children[0], source, childOptional, childOptional) + if (usage != null) return self + CommandDispatcher.ARGUMENT_SEPARATOR + usage + } else if (children.length > 1) { + const childUsage = new Set() + for (const child of children) { + const usage = this.#getSmartUsage(child, source, childOptional, true) + if (usage != null) childUsage.add(usage) + } + if (childUsage.length === 1) { + const usage = childUsage.values().next().value + return self + CommandDispatcher.ARGUMENT_SEPARATOR + (childOptional ? USAGE_OPTIONAL_OPEN + usage + USAGE_OPTIONAL_CLOSE : usage) + } else if (childUsage.length > 1) { + let string = open + let count = 0 + for (const child of children) { + if (count > 0) string += USAGE_OR + string += child.getUsageText() + count++ + } + if (count > 0) { + string += close + return self + CommandDispatcher.ARGUMENT_SEPARATOR + string + } + } + } + } + } + + return self + } + async getCompletionSuggestions (parse, cursor = parse.reader.getTotalLength()) { const context = parse.context diff --git a/lib/context/CommandContext.js b/lib/context/CommandContext.js index c652393..873ad8e 100644 --- a/lib/context/CommandContext.js +++ b/lib/context/CommandContext.js @@ -44,6 +44,10 @@ class CommandContext { return argument.result } + + hasNodes () { + return this.nodes.length !== 0 + } } module.exports = CommandContext