Compare commits

..

No commits in common. "8cd94b30f67f8eacf853fbc77bd60fb57a8262dc" and "d7b0b447b7e254d4af37f09acfa258956d24a7e0" have entirely different histories.

5 changed files with 164 additions and 350 deletions

View File

@ -382,8 +382,7 @@ function codegen_union(name, cg, gotype, tagfield, tagvar) {
print "}" print "}"
print "" print ""
# This cannot be a pointer method, it wouldn't work recursively. print "func (u *" gotype ") MarshalJSON() ([]byte, error) {"
print "func (u " gotype ") MarshalJSON() ([]byte, error) {"
print "\treturn json.Marshal(u.Interface)" print "\treturn json.Marshal(u.Interface)"
print "}" print "}"
print "" print ""

View File

@ -71,7 +71,7 @@ struct EventMessage {
PART, PART,
ACTION, ACTION,
} rendition; } rendition;
// Unix timestamp in milliseconds. // Unix timestamp in seconds.
u64 when; u64 when;
// Broken-up text, with in-band formatting. // Broken-up text, with in-band formatting.
union ItemData switch (enum Item { union ItemData switch (enum Item {
@ -104,8 +104,6 @@ struct EventMessage {
} items<>; } items<>;
case BUFFER_CLEAR: case BUFFER_CLEAR:
string buffer_name; string buffer_name;
// Restriction: command_seq is strictly increasing, across both of these.
case ERROR: case ERROR:
u32 command_seq; u32 command_seq;
string error; string error;

179
xC.c
View File

@ -1032,7 +1032,7 @@ input_el__restore (struct input_el *self)
static void static void
input_el__start_over (struct input_el *self) input_el__start_over (struct input_el *self)
{ {
wchar_t x[] = { L'c' & 31, 0 }; wchar_t x[] = { L'g' & 31, 0 };
el_wpush (self->editline, x); el_wpush (self->editline, x);
int dummy_count = 0; int dummy_count = 0;
@ -3105,7 +3105,7 @@ relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer,
e->is_unimportant = !!(line->flags & BUFFER_LINE_UNIMPORTANT); e->is_unimportant = !!(line->flags & BUFFER_LINE_UNIMPORTANT);
e->is_highlight = !!(line->flags & BUFFER_LINE_HIGHLIGHT); e->is_highlight = !!(line->flags & BUFFER_LINE_HIGHLIGHT);
e->rendition = 1 + line->r; e->rendition = 1 + line->r;
e->when = line->when * 1000; e->when = line->when;
size_t len = 0; size_t len = 0;
for (size_t i = 0; line->items[i].type; i++) for (size_t i = 0; line->items[i].type; i++)
@ -13271,6 +13271,12 @@ process_input (struct app_context *ctx, char *user_input)
// The amount of crap that goes into this is truly insane. // The amount of crap that goes into this is truly insane.
// It's mostly because of Editline's total ignorance of this task. // It's mostly because of Editline's total ignorance of this task.
static void
completion_init (struct completion *self)
{
memset (self, 0, sizeof *self);
}
static void static void
completion_free (struct completion *self) completion_free (struct completion *self)
{ {
@ -13289,13 +13295,13 @@ completion_add_word (struct completion *self, size_t start, size_t end)
self->words[self->words_len++] = (struct completion_word) { start, end }; self->words[self->words_len++] = (struct completion_word) { start, end };
} }
static struct completion static void
completion_make (const char *line, size_t len) completion_parse (struct completion *self, const char *line, size_t len)
{ {
struct completion self = { .line = xstrndup (line, len) }; self->line = xstrndup (line, len);
// The first and the last word may be empty // The first and the last word may be empty
const char *s = self.line; const char *s = self->line;
while (true) while (true)
{ {
const char *start = s; const char *start = s;
@ -13303,11 +13309,10 @@ completion_make (const char *line, size_t len)
const char *end = start + word_len; const char *end = start + word_len;
s = end + strspn (end, WORD_BREAKING_CHARS); s = end + strspn (end, WORD_BREAKING_CHARS);
completion_add_word (&self, start - self.line, end - self.line); completion_add_word (self, start - self->line, end - self->line);
if (s == end) if (s == end)
break; break;
} }
return self;
} }
static void static void
@ -13481,13 +13486,14 @@ complete_set (struct app_context *ctx, struct completion *data,
} }
static void static void
complete_topic (struct buffer *buffer, struct completion *data, complete_topic (struct app_context *ctx, struct completion *data,
const char *word, struct strv *output) const char *word, struct strv *output)
{ {
(void) data; (void) data;
// TODO: make it work in other server-related buffers, too, i.e. when we're // TODO: make it work in other server-related buffers, too, i.e. when we're
// completing the third word and the second word is a known channel name // completing the third word and the second word is a known channel name
struct buffer *buffer = ctx->current_buffer;
if (buffer->type != BUFFER_CHANNEL) if (buffer->type != BUFFER_CHANNEL)
return; return;
@ -13503,9 +13509,10 @@ complete_topic (struct buffer *buffer, struct completion *data,
} }
static void static void
complete_nicknames (struct buffer *buffer, struct completion *data, complete_nicknames (struct app_context *ctx, struct completion *data,
const char *word, struct strv *output) const char *word, struct strv *output)
{ {
struct buffer *buffer = ctx->current_buffer;
if (buffer->type == BUFFER_SERVER) if (buffer->type == BUFFER_SERVER)
{ {
struct user *self_user = buffer->server->irc_user; struct user *self_user = buffer->server->irc_user;
@ -13527,9 +13534,9 @@ complete_nicknames (struct buffer *buffer, struct completion *data,
} }
} }
static struct strv static char **
complete_word (struct app_context *ctx, struct buffer *buffer, complete_word (struct app_context *ctx, struct completion *data,
struct completion *data, const char *word) const char *word)
{ {
char *initial = completion_word (data, 0); char *initial = completion_word (data, 0);
@ -13548,11 +13555,11 @@ complete_word (struct app_context *ctx, struct buffer *buffer,
} }
else if (data->location == 1 && !strcmp (initial, "/topic")) else if (data->location == 1 && !strcmp (initial, "/topic"))
{ {
complete_topic (buffer, data, word, &words); complete_topic (ctx, data, word, &words);
complete_nicknames (buffer, data, word, &words); complete_nicknames (ctx, data, word, &words);
} }
else else
complete_nicknames (buffer, data, word, &words); complete_nicknames (ctx, data, word, &words);
cstr_set (&initial, NULL); cstr_set (&initial, NULL);
LIST_FOR_EACH (struct hook, iter, ctx->completion_hooks) LIST_FOR_EACH (struct hook, iter, ctx->completion_hooks)
@ -13561,12 +13568,17 @@ complete_word (struct app_context *ctx, struct buffer *buffer,
hook->complete (hook, data, word, &words); hook->complete (hook, data, word, &words);
} }
if (words.len <= 2) if (words.len == 1)
{
// Nothing matched
strv_free (&words);
return NULL;
}
if (words.len == 2)
{ {
// When nothing matches, this copies the sentinel value
words.vector[0] = words.vector[1]; words.vector[0] = words.vector[1];
words.vector[1] = NULL; words.vector[1] = NULL;
words.len--;
} }
else else
{ {
@ -13577,7 +13589,7 @@ complete_word (struct app_context *ctx, struct buffer *buffer,
else else
words.vector[0] = xstrndup (words.vector[1], prefix); words.vector[0] = xstrndup (words.vector[1], prefix);
} }
return words; return words.vector;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -13643,26 +13655,26 @@ locale_to_utf8 (struct app_context *ctx, const char *locale,
return str_steal (&utf8); return str_steal (&utf8);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void
utf8_vector_to_locale (struct app_context *ctx, char **vector)
static struct strv
make_completions (struct app_context *ctx, struct buffer *buffer,
const char *line_utf8, size_t start, size_t end)
{ {
struct completion comp = completion_make (line_utf8, strlen (line_utf8)); for (; *vector; vector++)
completion_locate (&comp, start); {
char *word = xstrndup (line_utf8 + start, end - start); char *converted = iconv_xstrdup
struct strv completions = complete_word (ctx, buffer, &comp, word); (ctx->term_from_utf8, *vector, -1, NULL);
free (word); if (!soft_assert (converted))
completion_free (&comp); converted = xstrdup ("");
return completions;
cstr_set (vector, converted);
} }
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// Takes a line in locale-specific encoding and position of a word to complete, /// Takes a line in locale-specific encoding and position of a word to complete,
/// returns a vector of matches in locale-specific encoding. /// returns a vector of matches in locale-specific encoding.
static char ** static char **
make_input_completions make_completions (struct app_context *ctx, char *line, int start, int end)
(struct app_context *ctx, const char *line, int start, int end)
{ {
int *fixes[] = { &start, &end }; int *fixes[] = { &start, &end };
char *line_utf8 = locale_to_utf8 (ctx, line, fixes, N_ELEMENTS (fixes)); char *line_utf8 = locale_to_utf8 (ctx, line, fixes, N_ELEMENTS (fixes));
@ -13671,23 +13683,20 @@ make_input_completions
hard_assert (start >= 0 && end >= 0 && start <= end); hard_assert (start >= 0 && end >= 0 && start <= end);
struct strv completions = struct completion c;
make_completions (ctx, ctx->current_buffer, line_utf8, start, end); completion_init (&c);
completion_parse (&c, line, strlen (line));
completion_locate (&c, start);
char *word = xstrndup (line + start, end - start);
char **completions = complete_word (ctx, &c, word);
free (word);
completion_free (&c);
if (completions)
utf8_vector_to_locale (ctx, completions);
free (line_utf8); free (line_utf8);
if (!completions.len) return completions;
{
strv_free (&completions);
return NULL;
}
for (size_t i = 0; i < completions.len; i++)
{
char *converted = iconv_xstrdup
(ctx->term_from_utf8, completions.vector[i], -1, NULL);
if (!soft_assert (converted))
converted = xstrdup ("?");
cstr_set (&completions.vector[i], converted);
}
return completions.vector;
} }
// --- Common code for user actions -------------------------------------------- // --- Common code for user actions --------------------------------------------
@ -14372,7 +14381,7 @@ app_readline_completion (const char *text, int start, int end)
// Don't iterate over filenames and stuff // Don't iterate over filenames and stuff
rl_attempted_completion_over = true; rl_attempted_completion_over = true;
return make_input_completions (g_ctx, rl_line_buffer, start, end); return make_completions (g_ctx, rl_line_buffer, start, end);
} }
static int static int
@ -14416,6 +14425,8 @@ static unsigned char
on_editline_complete (EditLine *editline, int key) on_editline_complete (EditLine *editline, int key)
{ {
(void) key; (void) key;
(void) editline;
struct app_context *ctx = g_ctx; struct app_context *ctx = g_ctx;
// First prepare what Readline would have normally done for us... // First prepare what Readline would have normally done for us...
@ -14429,7 +14440,7 @@ on_editline_complete (EditLine *editline, int key)
while (el_start && !strchr (WORD_BREAKING_CHARS, copy[el_start - 1])) while (el_start && !strchr (WORD_BREAKING_CHARS, copy[el_start - 1]))
el_start--; el_start--;
char **completions = make_input_completions (ctx, copy, el_start, el_end); char **completions = make_completions (ctx, copy, el_start, el_end);
// XXX: possibly incorrect wrt. shift state encodings // XXX: possibly incorrect wrt. shift state encodings
copy[el_end] = '\0'; copy[el_end] = '\0';
@ -14527,6 +14538,12 @@ app_editline_init (struct input_el *self)
// Just what are you doing? // Just what are you doing?
CALL_ (input, bind_control, 'u', "vi-kill-line-prev"); CALL_ (input, bind_control, 'u', "vi-kill-line-prev");
// See input_el__redisplay(), functionally important
CALL_ (input, bind_control, 'q', "ed-redisplay");
// This is what buffered el_wgets() does, functionally important;
// perhaps it could be bound somewhere more appropriate
CALL_ (input, bind_control, 'g', "ed-start-over");
// We need to hide the prompt and input first // We need to hide the prompt and input first
CALL_ (input, bind, "\r", "send-line"); CALL_ (input, bind, "\r", "send-line");
CALL_ (input, bind, "\n", "send-line"); CALL_ (input, bind, "\n", "send-line");
@ -14535,11 +14552,6 @@ app_editline_init (struct input_el *self)
// Source the user's defaults file // Source the user's defaults file
el_source (self->editline, NULL); el_source (self->editline, NULL);
// See input_el__redisplay(), functionally important
CALL_ (input, bind_control, 'q', "ed-redisplay");
// This is what buffered el_wgets() does, functionally important
CALL_ (input, bind_control, 'c', "ed-start-over");
} }
#endif // HAVE_EDITLINE #endif // HAVE_EDITLINE
@ -15166,56 +15178,12 @@ client_message_buffer_name (const struct relay_command_message *m)
} }
} }
static void
client_process_buffer_complete (struct client *c, uint32_t seq,
struct buffer *buffer, struct relay_command_data_buffer_complete *req)
{
struct str *line = &req->text;
uint32_t end = req->position;
if (line->len < end || line->len != strlen (line->str))
{
relay_prepare_error (c->ctx, seq, "Invalid arguments");
goto out;
}
uint32_t start = end;
while (start && !strchr (WORD_BREAKING_CHARS, line->str[start - 1]))
start--;
struct strv completions =
make_completions (c->ctx, buffer, line->str, start, end);
if (completions.len > UINT32_MAX)
{
relay_prepare_error (c->ctx, seq, "Internal error");
goto out_internal;
}
struct relay_event_data_response *e =
&relay_prepare (c->ctx)->data.response;
e->event = RELAY_EVENT_RESPONSE;
e->command_seq = seq;
e->data.command = RELAY_COMMAND_BUFFER_COMPLETE;
struct relay_response_data_buffer_complete *resp =
&e->data.buffer_complete;
resp->start = start;
resp->completions_len = completions.len;
resp->completions = xcalloc (completions.len, sizeof *resp->completions);
for (size_t i = 0; i < completions.len; i++)
resp->completions[i] = str_from_cstr (completions.vector[i]);
out_internal:
strv_free (&completions);
out:
relay_send (c);
}
static void static void
client_process_buffer_log client_process_buffer_log
(struct client *c, uint32_t seq, struct buffer *buffer) (struct client *c, uint32_t seq, struct buffer *buffer)
{ {
struct relay_event_data_response *e = struct relay_event_message *m = relay_prepare (c->ctx);
&relay_prepare (c->ctx)->data.response; struct relay_event_data_response *e = &m->data.response;
e->event = RELAY_EVENT_RESPONSE; e->event = RELAY_EVENT_RESPONSE;
e->command_seq = seq; e->command_seq = seq;
e->data.command = RELAY_COMMAND_BUFFER_LOG; e->data.command = RELAY_COMMAND_BUFFER_LOG;
@ -15286,8 +15254,9 @@ client_process_message (struct client *c,
reset_autoaway (c->ctx); reset_autoaway (c->ctx);
break; break;
case RELAY_COMMAND_BUFFER_COMPLETE: case RELAY_COMMAND_BUFFER_COMPLETE:
client_process_buffer_complete (c, m->command_seq, buffer, // TODO: Run the completion machinery.
&m->data.buffer_complete); relay_prepare_error (c->ctx, m->command_seq, "Not implemented");
relay_send (c);
break; break;
case RELAY_COMMAND_BUFFER_INPUT: case RELAY_COMMAND_BUFFER_INPUT:
(void) process_input_utf8 (c->ctx, (void) process_input_utf8 (c->ctx,

View File

@ -86,19 +86,19 @@ body {
padding: .1rem .3rem; padding: .1rem .3rem;
white-space: pre-wrap; white-space: pre-wrap;
} }
.content .b { .content span.b {
font-weight: bold; font-weight: bold;
} }
.content .i { .content span.i {
font-style: italic; font-style: italic;
} }
.content .u { .content span.u {
text-decoration: underline; text-decoration: underline;
} }
.content .s { .content span.s {
text-decoration: line-through; text-decoration: line-through;
} }
.content .m { .content span.m {
font-family: monospace; font-family: monospace;
} }

View File

@ -1,175 +1,5 @@
// Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name> // Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
// SPDX-License-Identifier: 0BSD // SPDX-License-Identifier: 0BSD
'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 => {
reject()
this.ws = undefined
}
})
}
_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"
this.dispatchEvent(new CustomEvent(e.event, {detail: e}))
// 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"
let seq = ++this.commandSeq
if (seq >= 1 << 32)
seq = this.commandSeq = 0
this.ws.send(JSON.stringify({commandSeq: seq, data: params}))
return new Promise((resolve, reject) => {
this.promised[seq] = {resolve, reject}
})
}
}
// ---- Event processing -------------------------------------------------------
// TODO: Probably reset state on disconnect, and indicate to user.
let rpc = new RelayRpc(proxy)
rpc.connect()
.then(result => {
rpc.send({command: 'Hello', version: 1})
})
let buffers = new Map()
let bufferCurrent = undefined
rpc.addEventListener('BufferUpdate', event => {
let e = event.detail, b = buffers.get(e.bufferName)
if (b === undefined) {
b = {lines: []}
buffers.set(e.bufferName, b)
}
// TODO: Update any buffer properties.
})
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 => {
let e = event.detail
bufferCurrent = e.bufferName
// TODO: Somehow scroll to the end of it immediately.
// TODO: Focus the textarea.
})
rpc.addEventListener('BufferLine', event => {
let e = event.detail, b = buffers.get(e.bufferName)
if (b !== undefined)
b.lines.push({when: e.when, rendition: e.rendition, items: e.items})
})
rpc.addEventListener('BufferClear', event => {
let e = event.detail, b = buffers.get(e.bufferName)
if (b !== undefined)
b.lines.length = 0
})
// --- Colours ----------------------------------------------------------------- // --- Colours -----------------------------------------------------------------
@ -203,6 +33,69 @@ function applyColor(fg, bg, inverse) {
return style return style
} }
// ---- Event processing -------------------------------------------------------
// TODO: Probably reset state on disconnect, and indicate to user.
let socket = new WebSocket(proxy)
let commandSeq = 0
function send(command) {
socket.send(JSON.stringify({commandSeq: ++commandSeq, data: command}))
}
socket.onopen = function(event) {
send({command: 'Hello', version: 1})
}
let buffers = new Map()
let bufferCurrent = undefined
socket.onmessage = function(event) {
console.log(event.data)
let e = JSON.parse(event.data).data
switch (e.event) {
case 'BufferUpdate':
{
let b = buffers.get(e.bufferName)
if (b === undefined) {
b = {lines: []}
buffers.set(e.bufferName, b)
}
// TODO: Update any buffer properties.
break
}
case 'BufferRename':
buffers.set(e.new, buffers.get(e.bufferName))
buffers.delete(e.bufferName)
break
case 'BufferRemove':
buffers.delete(e.bufferName)
break
case 'BufferActivate':
bufferCurrent = e.bufferName
// TODO: Somehow scroll to the end of it immediately.
// TODO: Focus the textarea.
break
case 'BufferLine':
{
let b = buffers.get(e.bufferName)
if (b !== undefined)
b.lines.push({when: e.when, rendition: e.rendition, items: e.items})
break
}
case 'BufferClear':
{
let b = buffers.get(e.bufferName)
if (b !== undefined)
b.lines.length = 0
break
}
}
m.redraw()
}
// ---- UI --------------------------------------------------------------------- // ---- UI ---------------------------------------------------------------------
let BufferList = { let BufferList = {
@ -210,8 +103,8 @@ let BufferList = {
let items = [] let items = []
buffers.forEach((b, name) => { buffers.forEach((b, name) => {
let attrs = { let attrs = {
onclick: event => { onclick: e => {
rpc.send({command: 'BufferActivate', bufferName: name}) send({command: 'BufferActivate', bufferName: name})
}, },
} }
if (name == bufferCurrent) if (name == bufferCurrent)
@ -222,24 +115,6 @@ let BufferList = {
}, },
} }
function linkify(text, attrs, a) {
let re = new RegExp([
/https?:\/\//,
/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/,
/[^\[\](){}<>"'\s,.:]/,
].map(r => r.source).join(''), 'g')
let end = 0, match
while ((match = re.exec(text)) !== null) {
if (end < match.index)
a.push(m('span', attrs, text.substring(end, match.index)))
a.push(m('a', {href: match[0], ...attrs}, match[0]))
end = re.lastIndex
}
if (end < text.length)
a.push(m('span', attrs, text.substring(end)))
}
let Content = { let Content = {
view: vnode => { view: vnode => {
let line = vnode.children[0] let line = vnode.children[0]
@ -264,10 +139,11 @@ let Content = {
line.items.forEach(item => { line.items.forEach(item => {
switch (item.kind) { switch (item.kind) {
case 'Text': case 'Text':
linkify(item.text, { // TODO: Detect and transform links.
content.push(m('span', {
class: Array.from(classes.keys()).join(' '), class: Array.from(classes.keys()).join(' '),
style: applyColor(fg, bg, inverse), style: applyColor(fg, bg, inverse),
}, content) }, item.text))
break break
case 'Reset': case 'Reset':
classes.clear() classes.clear()
@ -303,7 +179,7 @@ let Buffer = {
let lastDateMark = undefined let lastDateMark = undefined
b.lines.forEach(line => { b.lines.forEach(line => {
let date = new Date(line.when) let date = new Date(line.when * 1000)
let dateMark = date.toLocaleDateString() let dateMark = date.toLocaleDateString()
if (dateMark !== lastDateMark) { if (dateMark !== lastDateMark) {
lines.push(m('.date', {}, dateMark)) lines.push(m('.date', {}, dateMark))
@ -320,54 +196,26 @@ let Buffer = {
}, },
} }
function onKeyDown(event) {
// TODO: And perhaps on other actions, too.
rpc.send({command: 'Active'})
// TODO: Cancel any current autocomplete.
let textarea = event.currentTarget
switch (event.keyCode) {
case 9:
if (textarea.selectionStart !== textarea.selectionEnd)
return
rpc.send({
command: 'BufferComplete',
bufferName: bufferCurrent,
text: textarea.value,
position: textarea.selectionEnd,
}).then(response => {
// TODO: Somehow display remaining options, or cycle through.
if (response.completions.length)
textarea.setRangeText(response.completions[0],
response.start, textarea.selectionEnd, 'end')
if (response.completions.length === 1)
textarea.setRangeText(' ',
textarea.selectionStart, textarea.selectionEnd, 'end')
})
break;
case 13:
rpc.send({
command: 'BufferInput',
bufferName: bufferCurrent,
text: textarea.value,
})
textarea.value = ''
break;
default:
return
}
event.preventDefault()
}
// TODO: This should be remembered across buffer switches, // TODO: This should be remembered across buffer switches,
// and we'll probably have to intercept /all/ key presses. // and we'll probably have to intercept /all/ key presses.
let Input = { let Input = {
view: vnode => { view: vnode => {
return m('textarea', { return m('textarea', {
rows: 1, rows: 1,
onkeydown: onKeyDown, onkeydown: e => {
// TODO: And perhaps on other actions, too.
send({command: 'Active'})
if (e.keyCode !== 13)
return
send({
command: 'BufferInput',
bufferName: bufferCurrent,
text: e.currentTarget.value,
})
e.preventDefault()
e.currentTarget.value = ''
},
}) })
}, },
} }