import { ref } from 'vue' import type { SimpleCommand, Uri } from './Config' import { useRouter } from 'vitepress' export default function useTerminal(inputEl: HTMLTextAreaElement, commands: SimpleCommand[]) { const prompt = '\n$> ' const footerLinks = ref([]) const router = useRouter() console.log('router', router) function moveCursorToEnd() { const pos = inputEl.value.length // allow text selection if (inputEl.selectionStart !== inputEl.selectionEnd) { console.debug('allowing text selection', inputEl.selectionStart, inputEl.selectionEnd) return } inputEl.setSelectionRange(pos, pos) } function setFocus() { inputEl.focus() } function addText(text: string, addPrompt=true) { const line = addPrompt ? text + prompt : text inputEl.value = inputEl.value + line inputEl.scrollTop = inputEl.scrollTopMax } function addLine(line: string) { addText('\n'+line) } function clear() { footerLinks.value = [] inputEl.value = '' addText('') } type SYS_OUT = 'NOT_FOUND' | 'USAGE' | 'INFO' | '404' const SHELL = 'k0rSH' const INFO = 'k0rSH v0.1: the k0r SHell, fiddled together by k0r -- https://k0r.in' const PAD = 16 const USAGE = [ ...commands.map(cmd => { const command = `${(cmd.command+':').padEnd(PAD)}` const help = `${cmd.help ?? 'no helptext provided'}` const aliases = cmd.aliases ? ` (aliases: ${cmd.aliases?.join(', ')})` : '' return `${command}${help}${aliases}` }), `${'help:'.padEnd(PAD)}This help text. (aliases: usage)`, `${'version:'.padEnd(PAD)}Print version information.`, `${'clear:'.padEnd(PAD)}Clear the screen.`, ].join('\n') function systemOutput(output: SYS_OUT, arg = '') { switch (output) { case 'NOT_FOUND': console.debug('command not found') addLine(`${SHELL}: ${arg}: command not found...`) break case 'USAGE': console.log('help is underway') addLine(`${SHELL} - available commands:\n\n${USAGE}`) break case 'INFO': console.log('explaining myself') addLine(`${SHELL}: ${INFO}`) break case '404': console.log('page not found', arg) addLine(`${SHELL}: ${arg}: this page does not exist`) break } } function cursorAtPrompt() { return inputEl.value.endsWith(prompt) } function setFooter(uris: Uri[]) { footerLinks.value = uris } /// returns current command written in the command line /// in the format: [command, arg1, arg2, ..., argN] function getCurrentCommand() { const value = inputEl.value const start = value.lastIndexOf(prompt) + prompt.length const end = value.length return value.slice(start, end).trim().split(' ') } function execUserCommand(cmd: string) { const userCommand = commands.find(c => { const commandMatch = c.command === cmd const aliasesMatch = c.aliases.includes(cmd) return commandMatch || aliasesMatch }) if (!userCommand) return systemOutput('NOT_FOUND', cmd) addLine(userCommand.message) setFooter(userCommand.uris) } function listPages() { addLine('TODO: list pages') } async function openPage(page: string) { await router.go(page) } function handleCurrentCommand() { const [cmd, ...args] = getCurrentCommand() if (!cmd) { addText('') return } switch (cmd) { case 'help': case 'usage': systemOutput('USAGE') break case 'version': systemOutput('INFO') break case 'clear': clear() break case 'ls': case 'list': listPages() break case 'go': case 'open': addText('\n', false) if (!args.length) addText('USAGE: go page_name') else router.go(args[0]) break default: execUserCommand(cmd) } } function handleInput(ev) { switch (ev.key) { case 'Enter': handleCurrentCommand() ev.preventDefault() break case 'Backspace': if (cursorAtPrompt()) ev.preventDefault() break } } inputEl.addEventListener('focus', () => moveCursorToEnd()) inputEl.addEventListener('blur', () => inputEl.focus()) inputEl.addEventListener('click', () => moveCursorToEnd()) inputEl.addEventListener('keydown', handleInput) return { addText, addLine, setFocus, clear, footerLinks } }