diff --git a/xP/public/xP.js b/xP/public/xP.js index e18d03b..b62d6c2 100644 --- a/xP/public/xP.js +++ b/xP/public/xP.js @@ -197,7 +197,11 @@ rpc.addEventListener('Ping', event => { rpc.addEventListener('BufferUpdate', event => { let e = event.detail, b = buffers.get(e.bufferName) if (b === undefined) { - buffers.set(e.bufferName, (b = {lines: []})) + buffers.set(e.bufferName, (b = { + lines: [], + history: [], + historyAt: 0, + })) bufferResetStats(b) } b.hideUnimportant = e.hideUnimportant @@ -247,6 +251,9 @@ rpc.addEventListener('BufferActivate', event => { old.inputStart = textarea.selectionStart old.inputEnd = textarea.selectionEnd old.inputDirection = textarea.selectionDirection + // Note that we effectively overwrite the newest line + // with the current textarea contents, and jump there. + old.historyAt = old.history.length } textarea.value = '' @@ -324,6 +331,15 @@ for (let i = 0; i < 24; i++) { // ---- UI --------------------------------------------------------------------- +// On macOS, the Alt/Option key transforms characters, which basically breaks +// all event.altKey shortcuts, so require combining them with Control as well +// on that system. +function hasShortcutModifiers(event) { + // This method of detection only works with Blink browsers, as of writing. + return event.altKey && !event.metaKey && + (navigator.userAgentData?.platform === 'macOS') === event.ctrlKey +} + let linkRE = [ /https?:\/\//, /([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/, @@ -584,27 +600,71 @@ let Input = { bufferName: bufferCurrent, text: textarea.value, }) + + // b.history[b.history.length] is virtual, and is represented + // either by textarea contents when it's currently being edited, + // or by b.input in all other cases. + let b = buffers.get(bufferCurrent) + b.history.push(textarea.value) + b.historyAt = b.history.length textarea.value = '' return true }, + previous: textarea => { + let b = buffers.get(bufferCurrent) + if (b === undefined) + return false + + // TODO: Ding otherwise. + if (b.historyAt > 0) { + if (b.historyAt == b.history.length) + b.input = textarea.value + textarea.value = b.history[--b.historyAt] + } + return true + }, + + next: textarea => { + let b = buffers.get(bufferCurrent) + if (b === undefined) + return false + + // TODO: Ding otherwise. + if (b.historyAt < b.history.length) { + if (++b.historyAt == b.history.length) + textarea.value = b.input + else + textarea.value = b.history[b.historyAt] + } + return true + }, + onKeyDown: event => { // TODO: And perhaps on other actions, too. rpc.send({command: 'Active'}) let textarea = event.currentTarget let handled = false - switch (event.keyCode) { - case 9: - if (!event.ctrlKey && !event.metaKey && !event.altKey && - !event.shiftKey) + if (hasShortcutModifiers(event)) { + switch (event.key) { + case 'p': + handled = Input.previous(textarea) + break + case 'n': + handled = Input.next(textarea) + break + } + } else if (!event.altKey && !event.ctrlKey && !event.metaKey && + !event.shiftKey) { + switch (event.keyCode) { + case 9: handled = Input.complete(textarea) - break - case 13: - if (!event.ctrlKey && !event.metaKey && !event.altKey && - !event.shiftKey) + break + case 13: handled = Input.submit(textarea) - break + break + } } if (handled) event.preventDefault() @@ -636,28 +696,34 @@ let Main = { window.addEventListener('load', () => m.mount(document.body, Main)) document.addEventListener('keydown', event => { - if (rpc.ws == undefined || event.ctrlKey || event.metaKey) + if (rpc.ws == undefined || !hasShortcutModifiers(event)) return - if (event.altKey && event.key == 'Tab') { + switch (event.key) { + case 'Tab': if (bufferLast !== undefined) bufferActivate(bufferLast) - } else if (event.altKey && event.key == 'h') { + break + case 'h': bufferToggleLog() - } else if (event.altKey && event.key == 'a') { + break + case 'a': for (const [name, b] of buffers) if (name !== bufferCurrent && b.newMessages) { bufferActivate(name) break } - } else if (event.altKey && event.key == '!') { + break + case '!': for (const [name, b] of buffers) if (name !== bufferCurrent && b.highlighted) { bufferActivate(name) break } - } else + break + default: return + } event.preventDefault() })