Compare commits
No commits in common. "62773acaa099d7633a6d83d44b790f4f53fb274e" and "0bc2c12eecfb5b035c498272556f8fc6a39059a9" have entirely different histories.
62773acaa0
...
0bc2c12eec
6
NEWS
6
NEWS
@ -1,4 +1,4 @@
|
|||||||
2.0.0 (Unreleased)
|
Unreleased
|
||||||
|
|
||||||
* xD: implemented WALLOPS, choosing to make it target even non-operators
|
* xD: implemented WALLOPS, choosing to make it target even non-operators
|
||||||
|
|
||||||
@ -8,10 +8,6 @@
|
|||||||
with the exception of editor_command/editor, backlog_helper/pager,
|
with the exception of editor_command/editor, backlog_helper/pager,
|
||||||
and backlog_helper_strip_formatting/pager_strip_formatting
|
and backlog_helper_strip_formatting/pager_strip_formatting
|
||||||
|
|
||||||
* xC: all attributes.* configuration options have been made abstract in
|
|
||||||
a subset of the git-config(1) format, and renamed to theme.*,
|
|
||||||
with the exception of attributes.reset, which has no replacement
|
|
||||||
|
|
||||||
* xC: replaced behaviour.save_on_quit with general.autosave
|
* xC: replaced behaviour.save_on_quit with general.autosave
|
||||||
|
|
||||||
* xC: improved pager integration capabilities
|
* xC: improved pager integration capabilities
|
||||||
|
32
README.adoc
32
README.adoc
@ -26,13 +26,12 @@ As a unique bonus, you can launch a full text editor from within.
|
|||||||
xP
|
xP
|
||||||
--
|
--
|
||||||
The web frontend for 'xC', making use of its networked relay interface.
|
The web frontend for 'xC', making use of its networked relay interface.
|
||||||
So far it's a bit rough around the edges, yet fully usable.
|
So far it's somewhat basic, yet usable.
|
||||||
|
|
||||||
xF
|
xF
|
||||||
--
|
--
|
||||||
The X11 frontend for 'xC', making use of its networked relay interface.
|
The X11 frontend for 'xC', making use of its networked relay interface.
|
||||||
This subproject has been put on hold, partly because of its massive overlap
|
It's currently in development, and hidden behind a CMake option.
|
||||||
with 'xP', and is hidden behind a CMake option.
|
|
||||||
|
|
||||||
xD
|
xD
|
||||||
--
|
--
|
||||||
@ -118,19 +117,8 @@ as a `forking` type systemd user service.
|
|||||||
|
|
||||||
xP
|
xP
|
||||||
~~
|
~~
|
||||||
The precondition for running 'xC' frontends is enabling its relay interface:
|
Install the Go compiler, and build the server using `make` in its directory,
|
||||||
|
then run it from within the _public_ subdirectory.
|
||||||
/set general.relay_bind = "127.0.0.1:9000"
|
|
||||||
|
|
||||||
To build the web server, you'll need to install the Go compiler, and run `make`
|
|
||||||
from the _xP_ directory. Then start it from the _public_ subdirectory,
|
|
||||||
and navigate to the adress you gave it as its first argument--in the following
|
|
||||||
example, that would be http://localhost:8080[]:
|
|
||||||
|
|
||||||
$ ../xP 127.0.0.1:8080 127.0.0.1:9000
|
|
||||||
|
|
||||||
For remote use, it's recommended to put 'xP' behind a reverse proxy, with TLS,
|
|
||||||
and some form of HTTP authentication.
|
|
||||||
|
|
||||||
Client Certificates
|
Client Certificates
|
||||||
-------------------
|
-------------------
|
||||||
@ -177,12 +165,12 @@ properly set-up terminal emulator, it suffices to run:
|
|||||||
/set general.pager = Press Tab here and change +Gb to +Gb1d
|
/set general.pager = Press Tab here and change +Gb to +Gb1d
|
||||||
/set general.date_change_line = "%a %e %b %Y"
|
/set general.date_change_line = "%a %e %b %Y"
|
||||||
/set general.plugin_autoload += "fancy-prompt.lua"
|
/set general.plugin_autoload += "fancy-prompt.lua"
|
||||||
/set theme.userhost = "109"
|
/set attributes.userhost = "\x1b[38;5;109m"
|
||||||
/set theme.join = "108"
|
/set attributes.join = "\x1b[38;5;108m"
|
||||||
/set theme.part = "138"
|
/set attributes.part = "\x1b[38;5;138m"
|
||||||
/set theme.external = "248"
|
/set attributes.external = "\x1b[38;5;248m"
|
||||||
/set theme.timestamp = "250 255"
|
/set attributes.timestamp = "\x1b[48;5;255m\x1b[38;5;250m"
|
||||||
/set theme.read_marker = "202"
|
/set attributes.read_marker = "\x1b[38;5;202m"
|
||||||
|
|
||||||
Configuration profiles
|
Configuration profiles
|
||||||
----------------------
|
----------------------
|
||||||
|
2
liberty
2
liberty
@ -1 +1 @@
|
|||||||
Subproject commit 22a121383f73fa7739f324021b6ad0ba6ed3cdb3
|
Subproject commit f545be725df9195a5b5897ad95a0220acf10f148
|
491
xC.c
491
xC.c
@ -19,6 +19,7 @@
|
|||||||
// A table of all attributes we use for output
|
// A table of all attributes we use for output
|
||||||
#define ATTR_TABLE(XX) \
|
#define ATTR_TABLE(XX) \
|
||||||
XX( PROMPT, prompt, Terminal attrs for the prompt ) \
|
XX( PROMPT, prompt, Terminal attrs for the prompt ) \
|
||||||
|
XX( RESET, reset, String to reset terminal attributes ) \
|
||||||
XX( DATE_CHANGE, date_change, Terminal attrs for date change ) \
|
XX( DATE_CHANGE, date_change, Terminal attrs for date change ) \
|
||||||
XX( READ_MARKER, read_marker, Terminal attrs for the read marker ) \
|
XX( READ_MARKER, read_marker, Terminal attrs for the read marker ) \
|
||||||
XX( WARNING, warning, Terminal attrs for warnings ) \
|
XX( WARNING, warning, Terminal attrs for warnings ) \
|
||||||
@ -33,7 +34,6 @@
|
|||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
ATTR_RESET,
|
|
||||||
#define XX(x, y, z) ATTR_ ## x,
|
#define XX(x, y, z) ATTR_ ## x,
|
||||||
ATTR_TABLE (XX)
|
ATTR_TABLE (XX)
|
||||||
#undef XX
|
#undef XX
|
||||||
@ -48,9 +48,6 @@ enum
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#define PROGRAM_NAME "xC"
|
#define PROGRAM_NAME "xC"
|
||||||
|
|
||||||
// fmemopen
|
|
||||||
#define _POSIX_C_SOURCE 200809L
|
|
||||||
|
|
||||||
#include "common.c"
|
#include "common.c"
|
||||||
#include "xD-replies.c"
|
#include "xD-replies.c"
|
||||||
#include "xC-proto.c"
|
#include "xC-proto.c"
|
||||||
@ -1455,58 +1452,6 @@ channel_destroy (struct channel *self)
|
|||||||
|
|
||||||
REF_COUNTABLE_METHODS (channel)
|
REF_COUNTABLE_METHODS (channel)
|
||||||
|
|
||||||
// ~~~ Attribute utilities ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
TEXT_BOLD = 1 << 0,
|
|
||||||
TEXT_ITALIC = 1 << 1,
|
|
||||||
TEXT_UNDERLINE = 1 << 2,
|
|
||||||
TEXT_INVERSE = 1 << 3,
|
|
||||||
TEXT_BLINK = 1 << 4,
|
|
||||||
TEXT_CROSSED_OUT = 1 << 5,
|
|
||||||
TEXT_MONOSPACE = 1 << 6
|
|
||||||
};
|
|
||||||
|
|
||||||
// Similar to code in liberty-tui.c.
|
|
||||||
struct attrs
|
|
||||||
{
|
|
||||||
short fg; ///< Foreground (256-colour cube or -1)
|
|
||||||
short bg; ///< Background (256-colour cube or -1)
|
|
||||||
unsigned attrs; ///< TEXT_* mask
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Decode attributes in the value using a subset of the git config format,
|
|
||||||
/// ignoring all errors since it doesn't affect functionality
|
|
||||||
static struct attrs
|
|
||||||
attrs_decode (const char *value)
|
|
||||||
{
|
|
||||||
struct strv v = strv_make ();
|
|
||||||
cstr_split (value, " ", true, &v);
|
|
||||||
|
|
||||||
int colors = 0;
|
|
||||||
struct attrs attrs = { -1, -1, 0 };
|
|
||||||
for (char **it = v.vector; *it; it++)
|
|
||||||
{
|
|
||||||
char *end = NULL;
|
|
||||||
long n = strtol (*it, &end, 10);
|
|
||||||
if (*it != end && !*end && n >= SHRT_MIN && n <= SHRT_MAX)
|
|
||||||
{
|
|
||||||
if (colors == 0) attrs.fg = n;
|
|
||||||
if (colors == 1) attrs.bg = n;
|
|
||||||
colors++;
|
|
||||||
}
|
|
||||||
else if (!strcmp (*it, "bold")) attrs.attrs |= TEXT_BOLD;
|
|
||||||
else if (!strcmp (*it, "italic")) attrs.attrs |= TEXT_ITALIC;
|
|
||||||
else if (!strcmp (*it, "ul")) attrs.attrs |= TEXT_UNDERLINE;
|
|
||||||
else if (!strcmp (*it, "reverse")) attrs.attrs |= TEXT_INVERSE;
|
|
||||||
else if (!strcmp (*it, "blink")) attrs.attrs |= TEXT_BLINK;
|
|
||||||
else if (!strcmp (*it, "strike")) attrs.attrs |= TEXT_CROSSED_OUT;
|
|
||||||
}
|
|
||||||
strv_free (&v);
|
|
||||||
return attrs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ~~~ Buffers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// ~~~ Buffers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
enum formatter_item_type
|
enum formatter_item_type
|
||||||
@ -1520,6 +1465,17 @@ enum formatter_item_type
|
|||||||
FORMATTER_ITEM_IGNORE_ATTR ///< Un/set attribute ignoration
|
FORMATTER_ITEM_IGNORE_ATTR ///< Un/set attribute ignoration
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
TEXT_BOLD = 1 << 0,
|
||||||
|
TEXT_ITALIC = 1 << 1,
|
||||||
|
TEXT_UNDERLINE = 1 << 2,
|
||||||
|
TEXT_INVERSE = 1 << 3,
|
||||||
|
TEXT_BLINK = 1 << 4,
|
||||||
|
TEXT_CROSSED_OUT = 1 << 5,
|
||||||
|
TEXT_MONOSPACE = 1 << 6
|
||||||
|
};
|
||||||
|
|
||||||
struct formatter_item
|
struct formatter_item
|
||||||
{
|
{
|
||||||
enum formatter_item_type type : 16; ///< Type of this item
|
enum formatter_item_type type : 16; ///< Type of this item
|
||||||
@ -2160,13 +2116,12 @@ struct completion_hook
|
|||||||
|
|
||||||
struct app_context
|
struct app_context
|
||||||
{
|
{
|
||||||
/// Default terminal attributes
|
char *attrs_defaults[ATTR_COUNT]; ///< Default terminal attributes
|
||||||
struct attrs theme_defaults[ATTR_COUNT];
|
|
||||||
|
|
||||||
// Configuration:
|
// Configuration:
|
||||||
|
|
||||||
struct config config; ///< Program configuration
|
struct config config; ///< Program configuration
|
||||||
struct attrs theme[ATTR_COUNT]; ///< Terminal attributes
|
char *attrs[ATTR_COUNT]; ///< Terminal attributes
|
||||||
bool isolate_buffers; ///< Isolate global/server buffers
|
bool isolate_buffers; ///< Isolate global/server buffers
|
||||||
bool beep_on_highlight; ///< Beep on highlight
|
bool beep_on_highlight; ///< Beep on highlight
|
||||||
bool logging; ///< Logging to file enabled
|
bool logging; ///< Logging to file enabled
|
||||||
@ -2350,6 +2305,11 @@ app_context_free (struct app_context *self)
|
|||||||
plugin_destroy (iter);
|
plugin_destroy (iter);
|
||||||
|
|
||||||
config_free (&self->config);
|
config_free (&self->config);
|
||||||
|
for (size_t i = 0; i < ATTR_COUNT; i++)
|
||||||
|
{
|
||||||
|
free (self->attrs_defaults[i]);
|
||||||
|
free (self->attrs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
LIST_FOR_EACH (struct buffer, iter, self->buffers)
|
LIST_FOR_EACH (struct buffer, iter, self->buffers)
|
||||||
{
|
{
|
||||||
@ -2407,7 +2367,7 @@ on_config_show_all_prefixes_change (struct config_item *item)
|
|||||||
|
|
||||||
static void on_config_relay_bind_change (struct config_item *item);
|
static void on_config_relay_bind_change (struct config_item *item);
|
||||||
static void on_config_backlog_limit_change (struct config_item *item);
|
static void on_config_backlog_limit_change (struct config_item *item);
|
||||||
static void on_config_theme_change (struct config_item *item);
|
static void on_config_attribute_change (struct config_item *item);
|
||||||
static void on_config_logging_change (struct config_item *item);
|
static void on_config_logging_change (struct config_item *item);
|
||||||
|
|
||||||
#define TRIVIAL_BOOLEAN_ON_CHANGE(name) \
|
#define TRIVIAL_BOOLEAN_ON_CHANGE(name) \
|
||||||
@ -2688,10 +2648,10 @@ static struct config_schema g_config_general[] =
|
|||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct config_schema g_config_theme[] =
|
static struct config_schema g_config_attributes[] =
|
||||||
{
|
{
|
||||||
#define XX(x, y, z) { .name = #y, .comment = #z, .type = CONFIG_ITEM_STRING, \
|
#define XX(x, y, z) { .name = #y, .comment = #z, .type = CONFIG_ITEM_STRING, \
|
||||||
.on_change = on_config_theme_change },
|
.on_change = on_config_attribute_change },
|
||||||
ATTR_TABLE (XX)
|
ATTR_TABLE (XX)
|
||||||
#undef XX
|
#undef XX
|
||||||
{}
|
{}
|
||||||
@ -2706,9 +2666,9 @@ load_config_general (struct config_item *subtree, void *user_data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
load_config_theme (struct config_item *subtree, void *user_data)
|
load_config_attributes (struct config_item *subtree, void *user_data)
|
||||||
{
|
{
|
||||||
config_schema_apply_to_object (g_config_theme, subtree, user_data);
|
config_schema_apply_to_object (g_config_attributes, subtree, user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -2720,7 +2680,7 @@ register_config_modules (struct app_context *ctx)
|
|||||||
config_register_module (config, "aliases", NULL, NULL);
|
config_register_module (config, "aliases", NULL, NULL);
|
||||||
config_register_module (config, "plugins", NULL, NULL);
|
config_register_module (config, "plugins", NULL, NULL);
|
||||||
config_register_module (config, "general", load_config_general, ctx);
|
config_register_module (config, "general", load_config_general, ctx);
|
||||||
config_register_module (config, "theme", load_config_theme, ctx);
|
config_register_module (config, "attributes", load_config_attributes, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
@ -3154,64 +3114,6 @@ relay_prepare_buffer_activate (struct app_context *ctx, struct buffer *buffer)
|
|||||||
e->buffer_name = str_from_cstr (buffer->name);
|
e->buffer_name = str_from_cstr (buffer->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static union relay_item_data *
|
|
||||||
relay_translate_formatter (struct app_context *ctx, union relay_item_data *p,
|
|
||||||
struct formatter_item *i)
|
|
||||||
{
|
|
||||||
// XXX: See attr_printer_decode_color(), this is a footgun.
|
|
||||||
int16_t c16 = i->color;
|
|
||||||
int16_t c256 = i->color >> 16;
|
|
||||||
|
|
||||||
unsigned attrs = i->attribute;
|
|
||||||
switch (i->type)
|
|
||||||
{
|
|
||||||
case FORMATTER_ITEM_TEXT:
|
|
||||||
p->text.text = str_from_cstr (i->text);
|
|
||||||
(p++)->kind = RELAY_ITEM_TEXT;
|
|
||||||
break;
|
|
||||||
case FORMATTER_ITEM_FG_COLOR:
|
|
||||||
p->fg_color.color = c256 <= 0 ? c16 : c256;
|
|
||||||
(p++)->kind = RELAY_ITEM_FG_COLOR;
|
|
||||||
break;
|
|
||||||
case FORMATTER_ITEM_BG_COLOR:
|
|
||||||
p->bg_color.color = c256 <= 0 ? c16 : c256;
|
|
||||||
(p++)->kind = RELAY_ITEM_BG_COLOR;
|
|
||||||
break;
|
|
||||||
case FORMATTER_ITEM_ATTR:
|
|
||||||
(p++)->kind = RELAY_ITEM_RESET;
|
|
||||||
if ((c256 = ctx->theme[i->attribute].fg) >= 0)
|
|
||||||
{
|
|
||||||
p->fg_color.color = c256;
|
|
||||||
(p++)->kind = RELAY_ITEM_FG_COLOR;
|
|
||||||
}
|
|
||||||
if ((c256 = ctx->theme[i->attribute].bg) >= 0)
|
|
||||||
{
|
|
||||||
p->bg_color.color = c256;
|
|
||||||
(p++)->kind = RELAY_ITEM_BG_COLOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs = ctx->theme[i->attribute].attrs;
|
|
||||||
// Fall-through
|
|
||||||
case FORMATTER_ITEM_SIMPLE:
|
|
||||||
if (attrs & TEXT_BOLD)
|
|
||||||
(p++)->kind = RELAY_ITEM_FLIP_BOLD;
|
|
||||||
if (attrs & TEXT_ITALIC)
|
|
||||||
(p++)->kind = RELAY_ITEM_FLIP_ITALIC;
|
|
||||||
if (attrs & TEXT_UNDERLINE)
|
|
||||||
(p++)->kind = RELAY_ITEM_FLIP_UNDERLINE;
|
|
||||||
if (attrs & TEXT_INVERSE)
|
|
||||||
(p++)->kind = RELAY_ITEM_FLIP_INVERSE;
|
|
||||||
if (attrs & TEXT_CROSSED_OUT)
|
|
||||||
(p++)->kind = RELAY_ITEM_FLIP_CROSSED_OUT;
|
|
||||||
if (attrs & TEXT_MONOSPACE)
|
|
||||||
(p++)->kind = RELAY_ITEM_FLIP_MONOSPACE;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer,
|
relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer,
|
||||||
struct buffer_line *line, bool leak_to_active)
|
struct buffer_line *line, bool leak_to_active)
|
||||||
@ -3230,10 +3132,49 @@ relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer,
|
|||||||
for (size_t i = 0; line->items[i].type; i++)
|
for (size_t i = 0; line->items[i].type; i++)
|
||||||
len++;
|
len++;
|
||||||
|
|
||||||
// Beware of the upper bound, currently dominated by FORMATTER_ITEM_ATTR.
|
// XXX: This way helps xP's JSON conversion, but is super annoying for us.
|
||||||
union relay_item_data *p = e->items = xcalloc (len * 9, sizeof *e->items);
|
union relay_item_data *p = e->items = xcalloc (len * 6, sizeof *e->items);
|
||||||
for (struct formatter_item *i = line->items; len--; i++)
|
for (struct formatter_item *i = line->items; len--; i++)
|
||||||
p = relay_translate_formatter (ctx, p, i);
|
{
|
||||||
|
// XXX: See attr_printer_decode_color(), this is a footgun.
|
||||||
|
int16_t c16 = i->color;
|
||||||
|
int16_t c256 = i->color >> 16;
|
||||||
|
switch (i->type)
|
||||||
|
{
|
||||||
|
case FORMATTER_ITEM_TEXT:
|
||||||
|
p->text.text = str_from_cstr (i->text);
|
||||||
|
(p++)->kind = RELAY_ITEM_TEXT;
|
||||||
|
break;
|
||||||
|
case FORMATTER_ITEM_ATTR:
|
||||||
|
// For future consideration.
|
||||||
|
(p++)->kind = RELAY_ITEM_RESET;
|
||||||
|
break;
|
||||||
|
case FORMATTER_ITEM_FG_COLOR:
|
||||||
|
p->fg_color.color = c256 <= 0 ? c16 : c256;
|
||||||
|
(p++)->kind = RELAY_ITEM_FG_COLOR;
|
||||||
|
break;
|
||||||
|
case FORMATTER_ITEM_BG_COLOR:
|
||||||
|
p->bg_color.color = c256 <= 0 ? c16 : c256;
|
||||||
|
(p++)->kind = RELAY_ITEM_BG_COLOR;
|
||||||
|
break;
|
||||||
|
case FORMATTER_ITEM_SIMPLE:
|
||||||
|
if (i->attribute & TEXT_BOLD)
|
||||||
|
(p++)->kind = RELAY_ITEM_FLIP_BOLD;
|
||||||
|
if (i->attribute & TEXT_ITALIC)
|
||||||
|
(p++)->kind = RELAY_ITEM_FLIP_ITALIC;
|
||||||
|
if (i->attribute & TEXT_UNDERLINE)
|
||||||
|
(p++)->kind = RELAY_ITEM_FLIP_UNDERLINE;
|
||||||
|
if (i->attribute & TEXT_INVERSE)
|
||||||
|
(p++)->kind = RELAY_ITEM_FLIP_INVERSE;
|
||||||
|
if (i->attribute & TEXT_CROSSED_OUT)
|
||||||
|
(p++)->kind = RELAY_ITEM_FLIP_CROSSED_OUT;
|
||||||
|
if (i->attribute & TEXT_MONOSPACE)
|
||||||
|
(p++)->kind = RELAY_ITEM_FLIP_MONOSPACE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
e->items_len = p - e->items;
|
e->items_len = p - e->items;
|
||||||
}
|
}
|
||||||
@ -3298,6 +3239,123 @@ get_attribute_printer (FILE *stream)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
vprint_attributed (struct app_context *ctx,
|
||||||
|
FILE *stream, intptr_t attribute, const char *fmt, va_list ap)
|
||||||
|
{
|
||||||
|
terminal_printer_fn printer = get_attribute_printer (stream);
|
||||||
|
if (!attribute)
|
||||||
|
printer = NULL;
|
||||||
|
|
||||||
|
if (printer)
|
||||||
|
tputs (ctx->attrs[attribute], 1, printer);
|
||||||
|
|
||||||
|
vfprintf (stream, fmt, ap);
|
||||||
|
|
||||||
|
if (printer)
|
||||||
|
tputs (ctx->attrs[ATTR_RESET], 1, printer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_attributed (struct app_context *ctx,
|
||||||
|
FILE *stream, intptr_t attribute, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start (ap, fmt);
|
||||||
|
vprint_attributed (ctx, stream, attribute, fmt, ap);
|
||||||
|
va_end (ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
log_message_attributed (void *user_data, const char *quote, const char *fmt,
|
||||||
|
va_list ap)
|
||||||
|
{
|
||||||
|
FILE *stream = stderr;
|
||||||
|
struct app_context *ctx = g_ctx;
|
||||||
|
|
||||||
|
CALL (ctx->input, hide);
|
||||||
|
|
||||||
|
print_attributed (ctx, stream, (intptr_t) user_data, "%s", quote);
|
||||||
|
vprint_attributed (ctx, stream, (intptr_t) user_data, fmt, ap);
|
||||||
|
fputs ("\n", stream);
|
||||||
|
|
||||||
|
CALL (ctx->input, show);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
attr_by_name (const char *name)
|
||||||
|
{
|
||||||
|
static const char *table[ATTR_COUNT] =
|
||||||
|
{
|
||||||
|
#define XX(x, y, z) [ATTR_ ## x] = #y,
|
||||||
|
ATTR_TABLE (XX)
|
||||||
|
#undef XX
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < N_ELEMENTS (table); i++)
|
||||||
|
if (!strcmp (name, table[i]))
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_config_attribute_change (struct config_item *item)
|
||||||
|
{
|
||||||
|
struct app_context *ctx = item->user_data;
|
||||||
|
ssize_t id = attr_by_name (item->schema->name);
|
||||||
|
if (id != -1)
|
||||||
|
{
|
||||||
|
cstr_set (&ctx->attrs[id], xstrdup (item->type == CONFIG_ITEM_NULL
|
||||||
|
? ctx->attrs_defaults[id]
|
||||||
|
: item->value.string.str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
init_colors (struct app_context *ctx)
|
||||||
|
{
|
||||||
|
bool have_ti = init_terminal ();
|
||||||
|
char **defaults = ctx->attrs_defaults;
|
||||||
|
|
||||||
|
#define INIT_ATTR(id, ti) defaults[ATTR_ ## id] = xstrdup (have_ti ? (ti) : "")
|
||||||
|
|
||||||
|
INIT_ATTR (PROMPT, enter_bold_mode);
|
||||||
|
INIT_ATTR (RESET, exit_attribute_mode);
|
||||||
|
INIT_ATTR (DATE_CHANGE, enter_bold_mode);
|
||||||
|
INIT_ATTR (READ_MARKER, g_terminal.color_set_fg[COLOR_MAGENTA]);
|
||||||
|
INIT_ATTR (WARNING, g_terminal.color_set_fg[COLOR_YELLOW]);
|
||||||
|
INIT_ATTR (ERROR, g_terminal.color_set_fg[COLOR_RED]);
|
||||||
|
|
||||||
|
INIT_ATTR (EXTERNAL, g_terminal.color_set_fg[COLOR_WHITE]);
|
||||||
|
INIT_ATTR (TIMESTAMP, g_terminal.color_set_fg[COLOR_WHITE]);
|
||||||
|
INIT_ATTR (ACTION, g_terminal.color_set_fg[COLOR_RED]);
|
||||||
|
INIT_ATTR (USERHOST, g_terminal.color_set_fg[COLOR_CYAN]);
|
||||||
|
INIT_ATTR (JOIN, g_terminal.color_set_fg[COLOR_GREEN]);
|
||||||
|
INIT_ATTR (PART, g_terminal.color_set_fg[COLOR_RED]);
|
||||||
|
|
||||||
|
char *highlight = have_ti ? xstrdup_printf ("%s%s%s",
|
||||||
|
g_terminal.color_set_fg[COLOR_YELLOW],
|
||||||
|
g_terminal.color_set_bg[COLOR_MAGENTA],
|
||||||
|
enter_bold_mode) : NULL;
|
||||||
|
INIT_ATTR (HIGHLIGHT, highlight);
|
||||||
|
free (highlight);
|
||||||
|
|
||||||
|
#undef INIT_ATTR
|
||||||
|
|
||||||
|
// This prevents formatters from obtaining an attribute printer function
|
||||||
|
if (!have_ti)
|
||||||
|
{
|
||||||
|
g_terminal.stdout_is_tty = false;
|
||||||
|
g_terminal.stderr_is_tty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_log_message_real = log_message_attributed;
|
||||||
|
|
||||||
|
// Apply the default values so that we start with any formatting at all
|
||||||
|
config_schema_call_changed
|
||||||
|
(config_item_get (ctx->config.root, "attributes", NULL));
|
||||||
|
}
|
||||||
|
|
||||||
// ~~~ Attribute printer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// ~~~ Attribute printer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
// A little tool that tries to make the most of the terminal's capabilities
|
// A little tool that tries to make the most of the terminal's capabilities
|
||||||
@ -3307,12 +3365,12 @@ get_attribute_printer (FILE *stream)
|
|||||||
|
|
||||||
struct attr_printer
|
struct attr_printer
|
||||||
{
|
{
|
||||||
struct attrs *attrs; ///< Named attributes
|
char **attrs; ///< Named attributes
|
||||||
FILE *stream; ///< Output stream
|
FILE *stream; ///< Output stream
|
||||||
bool dirty; ///< Attributes are set
|
bool dirty; ///< Attributes are set
|
||||||
};
|
};
|
||||||
|
|
||||||
#define ATTR_PRINTER_INIT(attrs, stream) { attrs, stream, true }
|
#define ATTR_PRINTER_INIT(ctx, stream) { ctx->attrs, stream, true }
|
||||||
|
|
||||||
static void
|
static void
|
||||||
attr_printer_filtered_puts (FILE *stream, const char *attr)
|
attr_printer_filtered_puts (FILE *stream, const char *attr)
|
||||||
@ -3350,11 +3408,22 @@ static void
|
|||||||
attr_printer_reset (struct attr_printer *self)
|
attr_printer_reset (struct attr_printer *self)
|
||||||
{
|
{
|
||||||
if (self->dirty)
|
if (self->dirty)
|
||||||
attr_printer_tputs (self, exit_attribute_mode);
|
attr_printer_tputs (self, self->attrs[ATTR_RESET]);
|
||||||
|
|
||||||
self->dirty = false;
|
self->dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
attr_printer_apply_named (struct attr_printer *self, int attribute)
|
||||||
|
{
|
||||||
|
attr_printer_reset (self);
|
||||||
|
if (attribute != ATTR_RESET)
|
||||||
|
{
|
||||||
|
attr_printer_tputs (self, self->attrs[attribute]);
|
||||||
|
self->dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: commonly terminals have:
|
// NOTE: commonly terminals have:
|
||||||
// 8 colours (worst, bright fg often with BOLD, bg sometimes with BLINK)
|
// 8 colours (worst, bright fg often with BOLD, bg sometimes with BLINK)
|
||||||
// 16 colours (okayish, we have the full basic range guaranteed)
|
// 16 colours (okayish, we have the full basic range guaranteed)
|
||||||
@ -3482,135 +3551,6 @@ attr_printer_apply (struct attr_printer *self,
|
|||||||
self->dirty = true;
|
self->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
attr_printer_apply_named (struct attr_printer *self, int attribute)
|
|
||||||
{
|
|
||||||
attr_printer_reset (self);
|
|
||||||
if (attribute == ATTR_RESET)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// See the COLOR_256 macro or attr_printer_decode_color().
|
|
||||||
struct attrs *a = &self->attrs[attribute];
|
|
||||||
attr_printer_apply (self, a->attrs,
|
|
||||||
a->fg < 16 ? a->fg : (a->fg << 16 | (-1 & 0xFFFF)),
|
|
||||||
a->bg < 16 ? a->bg : (a->bg << 16 | (-1 & 0xFFFF)));
|
|
||||||
self->dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ~~~ Logging redirect ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
static void
|
|
||||||
vprint_attributed (struct app_context *ctx,
|
|
||||||
FILE *stream, intptr_t attribute, const char *fmt, va_list ap)
|
|
||||||
{
|
|
||||||
terminal_printer_fn printer = get_attribute_printer (stream);
|
|
||||||
if (!attribute)
|
|
||||||
printer = NULL;
|
|
||||||
|
|
||||||
struct attr_printer state = ATTR_PRINTER_INIT (ctx->theme, stream);
|
|
||||||
if (printer)
|
|
||||||
attr_printer_apply_named (&state, attribute);
|
|
||||||
|
|
||||||
vfprintf (stream, fmt, ap);
|
|
||||||
|
|
||||||
if (printer)
|
|
||||||
attr_printer_reset (&state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
print_attributed (struct app_context *ctx,
|
|
||||||
FILE *stream, intptr_t attribute, const char *fmt, ...)
|
|
||||||
{
|
|
||||||
va_list ap;
|
|
||||||
va_start (ap, fmt);
|
|
||||||
vprint_attributed (ctx, stream, attribute, fmt, ap);
|
|
||||||
va_end (ap);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
log_message_attributed (void *user_data, const char *quote, const char *fmt,
|
|
||||||
va_list ap)
|
|
||||||
{
|
|
||||||
FILE *stream = stderr;
|
|
||||||
struct app_context *ctx = g_ctx;
|
|
||||||
|
|
||||||
CALL (ctx->input, hide);
|
|
||||||
|
|
||||||
print_attributed (ctx, stream, (intptr_t) user_data, "%s", quote);
|
|
||||||
vprint_attributed (ctx, stream, (intptr_t) user_data, fmt, ap);
|
|
||||||
fputs ("\n", stream);
|
|
||||||
|
|
||||||
CALL (ctx->input, show);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ~~~ Theme ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
static ssize_t
|
|
||||||
attr_by_name (const char *name)
|
|
||||||
{
|
|
||||||
static const char *table[ATTR_COUNT] =
|
|
||||||
{
|
|
||||||
NULL,
|
|
||||||
#define XX(x, y, z) [ATTR_ ## x] = #y,
|
|
||||||
ATTR_TABLE (XX)
|
|
||||||
#undef XX
|
|
||||||
};
|
|
||||||
|
|
||||||
for (size_t i = 1; i < N_ELEMENTS (table); i++)
|
|
||||||
if (!strcmp (name, table[i]))
|
|
||||||
return i;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
on_config_theme_change (struct config_item *item)
|
|
||||||
{
|
|
||||||
struct app_context *ctx = item->user_data;
|
|
||||||
ssize_t id = attr_by_name (item->schema->name);
|
|
||||||
if (id != -1)
|
|
||||||
{
|
|
||||||
// TODO: There should be a validator.
|
|
||||||
ctx->theme[id] = item->type == CONFIG_ITEM_NULL
|
|
||||||
? ctx->theme_defaults[id]
|
|
||||||
: attrs_decode (item->value.string.str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
init_colors (struct app_context *ctx)
|
|
||||||
{
|
|
||||||
bool have_ti = init_terminal ();
|
|
||||||
|
|
||||||
#define INIT_ATTR(id, ...) ctx->theme[ATTR_ ## id] = \
|
|
||||||
ctx->theme_defaults[ATTR_ ## id] = (struct attrs) { __VA_ARGS__ }
|
|
||||||
|
|
||||||
INIT_ATTR (PROMPT, -1, -1, TEXT_BOLD);
|
|
||||||
INIT_ATTR (RESET, -1, -1);
|
|
||||||
INIT_ATTR (DATE_CHANGE, -1, -1, TEXT_BOLD);
|
|
||||||
INIT_ATTR (READ_MARKER, COLOR_MAGENTA, -1);
|
|
||||||
INIT_ATTR (WARNING, COLOR_YELLOW, -1);
|
|
||||||
INIT_ATTR (ERROR, COLOR_RED, -1);
|
|
||||||
|
|
||||||
INIT_ATTR (EXTERNAL, COLOR_WHITE, -1);
|
|
||||||
INIT_ATTR (TIMESTAMP, COLOR_WHITE, -1);
|
|
||||||
INIT_ATTR (HIGHLIGHT, COLOR_BRIGHT (YELLOW), COLOR_MAGENTA, TEXT_BOLD);
|
|
||||||
INIT_ATTR (ACTION, COLOR_RED, -1);
|
|
||||||
INIT_ATTR (USERHOST, COLOR_CYAN, -1);
|
|
||||||
INIT_ATTR (JOIN, COLOR_GREEN, -1);
|
|
||||||
INIT_ATTR (PART, COLOR_RED, -1);
|
|
||||||
|
|
||||||
#undef INIT_ATTR
|
|
||||||
|
|
||||||
// This prevents formatters from obtaining an attribute printer function
|
|
||||||
if (!have_ti)
|
|
||||||
{
|
|
||||||
g_terminal.stdout_is_tty = false;
|
|
||||||
g_terminal.stderr_is_tty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_log_message_real = log_message_attributed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Helpers -----------------------------------------------------------------
|
// --- Helpers -----------------------------------------------------------------
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -4473,7 +4413,7 @@ formatter_flush (struct formatter *self, FILE *stream, int flush_opts)
|
|||||||
if (self->ctx->word_wrapping && !(flush_opts & FLUSH_OPT_NOWRAP))
|
if (self->ctx->word_wrapping && !(flush_opts & FLUSH_OPT_NOWRAP))
|
||||||
line = line_wrap (line, g_terminal.columns);
|
line = line_wrap (line, g_terminal.columns);
|
||||||
|
|
||||||
struct attr_printer state = ATTR_PRINTER_INIT (self->ctx->theme, stream);
|
struct attr_printer state = ATTR_PRINTER_INIT (self->ctx, stream);
|
||||||
struct line_char_attrs attrs = {}; // Won't compare equal to anything
|
struct line_char_attrs attrs = {}; // Won't compare equal to anything
|
||||||
LIST_FOR_EACH (struct line_char, c, line)
|
LIST_FOR_EACH (struct line_char, c, line)
|
||||||
{
|
{
|
||||||
@ -4485,10 +4425,10 @@ formatter_flush (struct formatter *self, FILE *stream, int flush_opts)
|
|||||||
formatter_putc (NULL, stream);
|
formatter_putc (NULL, stream);
|
||||||
|
|
||||||
attrs = c->attrs;
|
attrs = c->attrs;
|
||||||
if (attrs.named == -1)
|
if (attrs.named != -1)
|
||||||
attr_printer_apply (&state, attrs.text, attrs.fg, attrs.bg);
|
|
||||||
else
|
|
||||||
attr_printer_apply_named (&state, attrs.named);
|
attr_printer_apply_named (&state, attrs.named);
|
||||||
|
else
|
||||||
|
attr_printer_apply (&state, attrs.text, attrs.fg, attrs.bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
formatter_putc (c, stream);
|
formatter_putc (c, stream);
|
||||||
@ -6880,28 +6820,17 @@ on_refresh_prompt (struct app_context *ctx)
|
|||||||
char *localized = iconv_xstrdup (ctx->term_from_utf8, prompt.str, -1, NULL);
|
char *localized = iconv_xstrdup (ctx->term_from_utf8, prompt.str, -1, NULL);
|
||||||
str_free (&prompt);
|
str_free (&prompt);
|
||||||
|
|
||||||
// XXX: to be completely correct, we should use tputs, but we cannot
|
|
||||||
if (have_attributes)
|
if (have_attributes)
|
||||||
{
|
{
|
||||||
char buf[16384] = "";
|
// XXX: to be completely correct, we should use tputs, but we cannot
|
||||||
FILE *memfp = fmemopen (buf, sizeof buf - 1, "wb");
|
input_maybe_set_prompt (ctx->input, xstrdup_printf ("%c%s%c%s%c%s%c%s",
|
||||||
struct attr_printer state = { ctx->theme, memfp, false };
|
INPUT_START_IGNORE, ctx->attrs[ATTR_PROMPT],
|
||||||
|
INPUT_END_IGNORE,
|
||||||
fputc (INPUT_START_IGNORE, memfp);
|
localized,
|
||||||
attr_printer_apply_named (&state, ATTR_PROMPT);
|
INPUT_START_IGNORE, ctx->attrs[ATTR_RESET],
|
||||||
fputc (INPUT_END_IGNORE, memfp);
|
INPUT_END_IGNORE,
|
||||||
|
attributed_suffix));
|
||||||
fputs (localized, memfp);
|
|
||||||
free (localized);
|
free (localized);
|
||||||
|
|
||||||
fputc (INPUT_START_IGNORE, memfp);
|
|
||||||
attr_printer_reset (&state);
|
|
||||||
fputc (INPUT_END_IGNORE, memfp);
|
|
||||||
|
|
||||||
fputs (attributed_suffix, memfp);
|
|
||||||
|
|
||||||
fclose (memfp);
|
|
||||||
input_maybe_set_prompt (ctx->input, xstrdup (buf));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
input_maybe_set_prompt (ctx->input, localized);
|
input_maybe_set_prompt (ctx->input, localized);
|
||||||
|
173
xP/public/xP.js
173
xP/public/xP.js
@ -127,32 +127,6 @@ class RelayRpc extends EventTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Utilities --------------------------------------------------------------
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
const audioContext = new AudioContext()
|
|
||||||
|
|
||||||
function beep() {
|
|
||||||
let gain = audioContext.createGain()
|
|
||||||
gain.gain.value = 0.5
|
|
||||||
gain.connect(audioContext.destination)
|
|
||||||
|
|
||||||
let oscillator = audioContext.createOscillator()
|
|
||||||
oscillator.type = "triangle"
|
|
||||||
oscillator.frequency.value = 800
|
|
||||||
oscillator.connect(gain)
|
|
||||||
oscillator.start(audioContext.currentTime)
|
|
||||||
oscillator.stop(audioContext.currentTime + 0.1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Event processing -------------------------------------------------------
|
// ---- Event processing -------------------------------------------------------
|
||||||
|
|
||||||
let rpc = new RelayRpc(proxy)
|
let rpc = new RelayRpc(proxy)
|
||||||
@ -163,7 +137,7 @@ let bufferCurrent = undefined
|
|||||||
let bufferLog = undefined
|
let bufferLog = undefined
|
||||||
let bufferAutoscroll = true
|
let bufferAutoscroll = true
|
||||||
|
|
||||||
function bufferResetStats(b) {
|
function resetBufferStats(b) {
|
||||||
b.newMessages = 0
|
b.newMessages = 0
|
||||||
b.newUnimportantMessages = 0
|
b.newUnimportantMessages = 0
|
||||||
b.highlighted = false
|
b.highlighted = false
|
||||||
@ -173,29 +147,6 @@ function bufferActivate(name) {
|
|||||||
rpc.send({command: 'BufferActivate', bufferName: name})
|
rpc.send({command: 'BufferActivate', bufferName: name})
|
||||||
}
|
}
|
||||||
|
|
||||||
function bufferToggleLog() {
|
|
||||||
if (bufferLog) {
|
|
||||||
setTimeout(() =>
|
|
||||||
document.getElementById('input')?.focus())
|
|
||||||
|
|
||||||
bufferLog = undefined
|
|
||||||
m.redraw()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = bufferCurrent
|
|
||||||
rpc.send({
|
|
||||||
command: 'BufferLog',
|
|
||||||
bufferName: name,
|
|
||||||
}).then(resp => {
|
|
||||||
if (bufferCurrent !== name)
|
|
||||||
return
|
|
||||||
|
|
||||||
bufferLog = rpc.base64decode(resp.log)
|
|
||||||
m.redraw()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let connecting = true
|
let connecting = true
|
||||||
rpc.connect().then(result => {
|
rpc.connect().then(result => {
|
||||||
buffers.clear()
|
buffers.clear()
|
||||||
@ -223,12 +174,8 @@ rpc.addEventListener('Ping', event => {
|
|||||||
rpc.addEventListener('BufferUpdate', event => {
|
rpc.addEventListener('BufferUpdate', event => {
|
||||||
let e = event.detail, b = buffers.get(e.bufferName)
|
let e = event.detail, b = buffers.get(e.bufferName)
|
||||||
if (b === undefined) {
|
if (b === undefined) {
|
||||||
buffers.set(e.bufferName, (b = {
|
buffers.set(e.bufferName, (b = {lines: []}))
|
||||||
lines: [],
|
resetBufferStats(b)
|
||||||
history: [],
|
|
||||||
historyAt: 0,
|
|
||||||
}))
|
|
||||||
bufferResetStats(b)
|
|
||||||
}
|
}
|
||||||
b.hideUnimportant = e.hideUnimportant
|
b.hideUnimportant = e.hideUnimportant
|
||||||
})
|
})
|
||||||
@ -259,7 +206,7 @@ rpc.addEventListener('BufferRemove', event => {
|
|||||||
rpc.addEventListener('BufferActivate', event => {
|
rpc.addEventListener('BufferActivate', event => {
|
||||||
let old = buffers.get(bufferCurrent)
|
let old = buffers.get(bufferCurrent)
|
||||||
if (old !== undefined)
|
if (old !== undefined)
|
||||||
bufferResetStats(old)
|
resetBufferStats(old)
|
||||||
|
|
||||||
bufferLast = bufferCurrent
|
bufferLast = bufferCurrent
|
||||||
let e = event.detail, b = buffers.get(e.bufferName)
|
let e = event.detail, b = buffers.get(e.bufferName)
|
||||||
@ -272,21 +219,13 @@ rpc.addEventListener('BufferActivate', event => {
|
|||||||
return
|
return
|
||||||
|
|
||||||
textarea.focus()
|
textarea.focus()
|
||||||
if (old !== undefined) {
|
if (old !== undefined)
|
||||||
old.input = textarea.value
|
old.input = textarea.value
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (b !== undefined)
|
||||||
|
textarea.value = b.input || ''
|
||||||
|
else
|
||||||
textarea.value = ''
|
textarea.value = ''
|
||||||
if (b !== undefined && b.input !== undefined) {
|
|
||||||
textarea.value = b.input
|
|
||||||
textarea.setSelectionRange(b.inputStart, b.inputEnd, b.inputDirection)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
rpc.addEventListener('BufferLine', event => {
|
rpc.addEventListener('BufferLine', event => {
|
||||||
@ -326,11 +265,8 @@ rpc.addEventListener('BufferLine', event => {
|
|||||||
|
|
||||||
// TODO: Find some way of highlighting the tab in a browser.
|
// TODO: Find some way of highlighting the tab in a browser.
|
||||||
// TODO: Also highlight on unseen private messages, like xC does.
|
// TODO: Also highlight on unseen private messages, like xC does.
|
||||||
if (line.isHighlight) {
|
if (!visible && line.isHighlight)
|
||||||
beep()
|
|
||||||
if (!visible)
|
|
||||||
b.highlighted = true
|
b.highlighted = true
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
rpc.addEventListener('BufferClear', event => {
|
rpc.addEventListener('BufferClear', event => {
|
||||||
@ -371,11 +307,26 @@ let Toolbar = {
|
|||||||
bufferAutoscroll = !bufferAutoscroll
|
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 => {
|
view: vnode => {
|
||||||
return m('.toolbar', {}, [
|
return m('.toolbar', {}, [
|
||||||
m('button', {onclick: Toolbar.toggleAutoscroll},
|
m('button', {onclick: Toolbar.toggleAutoscroll},
|
||||||
bufferAutoscroll ? 'Scroll lock' : 'Scroll unlock'),
|
bufferAutoscroll ? 'Scroll lock' : 'Scroll unlock'),
|
||||||
m('button', {onclick: event => bufferToggleLog()},
|
m('button', {onclick: Toolbar.toggleLog},
|
||||||
bufferLog === undefined ? 'Show log' : 'Hide log'),
|
bufferLog === undefined ? 'Show log' : 'Hide log'),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
@ -549,8 +500,8 @@ let Buffer = {
|
|||||||
|
|
||||||
let Log = {
|
let Log = {
|
||||||
oncreate: vnode => {
|
oncreate: vnode => {
|
||||||
|
if (vnode.dom !== undefined)
|
||||||
vnode.dom.scrollTop = vnode.dom.scrollHeight
|
vnode.dom.scrollTop = vnode.dom.scrollHeight
|
||||||
vnode.dom.focus()
|
|
||||||
},
|
},
|
||||||
|
|
||||||
linkify: text => {
|
linkify: text => {
|
||||||
@ -620,74 +571,28 @@ let Input = {
|
|||||||
bufferName: bufferCurrent,
|
bufferName: bufferCurrent,
|
||||||
text: textarea.value,
|
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 = ''
|
textarea.value = ''
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
|
||||||
previous: textarea => {
|
|
||||||
let b = buffers.get(bufferCurrent)
|
|
||||||
if (b === undefined)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (b.historyAt > 0) {
|
|
||||||
if (b.historyAt == b.history.length)
|
|
||||||
b.input = textarea.value
|
|
||||||
textarea.value = b.history[--b.historyAt]
|
|
||||||
} else {
|
|
||||||
beep()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
|
|
||||||
next: textarea => {
|
|
||||||
let b = buffers.get(bufferCurrent)
|
|
||||||
if (b === undefined)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (b.historyAt < b.history.length) {
|
|
||||||
if (++b.historyAt == b.history.length)
|
|
||||||
textarea.value = b.input
|
|
||||||
else
|
|
||||||
textarea.value = b.history[b.historyAt]
|
|
||||||
} else {
|
|
||||||
beep()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
|
|
||||||
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'})
|
||||||
|
|
||||||
let textarea = event.currentTarget
|
let textarea = event.currentTarget
|
||||||
let handled = false
|
let handled = false
|
||||||
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) {
|
switch (event.keyCode) {
|
||||||
case 9:
|
case 9:
|
||||||
|
if (!event.ctrlKey && !event.metaKey && !event.altKey &&
|
||||||
|
!event.shiftKey)
|
||||||
handled = Input.complete(textarea)
|
handled = Input.complete(textarea)
|
||||||
break
|
break
|
||||||
case 13:
|
case 13:
|
||||||
|
if (!event.ctrlKey && !event.metaKey && !event.altKey &&
|
||||||
|
!event.shiftKey)
|
||||||
handled = Input.submit(textarea)
|
handled = Input.submit(textarea)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (handled)
|
if (handled)
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
},
|
},
|
||||||
@ -718,34 +623,26 @@ let Main = {
|
|||||||
window.addEventListener('load', () => m.mount(document.body, Main))
|
window.addEventListener('load', () => m.mount(document.body, Main))
|
||||||
|
|
||||||
document.addEventListener('keydown', event => {
|
document.addEventListener('keydown', event => {
|
||||||
if (rpc.ws == undefined || !hasShortcutModifiers(event))
|
if (rpc.ws == undefined || event.ctrlKey || event.metaKey)
|
||||||
return
|
return
|
||||||
|
|
||||||
switch (event.key) {
|
if (event.altKey && event.key == 'Tab') {
|
||||||
case 'Tab':
|
|
||||||
if (bufferLast !== undefined)
|
if (bufferLast !== undefined)
|
||||||
bufferActivate(bufferLast)
|
bufferActivate(bufferLast)
|
||||||
break
|
} else if (event.altKey && event.key == 'a') {
|
||||||
case 'h':
|
|
||||||
bufferToggleLog()
|
|
||||||
break
|
|
||||||
case 'a':
|
|
||||||
for (const [name, b] of buffers)
|
for (const [name, b] of buffers)
|
||||||
if (name !== bufferCurrent && b.newMessages) {
|
if (name !== bufferCurrent && b.newMessages) {
|
||||||
bufferActivate(name)
|
bufferActivate(name)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
break
|
} else if (event.altKey && event.key == '!') {
|
||||||
case '!':
|
|
||||||
for (const [name, b] of buffers)
|
for (const [name, b] of buffers)
|
||||||
if (name !== bufferCurrent && b.highlighted) {
|
if (name !== bufferCurrent && b.highlighted) {
|
||||||
bufferActivate(name)
|
bufferActivate(name)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
break
|
} else
|
||||||
default:
|
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user