From f2d8de3ab92321419005b6f2bd6834dacda4cdb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Fri, 23 Sep 2022 09:37:23 +0200 Subject: [PATCH] xP: support adding formatting from keyboard Just like in xC, only with some indication. --- xC.c | 9 +++++- xP/public/xP.js | 82 ++++++++++++++++++++++++++++++++++--------------- 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/xC.c b/xC.c index d01c780..1b90964 100644 --- a/xC.c +++ b/xC.c @@ -15010,21 +15010,28 @@ process_formatting_escape (const struct pollfd *fd, struct app_context *ctx) if (buf->len != 1) goto error; + + // Letters mostly taken from their caret escapes + HTML element names. + // Additionally, 'm' stands for mono, 'x' for cross, 'r' for reset. switch (buf->str[0]) { case 'b' ^ 96: case 'b': CALL_ (ctx->input, insert, "\x02"); break; case 'c': CALL_ (ctx->input, insert, "\x03"); break; + case 'q': + case 'm': CALL_ (ctx->input, insert, "\x11"); break; + case 'v': CALL_ (ctx->input, insert, "\x16"); break; case 'i' ^ 96: case 'i': case ']': CALL_ (ctx->input, insert, "\x1d"); break; + case 's' ^ 96: + case 's': case 'x' ^ 96: case 'x': case '^': CALL_ (ctx->input, insert, "\x1e"); break; case 'u' ^ 96: case 'u': case '_': CALL_ (ctx->input, insert, "\x1f"); break; - case 'v': CALL_ (ctx->input, insert, "\x16"); break; case 'r': case 'o': CALL_ (ctx->input, insert, "\x0f"); break; diff --git a/xP/public/xP.js b/xP/public/xP.js index ae6dd28..3266063 100644 --- a/xP/public/xP.js +++ b/xP/public/xP.js @@ -687,34 +687,25 @@ let BufferContainer = { } let Toolbar = { - insert: formatting => { + format: formatting => { let textarea = document.getElementById('input') - if (textarea === null) - return - - const [start, end] = [textarea.selectionStart, textarea.selectionEnd] - if (start === end) { - textarea.setRangeText(formatting) - textarea.setSelectionRange( - start + formatting.length, end + formatting.length) - } else { - textarea.setRangeText( - formatting + textarea.value.substr(start, end) + formatting) - } - textarea.focus() + if (textarea !== null) + Input.format(textarea, formatting) }, view: vnode => { - let indicator = undefined + let indicators = [] if (bufferLog === undefined && !bufferAutoscroll) - indicator = m('.indicator', {}, '⇩') + indicators.push(m('.indicator', {}, '⇩')) + if (Input.formatting) + indicators.push(m('.indicator', {}, '#')) return m('.toolbar', {}, [ - indicator, - m('button', {onclick: event => Toolbar.insert('\u0002')}, + indicators, + m('button', {onclick: event => Toolbar.format('\u0002')}, m('b', {}, 'B')), - m('button', {onclick: event => Toolbar.insert('\u001D')}, + m('button', {onclick: event => Toolbar.format('\u001D')}, m('i', {}, 'I')), - m('button', {onclick: event => Toolbar.insert('\u001F')}, + m('button', {onclick: event => Toolbar.format('\u001F')}, m('u', {}, 'U')), m('button', {onclick: event => bufferToggleLog()}, bufferLog === undefined ? 'Log' : 'Hide log'), @@ -924,6 +915,21 @@ let Input = { return true }, + formatting: false, + + format: (textarea, formatting) => { + const [start, end] = [textarea.selectionStart, textarea.selectionEnd] + if (start === end) { + textarea.setRangeText(formatting) + textarea.setSelectionRange( + start + formatting.length, end + formatting.length) + } else { + textarea.setRangeText( + formatting + textarea.value.substr(start, end) + formatting) + } + textarea.focus() + }, + onKeyDown: event => { // TODO: And perhaps on other actions, too. rpc.send({command: 'Active'}) @@ -935,7 +941,29 @@ let Input = { let textarea = event.currentTarget let handled = false let success = true - if (hasShortcutModifiers(event)) { + if (Input.formatting) { + Input.formatting = false + + // Like process_formatting_escape() within xC. + handled = true + switch (event.key) { + case 'b': Input.format(textarea, '\u0002'); break + case 'c': Input.format(textarea, '\u0003'); break + case 'q': + case 'm': Input.format(textarea, '\u0011'); break + case 'v': Input.format(textarea, '\u0016'); break + case 'i': + case ']': Input.format(textarea, '\u001D'); break + case 's': + case 'x': + case '^': Input.format(textarea, '\u001E'); break + case 'u': + case '_': Input.format(textarea, '\u001F'); break + case 'r': + case 'o': Input.format(textarea, '\u000F'); break + default: success = false + } + } else if (hasShortcutModifiers(event)) { handled = true switch (event.key) { case 'b': success = Input.backward(textarea); break @@ -947,6 +975,7 @@ let Input = { case '>': success = Input.last(b, textarea); break case 'p': success = Input.previous(b, textarea); break case 'n': success = Input.next(b, textarea); break + case 'm': success = Input.formatting = true; break default: handled = false } } else if (!event.altKey && !event.ctrlKey && !event.metaKey && @@ -964,15 +993,20 @@ let Input = { event.preventDefault() }, + onStateChange: event => { + Completions.reset() + Input.formatting = false + }, + view: vnode => { return m('textarea#input', { rows: 1, onkeydown: Input.onKeyDown, - oninput: event => Completions.reset(), + oninput: Input.onStateChange, // Sadly only supported in Firefox as of writing. - onselectionchange: event => Completions.reset(), + onselectionchange: Input.onStateChange, // The list of completions is scrollable without receiving focus. - onblur: event => Completions.reset(), + onblur: Input.onStateChange, }) }, }