2022-09-05 22:34:20 +02:00
|
|
|
|
// Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
|
|
|
|
|
// SPDX-License-Identifier: 0BSD
|
2022-09-06 17:17:32 +02:00
|
|
|
|
'use strict'
|
|
|
|
|
|
|
|
|
|
// ---- RPC --------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
class RelayRpc extends EventTarget {
|
|
|
|
|
constructor(url) {
|
|
|
|
|
super()
|
|
|
|
|
this.url = url
|
|
|
|
|
this.commandSeq = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
connect() {
|
|
|
|
|
// We can't close the connection immediately, as that queues a task.
|
|
|
|
|
if (this.ws !== undefined)
|
|
|
|
|
throw "Already connecting or connected"
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
let ws = this.ws = new WebSocket(this.url)
|
|
|
|
|
ws.onopen = event => {
|
|
|
|
|
this._initialize()
|
|
|
|
|
resolve()
|
|
|
|
|
}
|
|
|
|
|
// It's going to be code 1006 with no further info.
|
|
|
|
|
ws.onclose = event => {
|
|
|
|
|
this.ws = undefined
|
2022-09-06 20:17:23 +02:00
|
|
|
|
reject()
|
2022-09-06 17:17:32 +02:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_initialize() {
|
|
|
|
|
this.ws.onopen = undefined
|
|
|
|
|
this.ws.onmessage = event => {
|
|
|
|
|
this._process(event.data)
|
|
|
|
|
}
|
|
|
|
|
this.ws.onerror = event => {
|
|
|
|
|
this.dispatchEvent(new CustomEvent('error'))
|
|
|
|
|
}
|
|
|
|
|
this.ws.onclose = event => {
|
|
|
|
|
let message = "Connection closed: " +
|
|
|
|
|
event.code + " (" + event.reason + ")"
|
|
|
|
|
for (const seq in this.promised)
|
|
|
|
|
this.promised[seq].reject(message)
|
|
|
|
|
|
|
|
|
|
this.ws = undefined
|
|
|
|
|
this.dispatchEvent(new CustomEvent('close', {
|
|
|
|
|
detail: {message, code: event.code, reason: event.reason},
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
// Now connect() can be called again.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.promised = {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_process(data) {
|
|
|
|
|
console.log(data)
|
|
|
|
|
|
|
|
|
|
if (typeof data !== 'string')
|
|
|
|
|
throw "Binary messages not supported"
|
|
|
|
|
|
|
|
|
|
let message = JSON.parse(data)
|
|
|
|
|
if (typeof message !== 'object')
|
|
|
|
|
throw "Invalid message"
|
|
|
|
|
let e = message.data
|
|
|
|
|
if (typeof e !== 'object')
|
|
|
|
|
throw "Invalid message"
|
|
|
|
|
|
|
|
|
|
switch (e.event) {
|
|
|
|
|
case 'Error':
|
|
|
|
|
if (this.promised[e.commandSeq] !== undefined)
|
|
|
|
|
this.promised[e.commandSeq].reject(e.error)
|
|
|
|
|
else
|
|
|
|
|
console.error("Unawaited error")
|
|
|
|
|
break
|
|
|
|
|
case 'Response':
|
|
|
|
|
if (this.promised[e.commandSeq] !== undefined)
|
|
|
|
|
this.promised[e.commandSeq].resolve(e.data)
|
|
|
|
|
else
|
|
|
|
|
console.error("Unawaited response")
|
|
|
|
|
break
|
|
|
|
|
default:
|
|
|
|
|
if (typeof e.event !== 'string')
|
|
|
|
|
throw "Invalid event tag"
|
|
|
|
|
|
2022-09-08 02:33:44 +02:00
|
|
|
|
this.dispatchEvent(new CustomEvent(
|
|
|
|
|
e.event, {detail: {eventSeq: message.eventSeq, ...e}}))
|
2022-09-06 17:17:32 +02:00
|
|
|
|
|
|
|
|
|
// Minor abstraction layering violation.
|
|
|
|
|
m.redraw()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete this.promised[e.commandSeq]
|
|
|
|
|
for (const seq in this.promised) {
|
|
|
|
|
// We don't particularly care about wraparound issues.
|
|
|
|
|
if (seq >= e.commandSeq)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
this.promised[seq].reject("No response")
|
|
|
|
|
delete this.promised[seq]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
send(params) {
|
|
|
|
|
if (this.ws === undefined)
|
|
|
|
|
throw "Not connected"
|
|
|
|
|
if (typeof params !== 'object')
|
|
|
|
|
throw "Method parameters must be an object"
|
|
|
|
|
|
2022-09-10 16:42:49 +02:00
|
|
|
|
// Left shifts in Javascript convert to a 32-bit signed number.
|
2022-09-06 17:17:32 +02:00
|
|
|
|
let seq = ++this.commandSeq
|
2022-09-10 16:42:49 +02:00
|
|
|
|
if ((seq << 0) != seq)
|
2022-09-06 17:17:32 +02:00
|
|
|
|
seq = this.commandSeq = 0
|
|
|
|
|
|
|
|
|
|
this.ws.send(JSON.stringify({commandSeq: seq, data: params}))
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
this.promised[seq] = {resolve, reject}
|
|
|
|
|
})
|
|
|
|
|
}
|
2022-09-06 23:37:06 +02:00
|
|
|
|
|
|
|
|
|
base64decode(str) {
|
|
|
|
|
return decodeURIComponent(atob(str).split('').map(c =>
|
|
|
|
|
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join(''))
|
|
|
|
|
}
|
2022-09-06 17:17:32 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- Event processing -------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
let rpc = new RelayRpc(proxy)
|
|
|
|
|
|
|
|
|
|
let buffers = new Map()
|
|
|
|
|
let bufferCurrent = undefined
|
2022-09-06 23:37:06 +02:00
|
|
|
|
let bufferLog = undefined
|
2022-09-07 13:52:30 +02:00
|
|
|
|
let bufferAutoscroll = true
|
2022-09-06 23:37:06 +02:00
|
|
|
|
|
2022-09-10 17:37:19 +02:00
|
|
|
|
function resetBufferStats(b) {
|
|
|
|
|
b.newMessages = 0
|
|
|
|
|
b.newUnimportantMessages = 0
|
|
|
|
|
b.highlighted = false
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-06 20:17:23 +02:00
|
|
|
|
let connecting = true
|
|
|
|
|
rpc.connect().then(result => {
|
|
|
|
|
buffers.clear()
|
|
|
|
|
bufferCurrent = undefined
|
2022-09-06 23:37:06 +02:00
|
|
|
|
bufferLog = undefined
|
2022-09-07 13:52:30 +02:00
|
|
|
|
bufferAutoscroll = true
|
2022-09-06 23:37:06 +02:00
|
|
|
|
|
2022-09-06 20:17:23 +02:00
|
|
|
|
rpc.send({command: 'Hello', version: 1})
|
|
|
|
|
connecting = false
|
|
|
|
|
m.redraw()
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
connecting = false
|
|
|
|
|
m.redraw()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
rpc.addEventListener('close', event => {
|
|
|
|
|
m.redraw()
|
|
|
|
|
})
|
2022-09-06 17:17:32 +02:00
|
|
|
|
|
2022-09-08 02:33:44 +02:00
|
|
|
|
rpc.addEventListener('Ping', event => {
|
|
|
|
|
rpc.send({command: 'PingResponse', eventSeq: event.detail.eventSeq})
|
|
|
|
|
})
|
|
|
|
|
|
2022-09-06 17:17:32 +02:00
|
|
|
|
rpc.addEventListener('BufferUpdate', event => {
|
|
|
|
|
let e = event.detail, b = buffers.get(e.bufferName)
|
|
|
|
|
if (b === undefined) {
|
2022-09-10 17:37:19 +02:00
|
|
|
|
buffers.set(e.bufferName, (b = {lines: []}))
|
|
|
|
|
resetBufferStats(b)
|
2022-09-06 17:17:32 +02:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2022-09-10 17:37:19 +02:00
|
|
|
|
rpc.addEventListener('BufferStats', event => {
|
|
|
|
|
let e = event.detail, b = buffers.get(e.bufferName)
|
|
|
|
|
if (b === undefined)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
b.newMessages = e.newMessages,
|
|
|
|
|
b.newUnimportantMessages = e.newUnimportantMessages
|
|
|
|
|
b.highlighted = e.highlighted
|
|
|
|
|
})
|
|
|
|
|
|
2022-09-06 17:17:32 +02:00
|
|
|
|
rpc.addEventListener('BufferRename', event => {
|
|
|
|
|
let e = event.detail
|
|
|
|
|
buffers.set(e.new, buffers.get(e.bufferName))
|
|
|
|
|
buffers.delete(e.bufferName)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
rpc.addEventListener('BufferRemove', event => {
|
|
|
|
|
let e = event.detail
|
|
|
|
|
buffers.delete(e.bufferName)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
rpc.addEventListener('BufferActivate', event => {
|
2022-09-06 23:17:47 +02:00
|
|
|
|
let old = buffers.get(bufferCurrent)
|
2022-09-10 17:37:19 +02:00
|
|
|
|
if (old !== undefined)
|
|
|
|
|
resetBufferStats(old)
|
2022-09-07 19:42:18 +02:00
|
|
|
|
|
|
|
|
|
let e = event.detail, b = buffers.get(e.bufferName)
|
2022-09-06 17:17:32 +02:00
|
|
|
|
bufferCurrent = e.bufferName
|
2022-09-06 23:37:06 +02:00
|
|
|
|
bufferLog = undefined
|
2022-09-07 13:52:30 +02:00
|
|
|
|
bufferAutoscroll = true
|
2022-09-06 23:17:47 +02:00
|
|
|
|
|
|
|
|
|
let textarea = document.getElementById('input')
|
|
|
|
|
if (textarea === null)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
textarea.focus()
|
|
|
|
|
if (old !== undefined)
|
|
|
|
|
old.input = textarea.value
|
|
|
|
|
|
|
|
|
|
if (b !== undefined)
|
|
|
|
|
textarea.value = b.input || ''
|
|
|
|
|
else
|
|
|
|
|
textarea.value = ''
|
2022-09-06 17:17:32 +02:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
rpc.addEventListener('BufferLine', event => {
|
2022-09-07 19:42:18 +02:00
|
|
|
|
let e = event.detail, b = buffers.get(e.bufferName), line = {...e}
|
|
|
|
|
delete line.event
|
|
|
|
|
delete line.leakToActive
|
|
|
|
|
if (b === undefined)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
// Initial sync: skip all other processing, let highlights be.
|
|
|
|
|
if (bufferCurrent === undefined) {
|
|
|
|
|
b.lines.push(line)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-10 15:48:16 +02:00
|
|
|
|
let visible = !document.hidden && bufferLog === undefined &&
|
2022-09-08 17:42:17 +02:00
|
|
|
|
(e.bufferName == bufferCurrent || e.leakToActive)
|
2022-09-07 19:42:18 +02:00
|
|
|
|
b.lines.push({...line})
|
2022-09-08 17:42:17 +02:00
|
|
|
|
if (!(visible || e.leakToActive) ||
|
|
|
|
|
b.newMessages || b.newUnimportantMessages) {
|
2022-09-07 19:42:18 +02:00
|
|
|
|
if (line.isUnimportant)
|
|
|
|
|
b.newUnimportantMessages++
|
|
|
|
|
else
|
|
|
|
|
b.newMessages++
|
|
|
|
|
}
|
2022-09-08 17:42:17 +02:00
|
|
|
|
|
2022-09-07 19:42:18 +02:00
|
|
|
|
if (e.leakToActive) {
|
|
|
|
|
let bc = buffers.get(bufferCurrent)
|
|
|
|
|
bc.lines.push({...line, leaked: true})
|
2022-09-08 17:42:17 +02:00
|
|
|
|
if (!visible || bc.newMessages || bc.newUnimportantMessages) {
|
2022-09-07 19:42:18 +02:00
|
|
|
|
if (line.isUnimportant)
|
|
|
|
|
bc.newUnimportantMessages++
|
|
|
|
|
else
|
|
|
|
|
bc.newMessages++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Find some way of highlighting the tab in a browser.
|
|
|
|
|
// TODO: Also highlight on unseen private messages, like xC does.
|
|
|
|
|
if (!visible && line.isHighlight)
|
|
|
|
|
b.highlighted = true
|
2022-09-06 17:17:32 +02:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
rpc.addEventListener('BufferClear', event => {
|
|
|
|
|
let e = event.detail, b = buffers.get(e.bufferName)
|
|
|
|
|
if (b !== undefined)
|
|
|
|
|
b.lines.length = 0
|
|
|
|
|
})
|
2022-09-05 22:34:20 +02:00
|
|
|
|
|
|
|
|
|
// --- Colours -----------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
let palette = [
|
|
|
|
|
'#000', '#800', '#080', '#880', '#008', '#808', '#088', '#ccc',
|
|
|
|
|
'#888', '#f00', '#0f0', '#ff0', '#00f', '#f0f', '#0ff', '#fff',
|
|
|
|
|
]
|
|
|
|
|
palette.length = 256
|
|
|
|
|
for (let i = 0; i < 216; i++) {
|
|
|
|
|
let r = i / 36 >> 0, g = (i / 6 >> 0) % 6, b = i % 6
|
|
|
|
|
r = !r ? '00' : (55 + 40 * r).toString(16)
|
|
|
|
|
g = !g ? '00' : (55 + 40 * g).toString(16)
|
|
|
|
|
b = !b ? '00' : (55 + 40 * b).toString(16)
|
|
|
|
|
palette[16 + i] = `#${r}${g}${b}`
|
|
|
|
|
}
|
|
|
|
|
for (let i = 0; i < 24; i++) {
|
|
|
|
|
let g = ('0' + (8 + i * 10).toString(16)).slice(-2)
|
|
|
|
|
palette[232 + i] = `#${g}${g}${g}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- UI ---------------------------------------------------------------------
|
|
|
|
|
|
2022-09-10 17:18:08 +02:00
|
|
|
|
let linkRE = [
|
|
|
|
|
/https?:\/\//,
|
|
|
|
|
/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/,
|
|
|
|
|
/[^\[\](){}<>"'\s,.:]/,
|
|
|
|
|
].map(r => r.source).join('')
|
|
|
|
|
|
2022-09-07 14:07:14 +02:00
|
|
|
|
let Toolbar = {
|
|
|
|
|
toggleAutoscroll: () => {
|
|
|
|
|
bufferAutoscroll = !bufferAutoscroll
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toggleLog: () => {
|
|
|
|
|
if (bufferLog) {
|
|
|
|
|
bufferLog = undefined
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rpc.send({
|
|
|
|
|
command: 'BufferLog',
|
|
|
|
|
bufferName: bufferCurrent,
|
|
|
|
|
}).then(resp => {
|
|
|
|
|
bufferLog = rpc.base64decode(resp.log)
|
|
|
|
|
m.redraw()
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
view: vnode => {
|
|
|
|
|
return m('.toolbar', {}, [
|
|
|
|
|
m('button', {onclick: Toolbar.toggleAutoscroll},
|
2022-09-07 17:33:14 +02:00
|
|
|
|
bufferAutoscroll ? 'Scroll lock' : 'Scroll unlock'),
|
2022-09-07 14:07:14 +02:00
|
|
|
|
m('button', {onclick: Toolbar.toggleLog},
|
|
|
|
|
bufferLog === undefined ? 'Show log' : 'Hide log'),
|
|
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
let BufferList = {
|
2022-09-07 14:07:14 +02:00
|
|
|
|
activate: name => {
|
|
|
|
|
rpc.send({command: 'BufferActivate', bufferName: name})
|
|
|
|
|
},
|
|
|
|
|
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
view: vnode => {
|
2022-09-06 23:37:06 +02:00
|
|
|
|
let items = Array.from(buffers, ([name, b]) => {
|
2022-09-07 19:42:18 +02:00
|
|
|
|
let classes = [], displayName = name
|
|
|
|
|
if (name == bufferCurrent) {
|
|
|
|
|
classes.push('current')
|
|
|
|
|
} else {
|
|
|
|
|
if (b.highlighted)
|
|
|
|
|
classes.push('highlighted')
|
|
|
|
|
if (b.newMessages) {
|
|
|
|
|
classes.push('activity')
|
|
|
|
|
displayName += ` (${b.newMessages})`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return m('.item', {
|
|
|
|
|
onclick: event => BufferList.activate(name),
|
|
|
|
|
class: classes.join(' '),
|
|
|
|
|
}, displayName)
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
})
|
|
|
|
|
return m('.list', {}, items)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let Content = {
|
2022-09-07 14:07:14 +02:00
|
|
|
|
applyColor: (fg, bg, inverse) => {
|
|
|
|
|
if (inverse)
|
|
|
|
|
[fg, bg] = [bg >= 0 ? bg : 15, fg >= 0 ? fg : 0]
|
|
|
|
|
|
|
|
|
|
let style = {}
|
|
|
|
|
if (fg >= 0)
|
|
|
|
|
style.color = palette[fg]
|
|
|
|
|
if (bg >= 0)
|
|
|
|
|
style.backgroundColor = palette[bg]
|
|
|
|
|
if (style)
|
|
|
|
|
return style
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
linkify: (text, attrs) => {
|
2022-09-10 17:18:08 +02:00
|
|
|
|
let re = new RegExp(linkRE, 'g'), a = [], end = 0, match
|
2022-09-07 14:07:14 +02:00
|
|
|
|
while ((match = re.exec(text)) !== null) {
|
|
|
|
|
if (end < match.index)
|
|
|
|
|
a.push(m('span', attrs, text.substring(end, match.index)))
|
2022-09-10 16:14:12 +02:00
|
|
|
|
a.push(m('a[target=_blank]', {href: match[0], ...attrs}, match[0]))
|
2022-09-07 14:07:14 +02:00
|
|
|
|
end = re.lastIndex
|
|
|
|
|
}
|
|
|
|
|
if (end < text.length)
|
|
|
|
|
a.push(m('span', attrs, text.substring(end)))
|
|
|
|
|
return a
|
|
|
|
|
},
|
|
|
|
|
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
view: vnode => {
|
|
|
|
|
let line = vnode.children[0]
|
2022-09-07 14:07:14 +02:00
|
|
|
|
let mark = undefined
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
switch (line.rendition) {
|
2022-09-07 14:07:14 +02:00
|
|
|
|
case 'Indent': mark = m('span.mark', {}, ''); break
|
|
|
|
|
case 'Status': mark = m('span.mark', {}, '–'); break
|
|
|
|
|
case 'Error': mark = m('span.mark.error', {}, '⚠'); break
|
|
|
|
|
case 'Join': mark = m('span.mark.join', {}, '→'); break
|
|
|
|
|
case 'Part': mark = m('span.mark.part', {}, '←'); break
|
|
|
|
|
case 'Action': mark = m('span.mark.action', {}, '✶'); break
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let classes = new Set()
|
|
|
|
|
let flip = c => {
|
|
|
|
|
if (classes.has(c))
|
|
|
|
|
classes.delete(c)
|
|
|
|
|
else
|
|
|
|
|
classes.add(c)
|
|
|
|
|
}
|
2022-09-05 22:34:20 +02:00
|
|
|
|
let fg = -1, bg = -1, inverse = false
|
2022-09-07 15:33:38 +02:00
|
|
|
|
return m('.content', vnode.attrs, [mark, line.items.flatMap(item => {
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
switch (item.kind) {
|
|
|
|
|
case 'Text':
|
2022-09-07 14:07:14 +02:00
|
|
|
|
return Content.linkify(item.text, {
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
class: Array.from(classes.keys()).join(' '),
|
2022-09-07 14:07:14 +02:00
|
|
|
|
style: Content.applyColor(fg, bg, inverse),
|
|
|
|
|
})
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
case 'Reset':
|
|
|
|
|
classes.clear()
|
2022-09-05 22:34:20 +02:00
|
|
|
|
fg = bg = -1
|
|
|
|
|
inverse = false
|
|
|
|
|
break
|
|
|
|
|
case 'FgColor':
|
|
|
|
|
fg = item.color
|
|
|
|
|
break
|
|
|
|
|
case 'BgColor':
|
|
|
|
|
bg = item.color
|
|
|
|
|
break
|
|
|
|
|
case 'FlipInverse':
|
|
|
|
|
inverse = !inverse
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
break
|
2022-09-07 14:07:14 +02:00
|
|
|
|
case 'FlipBold':
|
|
|
|
|
flip('b')
|
|
|
|
|
break
|
|
|
|
|
case 'FlipItalic':
|
|
|
|
|
flip('i')
|
|
|
|
|
break
|
|
|
|
|
case 'FlipUnderline':
|
|
|
|
|
flip('u')
|
|
|
|
|
break
|
|
|
|
|
case 'FlipCrossedOut':
|
|
|
|
|
flip('s')
|
|
|
|
|
break
|
|
|
|
|
case 'FlipMonospace':
|
|
|
|
|
flip('m')
|
|
|
|
|
break
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
}
|
2022-09-07 14:07:14 +02:00
|
|
|
|
})])
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let Buffer = {
|
2022-09-10 18:09:46 +02:00
|
|
|
|
controller: new AbortController(),
|
|
|
|
|
|
|
|
|
|
onbeforeremove: vnode => {
|
|
|
|
|
Buffer.controller.abort()
|
2022-09-06 22:30:23 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onupdate: vnode => {
|
2022-09-10 18:09:46 +02:00
|
|
|
|
if (bufferAutoscroll)
|
|
|
|
|
vnode.dom.scrollTop = vnode.dom.scrollHeight
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
oncreate: vnode => {
|
|
|
|
|
Buffer.onupdate(vnode)
|
|
|
|
|
window.addEventListener('resize', event => Buffer.onupdate(vnode),
|
|
|
|
|
{signal: Buffer.controller.signal})
|
2022-09-06 22:30:23 +02:00
|
|
|
|
},
|
|
|
|
|
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
view: vnode => {
|
|
|
|
|
let lines = []
|
|
|
|
|
let b = buffers.get(bufferCurrent)
|
|
|
|
|
if (b === undefined)
|
2022-09-10 18:09:46 +02:00
|
|
|
|
return m('.buffer')
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
|
|
|
|
|
let lastDateMark = undefined
|
2022-09-07 19:42:18 +02:00
|
|
|
|
let markBefore = b.lines.length
|
|
|
|
|
- b.newMessages - b.newUnimportantMessages
|
|
|
|
|
b.lines.forEach((line, i) => {
|
2022-09-06 14:38:09 +02:00
|
|
|
|
let date = new Date(line.when)
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
let dateMark = date.toLocaleDateString()
|
|
|
|
|
if (dateMark !== lastDateMark) {
|
|
|
|
|
lines.push(m('.date', {}, dateMark))
|
|
|
|
|
lastDateMark = dateMark
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-07 19:42:18 +02:00
|
|
|
|
if (i == markBefore)
|
|
|
|
|
lines.push(m('.unread'))
|
|
|
|
|
|
2022-09-07 15:33:38 +02:00
|
|
|
|
let attrs = {}
|
|
|
|
|
if (line.leaked)
|
|
|
|
|
attrs.class = 'leaked'
|
|
|
|
|
|
2022-09-07 19:42:18 +02:00
|
|
|
|
// TODO: Make use of isUnimportant.
|
2022-09-07 15:33:38 +02:00
|
|
|
|
lines.push(m('.time', {...attrs}, date.toLocaleTimeString()))
|
|
|
|
|
lines.push(m(Content, {...attrs}, line))
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
})
|
2022-09-07 19:23:17 +02:00
|
|
|
|
|
|
|
|
|
let dateMark = new Date().toLocaleDateString()
|
2022-09-07 19:42:18 +02:00
|
|
|
|
if (dateMark !== lastDateMark && lastDateMark !== undefined)
|
2022-09-07 19:23:17 +02:00
|
|
|
|
lines.push(m('.date', {}, dateMark))
|
2022-09-07 14:07:14 +02:00
|
|
|
|
return m('.buffer', {}, lines)
|
2022-09-06 23:37:06 +02:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-07 14:07:14 +02:00
|
|
|
|
let Log = {
|
|
|
|
|
oncreate: vnode => {
|
|
|
|
|
if (vnode.dom !== undefined)
|
|
|
|
|
vnode.dom.scrollTop = vnode.dom.scrollHeight
|
2022-09-07 13:52:30 +02:00
|
|
|
|
},
|
|
|
|
|
|
2022-09-10 17:18:08 +02:00
|
|
|
|
linkify: text => {
|
|
|
|
|
let re = new RegExp(linkRE, 'g'), a = [], end = 0, match
|
|
|
|
|
while ((match = re.exec(text)) !== null) {
|
|
|
|
|
if (end < match.index)
|
|
|
|
|
a.push(text.substring(end, match.index))
|
|
|
|
|
a.push(m('a[target=_blank]', {href: match[0]}, match[0]))
|
|
|
|
|
end = re.lastIndex
|
|
|
|
|
}
|
|
|
|
|
if (end < text.length)
|
|
|
|
|
a.push(text.substring(end))
|
|
|
|
|
return a
|
|
|
|
|
},
|
|
|
|
|
|
2022-09-07 14:07:14 +02:00
|
|
|
|
view: vnode => {
|
2022-09-10 17:18:08 +02:00
|
|
|
|
return m(".log", {}, Log.linkify(bufferLog))
|
2022-09-06 23:37:06 +02:00
|
|
|
|
},
|
2022-09-07 14:07:14 +02:00
|
|
|
|
}
|
2022-09-06 23:37:06 +02:00
|
|
|
|
|
2022-09-07 14:07:14 +02:00
|
|
|
|
let BufferContainer = {
|
2022-09-06 23:37:06 +02:00
|
|
|
|
view: vnode => {
|
2022-09-07 14:07:14 +02:00
|
|
|
|
return m('.buffer-container', {}, [
|
|
|
|
|
m('.filler'),
|
|
|
|
|
bufferLog !== undefined ? m(Log) : m(Buffer),
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-07 14:07:14 +02:00
|
|
|
|
let Input = {
|
2022-09-07 15:09:43 +02:00
|
|
|
|
counter: 0,
|
|
|
|
|
stamp: textarea => {
|
|
|
|
|
return [Input.counter,
|
|
|
|
|
textarea.selectionStart, textarea.selectionEnd, textarea.value]
|
|
|
|
|
},
|
|
|
|
|
|
2022-09-07 14:07:14 +02:00
|
|
|
|
complete: textarea => {
|
2022-09-06 17:17:32 +02:00
|
|
|
|
if (textarea.selectionStart !== textarea.selectionEnd)
|
2022-09-07 14:07:14 +02:00
|
|
|
|
return false
|
|
|
|
|
|
2022-09-07 15:09:43 +02:00
|
|
|
|
// Cancel any previous autocomplete, and ensure applicability.
|
|
|
|
|
Input.counter++
|
|
|
|
|
let state = Input.stamp(textarea)
|
2022-09-06 17:17:32 +02:00
|
|
|
|
rpc.send({
|
|
|
|
|
command: 'BufferComplete',
|
|
|
|
|
bufferName: bufferCurrent,
|
|
|
|
|
text: textarea.value,
|
|
|
|
|
position: textarea.selectionEnd,
|
2022-09-06 23:37:06 +02:00
|
|
|
|
}).then(resp => {
|
2022-09-07 15:09:43 +02:00
|
|
|
|
if (!Input.stamp(textarea).every((v, k) => v === state[k]))
|
|
|
|
|
return
|
|
|
|
|
|
2022-09-06 17:17:32 +02:00
|
|
|
|
// TODO: Somehow display remaining options, or cycle through.
|
2022-09-06 23:37:06 +02:00
|
|
|
|
if (resp.completions.length)
|
|
|
|
|
textarea.setRangeText(resp.completions[0],
|
|
|
|
|
resp.start, textarea.selectionEnd, 'end')
|
|
|
|
|
if (resp.completions.length === 1)
|
2022-09-06 17:17:32 +02:00
|
|
|
|
textarea.setRangeText(' ',
|
|
|
|
|
textarea.selectionStart, textarea.selectionEnd, 'end')
|
|
|
|
|
})
|
2022-09-07 14:07:14 +02:00
|
|
|
|
return true
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
submit: textarea => {
|
2022-09-06 17:17:32 +02:00
|
|
|
|
rpc.send({
|
|
|
|
|
command: 'BufferInput',
|
|
|
|
|
bufferName: bufferCurrent,
|
|
|
|
|
text: textarea.value,
|
|
|
|
|
})
|
|
|
|
|
textarea.value = ''
|
2022-09-07 14:07:14 +02:00
|
|
|
|
return true
|
|
|
|
|
},
|
2022-09-06 17:17:32 +02:00
|
|
|
|
|
2022-09-07 14:07:14 +02:00
|
|
|
|
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:
|
2022-09-07 17:25:31 +02:00
|
|
|
|
if (!event.shiftKey)
|
|
|
|
|
handled = Input.complete(textarea)
|
2022-09-07 14:07:14 +02:00
|
|
|
|
break
|
|
|
|
|
case 13:
|
2022-09-07 17:25:31 +02:00
|
|
|
|
if (!event.shiftKey)
|
|
|
|
|
handled = Input.submit(textarea)
|
2022-09-07 14:07:14 +02:00
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
if (handled)
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
},
|
2022-09-06 17:17:32 +02:00
|
|
|
|
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
view: vnode => {
|
2022-09-07 14:07:14 +02:00
|
|
|
|
return m('textarea#input', {rows: 1, onkeydown: Input.onKeyDown})
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let Main = {
|
|
|
|
|
view: vnode => {
|
2022-09-06 20:17:23 +02:00
|
|
|
|
let state = "Connected"
|
|
|
|
|
if (connecting)
|
|
|
|
|
state = "Connecting..."
|
|
|
|
|
else if (rpc.ws === undefined)
|
|
|
|
|
state = "Disconnected"
|
|
|
|
|
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
return m('.xP', {}, [
|
2022-09-06 23:37:06 +02:00
|
|
|
|
m('.title', {}, [`xP (${state})`, m(Toolbar)]),
|
2022-09-07 14:07:14 +02:00
|
|
|
|
m('.middle', {}, [m(BufferList), m(BufferContainer)]),
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
m('.status', {}, bufferCurrent),
|
|
|
|
|
m(Input),
|
|
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-06 21:27:14 +02:00
|
|
|
|
window.addEventListener('load', () => m.mount(document.body, Main))
|