Compare commits

...

3 Commits

Author SHA1 Message Date
f2d8de3ab9
xP: support adding formatting from keyboard
Just like in xC, only with some indication.
2022-09-23 09:42:24 +02:00
67d52a2d89
xP: fix up link detection
Allow balanced parantheses at the end of a link.
2022-09-23 09:42:23 +02:00
ef3d1cc409
xP: add formatting buttons
And fix autoscroll autoenabler, as well as toolbar padding.

Only add the basic toggles, which should be well supported.
2022-09-23 09:41:29 +02:00
3 changed files with 80 additions and 9 deletions

9
xC.c
View File

@ -15010,21 +15010,28 @@ process_formatting_escape (const struct pollfd *fd, struct app_context *ctx)
if (buf->len != 1) if (buf->len != 1)
goto error; 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]) switch (buf->str[0])
{ {
case 'b' ^ 96: case 'b' ^ 96:
case 'b': CALL_ (ctx->input, insert, "\x02"); break; case 'b': CALL_ (ctx->input, insert, "\x02"); break;
case 'c': CALL_ (ctx->input, insert, "\x03"); 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' ^ 96:
case 'i': case 'i':
case ']': CALL_ (ctx->input, insert, "\x1d"); break; case ']': CALL_ (ctx->input, insert, "\x1d"); break;
case 's' ^ 96:
case 's':
case 'x' ^ 96: case 'x' ^ 96:
case 'x': case 'x':
case '^': CALL_ (ctx->input, insert, "\x1e"); break; case '^': CALL_ (ctx->input, insert, "\x1e"); break;
case 'u' ^ 96: case 'u' ^ 96:
case 'u': case 'u':
case '_': CALL_ (ctx->input, insert, "\x1f"); break; case '_': CALL_ (ctx->input, insert, "\x1f"); break;
case 'v': CALL_ (ctx->input, insert, "\x16"); break;
case 'r': case 'r':
case 'o': CALL_ (ctx->input, insert, "\x0f"); break; case 'o': CALL_ (ctx->input, insert, "\x0f"); break;

View File

@ -69,12 +69,16 @@ body {
.toolbar { .toolbar {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
column-gap: .3em; margin-right: -.3em;
}
.indicator {
margin: 0 .3em;
} }
button { button {
font: inherit; font: inherit;
background: transparent; background: transparent;
border: 1px solid transparent; border: 1px solid transparent;
padding: 0 .3em;
} }
button:focus { button:focus {
border: 1px dashed #000; border: 1px dashed #000;

View File

@ -435,7 +435,7 @@ for (let i = 0; i < 24; i++) {
let linkRE = [ let linkRE = [
/https?:\/\//, /https?:\/\//,
/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/, /([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/,
/[^\[\](){}<>"'\s,.:]/, /([^\[\](){}<>"'\s,.:]|\([^\[\](){}<>"'\s]*\))/,
].map(r => r.source).join('') ].map(r => r.source).join('')
let BufferList = { let BufferList = {
@ -631,7 +631,7 @@ let Buffer = {
return m('.buffer', {onscroll: event => { return m('.buffer', {onscroll: event => {
const dom = event.target const dom = event.target
bufferAutoscroll = bufferAutoscroll =
dom.scrollTop + dom.clientHeight + 0.5 >= dom.scrollHeight dom.scrollTop + dom.clientHeight + 1 >= dom.scrollHeight
}}, lines) }}, lines)
}, },
} }
@ -687,9 +687,26 @@ let BufferContainer = {
} }
let Toolbar = { let Toolbar = {
format: formatting => {
let textarea = document.getElementById('input')
if (textarea !== null)
Input.format(textarea, formatting)
},
view: vnode => { view: vnode => {
let indicators = []
if (bufferLog === undefined && !bufferAutoscroll)
indicators.push(m('.indicator', {}, '⇩'))
if (Input.formatting)
indicators.push(m('.indicator', {}, '#'))
return m('.toolbar', {}, [ return m('.toolbar', {}, [
bufferLog === undefined && !bufferAutoscroll ? '⇩' : undefined, indicators,
m('button', {onclick: event => Toolbar.format('\u0002')},
m('b', {}, 'B')),
m('button', {onclick: event => Toolbar.format('\u001D')},
m('i', {}, 'I')),
m('button', {onclick: event => Toolbar.format('\u001F')},
m('u', {}, 'U')),
m('button', {onclick: event => bufferToggleLog()}, m('button', {onclick: event => bufferToggleLog()},
bufferLog === undefined ? 'Log' : 'Hide log'), bufferLog === undefined ? 'Log' : 'Hide log'),
]) ])
@ -898,6 +915,21 @@ let Input = {
return true 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 => { onKeyDown: event => {
// TODO: And perhaps on other actions, too. // TODO: And perhaps on other actions, too.
rpc.send({command: 'Active'}) rpc.send({command: 'Active'})
@ -909,7 +941,29 @@ let Input = {
let textarea = event.currentTarget let textarea = event.currentTarget
let handled = false let handled = false
let success = true 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 handled = true
switch (event.key) { switch (event.key) {
case 'b': success = Input.backward(textarea); break case 'b': success = Input.backward(textarea); break
@ -921,6 +975,7 @@ let Input = {
case '>': success = Input.last(b, textarea); break case '>': success = Input.last(b, textarea); break
case 'p': success = Input.previous(b, textarea); break case 'p': success = Input.previous(b, textarea); break
case 'n': success = Input.next(b, textarea); break case 'n': success = Input.next(b, textarea); break
case 'm': success = Input.formatting = true; break
default: handled = false default: handled = false
} }
} else if (!event.altKey && !event.ctrlKey && !event.metaKey && } else if (!event.altKey && !event.ctrlKey && !event.metaKey &&
@ -938,15 +993,20 @@ let Input = {
event.preventDefault() event.preventDefault()
}, },
onStateChange: event => {
Completions.reset()
Input.formatting = false
},
view: vnode => { view: vnode => {
return m('textarea#input', { return m('textarea#input', {
rows: 1, rows: 1,
onkeydown: Input.onKeyDown, onkeydown: Input.onKeyDown,
oninput: event => Completions.reset(), oninput: Input.onStateChange,
// Sadly only supported in Firefox as of writing. // Sadly only supported in Firefox as of writing.
onselectionchange: event => Completions.reset(), onselectionchange: Input.onStateChange,
// The list of completions is scrollable without receiving focus. // The list of completions is scrollable without receiving focus.
onblur: event => Completions.reset(), onblur: Input.onStateChange,
}) })
}, },
} }