display help, description and add tab autocompletion

This commit is contained in:
zokutyou2@gmail.com 2021-07-14 21:35:44 +09:00
parent 9513a6d12c
commit 79f4cdd25c
3 changed files with 54 additions and 23 deletions

View File

@ -455,10 +455,14 @@ class Commands(RequestHandler):
def get(self): def get(self):
commands = {} commands = {}
for (name, command) in self.master.commands.commands.items(): for (name, command) in self.master.commands.commands.items():
commands[name] = [] commands[name] = {
"args": [],
"signature_help": command.signature_help(),
"description": command.help
}
for parameter in command.parameters: for parameter in command.parameters:
commands[name].append({"name": parameter.name}) commands[name]["args"].append(parameter.name)
self.write({"commands": commands}) self.write({"commands": commands, "history": self.master.commands.execute("commands.history.get")})
def post(self): def post(self):
result = self.master.commands.execute(self.json["command"]) result = self.master.commands.execute(self.json["command"])

View File

@ -30,6 +30,11 @@ exports[`CommandBar Component 1`] = `
value="" value=""
/> />
</div> </div>
<div
class="available-commands popover bottom"
>
Available Commands: []
</div>
</div> </div>
</DocumentFragment> </DocumentFragment>
`; `;

View File

@ -3,7 +3,7 @@ import classnames from 'classnames'
import { Key, fetchApi } from '../utils' import { Key, fetchApi } from '../utils'
import Filt from '../filt/command' import Filt from '../filt/command'
export function AvailableCommands({input, commands}) { function getAvailableCommands(commands, input = "") {
if (!commands) return null if (!commands) return null
let availableCommands = [] let availableCommands = []
for (const [command, args] of Object.entries(commands)) { for (const [command, args] of Object.entries(commands)) {
@ -11,10 +11,10 @@ export function AvailableCommands({input, commands}) {
availableCommands.push(command) availableCommands.push(command)
} }
} }
return <div className="available-commands popover bottom">Available Commands: {JSON.stringify(availableCommands)}</div> return availableCommands
} }
export function ArgumentSuggestion({nextArgs, currentArg}){ export function CommandHelp({nextArgs, currentArg, help, description}){
let results = [] let results = []
for (let i = 0; i < nextArgs.length; i++) { for (let i = 0; i < nextArgs.length; i++) {
if (i==currentArg) { if (i==currentArg) {
@ -26,37 +26,49 @@ export function ArgumentSuggestion({nextArgs, currentArg}){
return (<div className="argument-suggestion popover top"> return (<div className="argument-suggestion popover top">
<div className="arrow"/> <div className="arrow"/>
<div className="popover-content"> <div className="popover-content">
Argument suggestion: {results} <div><strong>Argument suggestion:</strong> {results}</div>
{ help.includes("->") && <div><strong>Signature help: </strong>{help}</div>}
{ description && <div># {description}</div>}
</div> </div>
</div>) </div>)
} }
export default function CommandBar() { export default function CommandBar() {
const [input, setInput] = useState("") const [input, setInput] = useState("")
const [command, setCommand] = useState("") const [originalInput, setOriginalInput] = useState("")
const [results, setResults] = useState([]) const [currentCompletion, setCurrentCompletion] = useState(0)
const [history, setHistory] = useState([])
const [currentPos, setCurrentPos] = useState(0) const [availableCommands, setAvailableCommands] = useState([])
const [allCommands, setAllCommands] = useState({}) const [allCommands, setAllCommands] = useState({})
const [nextArgs, setNextArgs] = useState([]) const [nextArgs, setNextArgs] = useState([])
const [currentArg, setCurrentArg] = useState(0) const [currentArg, setCurrentArg] = useState(0)
const [commandHelp, setCommandHelp] = useState("") const [signatureHelp, setSignatureHelp] = useState("")
const [description, setDescription] = useState("")
const [results, setResults] = useState([])
const [history, setHistory] = useState([])
const [currentPos, setCurrentPos] = useState(0)
useEffect(() => { useEffect(() => {
fetchApi('/commands', { method: 'GET' }) fetchApi('/commands', { method: 'GET' })
.then(response => response.json()) .then(response => response.json())
.then(data => setAllCommands(data)) .then(data => {
setAllCommands(data)
setAvailableCommands(getAvailableCommands(data["commands"]))
})
}, []) }, [])
const parseCommand = (input) => { const parseCommand = (originalInput, input) => {
const parts = Filt.parse(input) const parts = Filt.parse(input)
if (allCommands["commands"].hasOwnProperty(parts[0])){ const originalParts = Filt.parse(originalInput)
setCommand(parts[0]) const commands = allCommands["commands"]
} else {
setCommand("")
}
const nextArgs = allCommands["commands"][parts[0]]?.map(arg => arg.name) setSignatureHelp(commands[parts[0]]?.signature_help)
setDescription(commands[parts[0]]?.description)
setAvailableCommands(getAvailableCommands(commands, originalParts[0]))
const nextArgs = allCommands["commands"][parts[0]]?.args
if (nextArgs) { if (nextArgs) {
setNextArgs([parts[0], ...nextArgs]) setNextArgs([parts[0], ...nextArgs])
@ -66,6 +78,7 @@ export default function CommandBar() {
const onChange = (e) => { const onChange = (e) => {
setInput(e.target.value) setInput(e.target.value)
setOriginalInput(e.target.value)
} }
const onKeyDown = (e) => { const onKeyDown = (e) => {
@ -91,25 +104,34 @@ export default function CommandBar() {
}) })
setInput("") setInput("")
setOriginalInput("")
} }
if (e.keyCode === Key.UP) { if (e.keyCode === Key.UP) {
if (currentPos > 0) { if (currentPos > 0) {
setInput(history[currentPos - 1]) setInput(history[currentPos - 1])
setOriginalInput(history[currentPos -1])
setCurrentPos(currentPos - 1) setCurrentPos(currentPos - 1)
} }
} }
if (e.keyCode === Key.DOWN) { if (e.keyCode === Key.DOWN) {
setInput(history[currentPos]) setInput(history[currentPos])
setOriginalInput(history[currentPos])
if (currentPos < history.length -1) { if (currentPos < history.length -1) {
setCurrentPos(currentPos + 1) setCurrentPos(currentPos + 1)
} }
} }
if (e.keyCode === Key.TAB) {
setInput(availableCommands[currentCompletion])
setCurrentCompletion((currentCompletion + 1) % availableCommands.length)
e.preventDefault()
}
e.stopPropagation() e.stopPropagation()
} }
const onKeyUp = (e) => { const onKeyUp = (e) => {
if (input == "") return if (input == "") return
parseCommand(input) console.log("keyup event")
parseCommand(originalInput, input)
e.stopPropagation() e.stopPropagation()
} }
@ -125,7 +147,7 @@ export default function CommandBar() {
</div> </div>
))} ))}
</div> </div>
{ nextArgs.length > 0 && <ArgumentSuggestion nextArgs={nextArgs} currentArg={currentArg} /> } { signatureHelp && <CommandHelp nextArgs={nextArgs} currentArg={currentArg} help={signatureHelp} description={description}/> }
<div className={classnames('command-input input-group')}> <div className={classnames('command-input input-group')}>
<span className="input-group-addon"> <span className="input-group-addon">
<i className={'fa fa-fw fa-terminal'}/> <i className={'fa fa-fw fa-terminal'}/>
@ -140,7 +162,7 @@ export default function CommandBar() {
onKeyUp={onKeyUp} onKeyUp={onKeyUp}
/> />
</div> </div>
{ !command && <AvailableCommands input={input} commands={allCommands["commands"]} /> } <div className="available-commands popover bottom">Available Commands: {JSON.stringify(availableCommands)}</div>
</div> </div>
) )
} }