Compare commits

...

6 Commits

Author SHA1 Message Date
57597bf8a2
xC: move TEXT_* constants where they belong 2022-08-27 15:06:28 +02:00
c0996fcbe7
xC: normalize BSD Editline's history behaviour
Now it's a realistically useful frontend.
2022-08-27 15:06:27 +02:00
03d8ea4c5a
xC: general.save_on_quit -> general.autosave
Power outages and similar situations make the former unreliable,
so get rid of any false promise it might seem to give.
2022-08-27 09:15:38 +02:00
dc002a2db4
xC: revise configuration options
This commit constitutes a breaking change to old configurations.

All behaviour.* options have now become general.*, with the following
few renames as exceptions:

 - editor_command -> editor
 - backlog_helper -> pager
 - backlog_helper_strip_formatting -> pager_strip_formatting
2022-08-27 09:15:37 +02:00
a32916ffcf
xC: label code sections better
Introduce tildes as a new sublevel of markers.
2022-08-27 09:15:37 +02:00
f7be510d26
xC: make fancy-prompt.lua alignment more reliable
And generally clean up that script.
2022-08-27 09:15:37 +02:00
5 changed files with 235 additions and 159 deletions

10
NEWS
View File

@ -1,6 +1,14 @@
Unreleased Unreleased
* xC: improved backlog helper integration capabilities * xC: all behaviour.* configuration options have been renamed to general.*,
with the exception of editor_command/editor, backlog_helper/pager,
and backlog_helper_strip_formatting/pager_strip_formatting
* xC: replaced behaviour.save_on_quit with general.autosave
* xC: improved pager integration capabilities
* xC: normalized editline's history behaviour, making it a viable frontend
* xC: made it show WALLOPS messages, as PRIVMSG for the server buffer * xC: made it show WALLOPS messages, as PRIVMSG for the server buffer

View File

@ -77,9 +77,6 @@ Runtime dependencies: openssl +
Additionally for 'xC': curses, libffi, lua >= 5.3 (optional), Additionally for 'xC': curses, libffi, lua >= 5.3 (optional),
readline >= 6.0 or libedit >= 2013-07-12 readline >= 6.0 or libedit >= 2013-07-12
Avoid libedit if you can, in general it works but at the moment history is
acting up and I have no clue about fixing it.
$ git clone --recursive https://git.janouch.name/p/xK.git $ git clone --recursive https://git.janouch.name/p/xK.git
$ mkdir xK/build $ mkdir xK/build
$ cd xK/build $ cd xK/build
@ -161,9 +158,9 @@ black-on-white and white-on-black terminals, or anything wild in between.
Assuming that your build supports Lua plugins, and that you have a decent, Assuming that your build supports Lua plugins, and that you have a decent,
properly set-up terminal emulator, it suffices to run: properly set-up terminal emulator, it suffices to run:
/set behaviour.backlog_helper = Press Tab here and change +Gb to +Gb1d /set general.pager = Press Tab here and change +Gb to +Gb1d
/set behaviour.date_change_line = "%a %e %b %Y" /set general.date_change_line = "%a %e %b %Y"
/set behaviour.plugin_autoload += "fancy-prompt.lua" /set general.plugin_autoload += "fancy-prompt.lua"
/set attributes.userhost = "\x1b[38;5;109m" /set attributes.userhost = "\x1b[38;5;109m"
/set attributes.join = "\x1b[38;5;108m" /set attributes.join = "\x1b[38;5;108m"
/set attributes.part = "\x1b[38;5;138m" /set attributes.part = "\x1b[38;5;138m"

View File

@ -1,7 +1,7 @@
-- --
-- fancy-prompt.lua: the fancy multiline prompt you probably want -- fancy-prompt.lua: the fancy multiline prompt you probably want
-- --
-- Copyright (c) 2016, Přemysl Eric Janouch <p@janouch.name> -- Copyright (c) 2016 - 2022, Přemysl Eric Janouch <p@janouch.name>
-- --
-- Permission to use, copy, modify, and/or distribute this software for any -- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted. -- purpose with or without fee is hereby granted.
@ -40,7 +40,7 @@ xC.hook_prompt (function (hook)
if buffer == current then if buffer == current then
current_n = i current_n = i
elseif buffer.new_messages_count ~= buffer.new_unimportant_count then elseif buffer.new_messages_count ~= buffer.new_unimportant_count then
if active ~= "" then active = active .. "," end active = active .. ","
if buffer.highlighted then if buffer.highlighted then
active = active .. "!" active = active .. "!"
bg_color = "224" bg_color = "224"
@ -48,7 +48,6 @@ xC.hook_prompt (function (hook)
active = active .. i active = active .. i
end end
end end
if active ~= "" then active = "(" .. active .. ")" end
local x = current_n .. ":" .. current.name local x = current_n .. ":" .. current.name
if chan and chan.users_len ~= 0 then if chan and chan.users_len ~= 0 then
local params = "" local params = ""
@ -56,25 +55,34 @@ xC.hook_prompt (function (hook)
params = params .. " +" .. mode .. " " .. param params = params .. " +" .. mode .. " " .. param
end end
local modes = chan.no_param_modes .. params:sub (3) local modes = chan.no_param_modes .. params:sub (3)
if modes ~= "" then x = x .. "(+" .. modes .. ")" end if modes ~= "" then
x = x .. "(+" .. modes .. ")"
end
x = x .. "{" .. chan.users_len .. "}" x = x .. "{" .. chan.users_len .. "}"
end end
if current.hide_unimportant then x = x .. "<H>" end if current.hide_unimportant then
x = x .. "<H>"
local lines, cols = xC.get_screen_size () end
x = x .. " " .. active .. string.rep (" ", cols) if active ~= "" then
x = x .. " (" .. active:sub (2) .. ")"
end
-- Readline 7.0.003 seems to be broken and completely corrupts the prompt. -- Readline 7.0.003 seems to be broken and completely corrupts the prompt.
-- However 8.0.004 seems to be fine with these, as is libedit 20191231-3.1. -- However 8.0.004 seems to be fine with these, as is libedit 20191231-3.1.
--x = x:gsub("[\128-\255]", "?") --x = x:gsub("[\128-\255]", "?")
-- Cut off extra characters and apply formatting, including the hack. -- Align to the terminal's width and apply formatting, including the hack.
-- FIXME: this doesn't count with full-width or zero-width characters. local lines, cols = xC.get_screen_size ()
-- We might want to export wcwidth() above term_from_utf8 somehow. local trailing, width = " ", xC.measure (x)
local overflow = utf8.offset (x, cols - 1) while cols > 0 and width >= cols do
if overflow then x = x:sub (1, overflow) end x = x:sub (1, utf8.offset (x, -1) - 1)
trailing, width = ">", xC.measure (x)
end
x = "\x01\x1b[0;4;1;38;5;16m\x1b[48;5;" .. bg_color .. "m\x02" .. x = "\x01\x1b[0;4;1;38;5;16m\x1b[48;5;" .. bg_color .. "m\x02" ..
x .. "\x01\x1b[0;4;1;7;38;5;" .. bg_color .. "m\x02 \x01\x1b[0;1m\x02" x .. string.rep (" ", cols - width - 1) ..
"\x01\x1b[0;4;1;7;38;5;" .. bg_color .. "m\x02" ..
trailing .. "\x01\x1b[0;1m\x02"
local user_prefix = function (chan, user) local user_prefix = function (chan, user)
for i, chan_user in ipairs (chan.users) do for i, chan_user in ipairs (chan.users) do

View File

@ -105,7 +105,7 @@ _~/.config/xC/xC.conf_::
as the */set* command, to make changes in it. as the */set* command, to make changes in it.
_~/.local/share/xC/logs/_:: _~/.local/share/xC/logs/_::
When enabled by *behaviour.logging*, log files are stored here. When enabled by *general.logging*, log files are stored here.
_~/.local/share/xC/plugins/_:: _~/.local/share/xC/plugins/_::
_/usr/local/share/xC/plugins/_:: _/usr/local/share/xC/plugins/_::
@ -114,8 +114,7 @@ _/usr/share/xC/plugins/_::
Bugs Bugs
---- ----
The editline (libedit) frontend is more of a proof of concept that mostly seems The editline (libedit) frontend may exhibit some unexpected behaviour.
to work but exhibits bugs that are not our fault.
Reporting bugs Reporting bugs
-------------- --------------

334
xC.c
View File

@ -259,7 +259,7 @@ struct input_vtable
XX (get_line) XX (clear_line) XX (insert) \ XX (get_line) XX (clear_line) XX (insert) \
XX (on_tty_resized) XX (on_tty_readable) XX (on_tty_resized) XX (on_tty_readable)
// --- GNU Readline ------------------------------------------------------------ // ~~~ GNU Readline ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#ifdef HAVE_READLINE #ifdef HAVE_READLINE
@ -728,7 +728,7 @@ input_rl_new (void)
#define input_new input_rl_new #define input_new input_rl_new
#endif // HAVE_READLINE #endif // HAVE_READLINE
// --- BSD Editline ------------------------------------------------------------ // ~~~ BSD Editline ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#ifdef HAVE_EDITLINE #ifdef HAVE_EDITLINE
@ -761,6 +761,7 @@ struct input_el
{ {
struct input super; ///< Parent class struct input super; ///< Parent class
EditLine *editline; ///< The EditLine object EditLine *editline; ///< The EditLine object
FILE *null; ///< Output redirect
bool active; ///< Are we a thing? bool active; ///< Are we a thing?
char *prompt; ///< The prompt we use char *prompt; ///< The prompt we use
@ -778,12 +779,12 @@ input_el__redisplay (void *input)
// See rl_redisplay(), however NetBSD editline's map.c v1.54 breaks VREPRINT // See rl_redisplay(), however NetBSD editline's map.c v1.54 breaks VREPRINT
// so we bind redisplay somewhere else in app_editline_init() // so we bind redisplay somewhere else in app_editline_init()
struct input_el *self = input; struct input_el *self = input;
char x[] = { 'q' & 31, 0 }; wchar_t x[] = { L'q' & 31, 0 };
el_push (self->editline, x); el_wpush (self->editline, x);
// We have to do this or it gets stuck and nothing is done // We have to do this or it gets stuck and nothing is done
int count = 0; int dummy_count = 0;
(void) el_wgets (self->editline, &count); (void) el_wgets (self->editline, &dummy_count);
} }
static char * static char *
@ -1026,18 +1027,50 @@ input_el__restore (struct input_el *self)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// XXX: Editline keeping its own history position (look for "eventno" there).
// Invoking ed-next-history through our bind from app_editline_init() seems
// like the only viable hack to get consistent history behaviour.
static void
input_el__bottom (struct input_el *self)
{
// First, we need to redirect output to avoid ringing the terminal bell.
FILE *out = NULL;
el_wget (self->editline, EL_GETFP, 1, &out);
el_wset (self->editline, EL_SETFP, 1, self->null);
// Invoke hist_get() to make the history pointer's cursor match "eventno".
int down = 1, dummy_count = 0;
el_wpush (self->editline, L"\x1bn");
(void) el_wgets (self->editline, &dummy_count);
// It doesn't seem like we can just retrieve the position.
HistEventW ev;
while (!history_w (self->current->history, &ev, H_PREV))
down++;
while (down--)
{
el_wpush (self->editline, L"\x1bn");
(void) el_wgets (self->editline, &dummy_count);
}
el_wset (self->editline, EL_SETFP, 1, out);
}
static void static void
input_el_buffer_switch (void *input, input_buffer_t input_buffer) input_el_buffer_switch (void *input, input_buffer_t input_buffer)
{ {
struct input_el *self = input; struct input_el *self = input;
struct input_el_buffer *buffer = input_buffer; struct input_el_buffer *buffer = input_buffer;
if (!self->active)
return;
if (self->current) if (self->current)
input_el__save_buffer (self, self->current); input_el__save_buffer (self, self->current);
input_el__restore_buffer (self, buffer);
el_wset (self->editline, EL_HIST, history, buffer->history);
self->current = buffer; self->current = buffer;
el_wset (self->editline, EL_HIST, history, buffer->history);
input_el__bottom (self);
input_el__restore_buffer (self, buffer);
} }
static void static void
@ -1111,7 +1144,7 @@ input_el_on_tty_readable (void *input)
if (!buf || count-- <= 0) if (!buf || count-- <= 0)
return; return;
if (count == 0 && buf[0] == ('D' - 0x40) /* hardcoded VEOF in editline */) if (count == 0 && buf[0] == ('D' - 0x40) /* hardcoded VEOF in el_wgets() */)
{ {
el_deletestr (self->editline, 1); el_deletestr (self->editline, 1);
input_el__redisplay (self); input_el__redisplay (self);
@ -1131,6 +1164,7 @@ input_el_destroy (void *input)
free (iter->help); free (iter->help);
ffi_closure_free (iter); ffi_closure_free (iter);
} }
fclose (self->null);
free (self->prompt); free (self->prompt);
free (self); free (self);
} }
@ -1144,6 +1178,7 @@ input_el_new (void)
{ {
struct input_el *self = xcalloc (1, sizeof *self); struct input_el *self = xcalloc (1, sizeof *self);
self->super.vtable = &input_el_vtable; self->super.vtable = &input_el_vtable;
self->null = fopen ("/dev/null", "w");
return &self->super; return &self->super;
} }
@ -1160,7 +1195,7 @@ input_el_new (void)
// //
// The only exception is IRC identifiers. // The only exception is IRC identifiers.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // ~~~ Scripting support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// We need a few reference countable objects with support for both strong // We need a few reference countable objects with support for both strong
// and weak references (mainly used for scripted plugins). // and weak references (mainly used for scripted plugins).
@ -1272,7 +1307,7 @@ struct ispect_field
{ #field, offsetof (struct object, field), ISPECT_STR_MAP, \ { #field, offsetof (struct object, field), ISPECT_STR_MAP, \
ISPECT_REF, g_##ref_type##_ispect, is_list }, ISPECT_REF, g_##ref_type##_ispect, is_list },
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // ~~~ Chat ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
struct user_channel struct user_channel
{ {
@ -1431,7 +1466,7 @@ channel_destroy (struct channel *self)
REF_COUNTABLE_METHODS (channel) REF_COUNTABLE_METHODS (channel)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // ~~~ Buffers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
enum formatter_item_type enum formatter_item_type
{ {
@ -1444,10 +1479,21 @@ 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
int attribute : 16; ///< Attribute ID int attribute : 16; ///< Attribute ID or a TEXT_* mask
int color; ///< Colour int color; ///< Colour
char *text; ///< String char *text; ///< String
}; };
@ -1627,7 +1673,7 @@ buffer_destroy (struct buffer *self)
REF_COUNTABLE_METHODS (buffer) REF_COUNTABLE_METHODS (buffer)
#define buffer_ref do_not_use_dangerous #define buffer_ref do_not_use_dangerous
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // ~~~ Server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// The only real purpose of this is to abstract away TLS // The only real purpose of this is to abstract away TLS
struct transport struct transport
@ -1903,7 +1949,7 @@ server_destroy (struct server *self)
REF_COUNTABLE_METHODS (server) REF_COUNTABLE_METHODS (server)
#define server_ref do_not_use_dangerous #define server_ref do_not_use_dangerous
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // ~~~ Scripting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
struct plugin struct plugin
{ {
@ -2022,7 +2068,7 @@ struct completion_hook
struct completion *data, const char *word, struct strv *output); struct completion *data, const char *word, struct strv *output);
}; };
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // ~~~ Main context ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
struct app_context struct app_context
{ {
@ -2085,7 +2131,7 @@ struct app_context
bool in_bracketed_paste; ///< User is pasting some content bool in_bracketed_paste; ///< User is pasting some content
struct str input_buffer; ///< Buffered pasted content struct str input_buffer; ///< Buffered pasted content
bool running_backlog_helper; ///< Running a backlog helper bool running_pager; ///< Running a pager for buffer history
bool running_editor; ///< Running editor for the input bool running_editor; ///< Running editor for the input
char *editor_filename; ///< The file being edited by user char *editor_filename; ///< The file being edited by user
int terminal_suspended; ///< Terminal suspension level int terminal_suspended; ///< Terminal suspension level
@ -2420,18 +2466,65 @@ static struct config_schema g_config_server[] =
{} {}
}; };
static struct config_schema g_config_behaviour[] = static struct config_schema g_config_general[] =
{ {
{ .name = "autosave",
.comment = "Save configuration automatically after each change",
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "on" },
{ .name = "debug_mode",
.comment = "Produce some debugging output",
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "off",
.on_change = on_config_debug_mode_change },
{ .name = "logging",
.comment = "Log buffer contents to file",
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "off",
.on_change = on_config_logging_change },
{ .name = "plugin_autoload",
.comment = "Plugins to automatically load on start",
.type = CONFIG_ITEM_STRING_ARRAY,
.validate = config_validate_nonjunk_string },
// Buffer history:
{ .name = "backlog_limit",
.comment = "Maximum number of lines stored in the backlog",
.type = CONFIG_ITEM_INTEGER,
.validate = config_validate_nonnegative,
.default_ = "1000",
.on_change = on_config_backlog_limit_change },
{ .name = "pager",
.comment = "Shell command to page buffer history (args: name [path])",
.type = CONFIG_ITEM_STRING,
.default_ = "`name=$(echo \"$1\" | sed 's/[%?:.]/\\\\&/g'); "
"prompt='?f%F:'$name'. ?db- page %db?L of %D. .(?eEND:?PB%PB\\%..)'; "
"LESSSECURE=1 less +Gb -Ps\"$prompt\" \"${2:--R}\"`" },
{ .name = "pager_strip_formatting",
.comment = "Strip terminal formatting from pager input",
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "off" },
// Output adjustments:
{ .name = "beep_on_highlight",
.comment = "Ring the bell when highlighted or on a new invisible PM",
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "on",
.on_change = on_config_beep_on_highlight_change },
{ .name = "date_change_line",
.comment = "Input to strftime(3) for the date change line",
.type = CONFIG_ITEM_STRING,
.default_ = "\"%F\"" },
{ .name = "isolate_buffers", { .name = "isolate_buffers",
.comment = "Don't leak messages from the server and global buffers", .comment = "Don't leak messages from the server and global buffers",
.type = CONFIG_ITEM_BOOLEAN, .type = CONFIG_ITEM_BOOLEAN,
.default_ = "off", .default_ = "off",
.on_change = on_config_isolate_buffers_change }, .on_change = on_config_isolate_buffers_change },
{ .name = "beep_on_highlight", { .name = "read_marker_char",
.comment = "Beep when highlighted or on a new invisible PM", .comment = "The character to use for the read marker line",
.type = CONFIG_ITEM_BOOLEAN, .type = CONFIG_ITEM_STRING,
.default_ = "on", .default_ = "\"-\"",
.on_change = on_config_beep_on_highlight_change }, .validate = config_validate_nonjunk_string },
{ .name = "show_all_prefixes", { .name = "show_all_prefixes",
.comment = "Show all prefixes in front of nicknames", .comment = "Show all prefixes in front of nicknames",
.type = CONFIG_ITEM_BOOLEAN, .type = CONFIG_ITEM_BOOLEAN,
@ -2442,7 +2535,9 @@ static struct config_schema g_config_behaviour[] =
.type = CONFIG_ITEM_BOOLEAN, .type = CONFIG_ITEM_BOOLEAN,
.default_ = "on", .default_ = "on",
.on_change = on_config_word_wrapping_change }, .on_change = on_config_word_wrapping_change },
{ .name = "editor_command",
// User input:
{ .name = "editor",
.comment = "VIM: \"vim +%Bgo %F\", Emacs: \"emacs -nw +%L:%C %F\", " .comment = "VIM: \"vim +%Bgo %F\", Emacs: \"emacs -nw +%L:%C %F\", "
"nano/micro/kakoune: \"nano/micro/kak +%L:%C %F\"", "nano/micro/kakoune: \"nano/micro/kak +%L:%C %F\"",
.type = CONFIG_ITEM_STRING }, .type = CONFIG_ITEM_STRING },
@ -2450,58 +2545,8 @@ static struct config_schema g_config_behaviour[] =
.comment = "Normalize newlines and quote the command prefix in pastes", .comment = "Normalize newlines and quote the command prefix in pastes",
.type = CONFIG_ITEM_BOOLEAN, .type = CONFIG_ITEM_BOOLEAN,
.default_ = "on" }, .default_ = "on" },
{ .name = "date_change_line",
.comment = "Input to strftime(3) for the date change line",
.type = CONFIG_ITEM_STRING,
.default_ = "\"%F\"" },
{ .name = "read_marker_char",
.comment = "The character to use for the read marker line",
.type = CONFIG_ITEM_STRING,
.default_ = "\"-\"",
.validate = config_validate_nonjunk_string },
{ .name = "logging",
.comment = "Log buffer contents to file",
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "off",
.on_change = on_config_logging_change },
{ .name = "save_on_quit",
.comment = "Save configuration before quitting",
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "on" },
{ .name = "debug_mode",
.comment = "Produce some debugging output",
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "off",
.on_change = on_config_debug_mode_change },
{ .name = "backlog_limit",
.comment = "Maximum number of lines stored in the backlog",
.type = CONFIG_ITEM_INTEGER,
.validate = config_validate_nonnegative,
.default_ = "1000",
.on_change = on_config_backlog_limit_change },
{ .name = "backlog_helper",
.comment = "Shell command to page buffer history (args: name [path])",
.type = CONFIG_ITEM_STRING,
.default_ = "`name=$(echo \"$1\" | sed 's/[%?:.]/\\\\&/g'); "
"prompt='?f%F:'$name'. ?db- page %db?L of %D. .(?eEND:?PB%PB\\%..)'; "
"LESSSECURE=1 less +Gb -Ps\"$prompt\" \"${2:--R}\"`" },
{ .name = "backlog_helper_strip_formatting",
.comment = "Strip formatting from backlog helper input",
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "off" },
{ .name = "reconnect_delay_growing",
.comment = "Growing factor for reconnect delay",
.type = CONFIG_ITEM_INTEGER,
.validate = config_validate_nonnegative,
.default_ = "2" },
{ .name = "reconnect_delay_max",
.comment = "Maximum reconnect delay in seconds",
.type = CONFIG_ITEM_INTEGER,
.validate = config_validate_nonnegative,
.default_ = "600" },
// Pan-server configuration:
{ .name = "autoaway_message", { .name = "autoaway_message",
.comment = "Automated away message", .comment = "Automated away message",
.type = CONFIG_ITEM_STRING, .type = CONFIG_ITEM_STRING,
@ -2511,11 +2556,16 @@ static struct config_schema g_config_behaviour[] =
.type = CONFIG_ITEM_INTEGER, .type = CONFIG_ITEM_INTEGER,
.validate = config_validate_nonnegative, .validate = config_validate_nonnegative,
.default_ = "1800" }, .default_ = "1800" },
{ .name = "reconnect_delay_growing",
{ .name = "plugin_autoload", .comment = "Growth factor for the reconnect delay",
.comment = "Plugins to automatically load on start", .type = CONFIG_ITEM_INTEGER,
.type = CONFIG_ITEM_STRING_ARRAY, .validate = config_validate_nonnegative,
.validate = config_validate_nonjunk_string }, .default_ = "2" },
{ .name = "reconnect_delay_max",
.comment = "Maximum reconnect delay in seconds",
.type = CONFIG_ITEM_INTEGER,
.validate = config_validate_nonnegative,
.default_ = "600" },
{} {}
}; };
@ -2531,9 +2581,9 @@ static struct config_schema g_config_attributes[] =
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void static void
load_config_behaviour (struct config_item *subtree, void *user_data) load_config_general (struct config_item *subtree, void *user_data)
{ {
config_schema_apply_to_object (g_config_behaviour, subtree, user_data); config_schema_apply_to_object (g_config_general, subtree, user_data);
} }
static void static void
@ -2550,7 +2600,7 @@ register_config_modules (struct app_context *ctx)
config_register_module (config, "servers", NULL, NULL); config_register_module (config, "servers", NULL, NULL);
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, "behaviour", load_config_behaviour, ctx); config_register_module (config, "general", load_config_general, ctx);
config_register_module (config, "attributes", load_config_attributes, ctx); config_register_module (config, "attributes", load_config_attributes, ctx);
} }
@ -2785,24 +2835,13 @@ init_colors (struct app_context *ctx)
(config_item_get (ctx->config.root, "attributes", NULL)); (config_item_get (ctx->config.root, "attributes", NULL));
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // ~~~ 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
// to set up text attributes. It mostly targets just terminal emulators as that // to set up text attributes. It mostly targets just terminal emulators as that
// is what people are using these days. At least no stupid ncurses limits us // is what people are using these days. At least no stupid ncurses limits us
// with colour pairs. // with colour pairs.
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 attr_printer struct attr_printer
{ {
char **attrs; ///< Named attributes char **attrs; ///< Named attributes
@ -3776,7 +3815,7 @@ explode_text (struct exploder *self, const char *text)
if (!strchr ("\a\b\x0e\x0f\x1b" /* BEL BS SO SI ESC */, *p)) if (!strchr ("\a\b\x0e\x0f\x1b" /* BEL BS SO SI ESC */, *p))
str_append_c (&filtered, *p); str_append_c (&filtered, *p);
size_t term_len = 0; size_t term_len = 0, processed = 0, len;
char *term = iconv_xstrdup (self->ctx->term_from_utf8, char *term = iconv_xstrdup (self->ctx->term_from_utf8,
filtered.str, filtered.len + 1, &term_len); filtered.str, filtered.len + 1, &term_len);
str_free (&filtered); str_free (&filtered);
@ -3785,11 +3824,10 @@ explode_text (struct exploder *self, const char *text)
memset (&ps, 0, sizeof ps); memset (&ps, 0, sizeof ps);
wchar_t wch; wchar_t wch;
size_t len, processed = 0;
while ((len = mbrtowc (&wch, term + processed, term_len - processed, &ps))) while ((len = mbrtowc (&wch, term + processed, term_len - processed, &ps)))
{ {
hard_assert (len != (size_t) -2 && len != (size_t) -1); hard_assert (len != (size_t) -2 && len != (size_t) -1);
processed += len; hard_assert ((processed += len) <= term_len);
struct line_char *c = line_char_new (wch); struct line_char *c = line_char_new (wch);
c->attrs = self->attrs; c->attrs = self->attrs;
@ -3924,7 +3962,7 @@ buffer_update_time (struct app_context *ctx, time_t now, FILE *stream,
char buf[64] = ""; char buf[64] = "";
const char *format = const char *format =
get_config_string (ctx->config.root, "behaviour.date_change_line"); get_config_string (ctx->config.root, "general.date_change_line");
if (!strftime (buf, sizeof buf, format, &current)) if (!strftime (buf, sizeof buf, format, &current))
{ {
print_error ("%s: %s", "strftime", strerror (errno)); print_error ("%s: %s", "strftime", strerror (errno));
@ -4301,8 +4339,8 @@ buffer_print_read_marker (struct app_context *ctx, FILE *stream, int flush_opts)
{ {
struct formatter f = formatter_make (ctx, NULL); struct formatter f = formatter_make (ctx, NULL);
const int timestamp_width = 8; // hardcoded to %T right now, simple const int timestamp_width = 8; // hardcoded to %T right now, simple
const char *marker_char = get_config_string (ctx->config.root, const char *marker_char =
"behaviour.read_marker_char"); get_config_string (ctx->config.root, "general.read_marker_char");
// We could turn this off on FLUSH_OPT_NOWRAP, however our default pager // We could turn this off on FLUSH_OPT_NOWRAP, however our default pager
// wraps lines for us even if we don't do it ourselves, and thus there's // wraps lines for us even if we don't do it ourselves, and thus there's
@ -4912,8 +4950,8 @@ static int64_t
irc_get_reconnect_delay (struct server *s) irc_get_reconnect_delay (struct server *s)
{ {
int64_t delay = get_config_integer (s->config, "reconnect_delay"); int64_t delay = get_config_integer (s->config, "reconnect_delay");
int64_t delay_factor = get_config_integer (s->ctx->config.root, int64_t delay_factor = get_config_integer
"behaviour.reconnect_delay_growing"); (s->ctx->config.root, "general.reconnect_delay_growing");
for (unsigned i = 0; i < s->reconnect_attempt; i++) for (unsigned i = 0; i < s->reconnect_attempt; i++)
{ {
if (delay_factor && delay > INT64_MAX / delay_factor) if (delay_factor && delay > INT64_MAX / delay_factor)
@ -4921,8 +4959,8 @@ irc_get_reconnect_delay (struct server *s)
delay *= delay_factor; delay *= delay_factor;
} }
int64_t delay_max = get_config_integer (s->ctx->config.root, int64_t delay_max =
"behaviour.reconnect_delay_max"); get_config_integer (s->ctx->config.root, "general.reconnect_delay_max");
return MIN (delay, delay_max); return MIN (delay, delay_max);
} }
@ -10527,6 +10565,33 @@ lua_plugin_get_screen_size (lua_State *L)
return 2; return 2;
} }
static int
lua_plugin_measure (lua_State *L)
{
struct lua_plugin *plugin = lua_touserdata (L, lua_upvalueindex (1));
const char *line = lua_plugin_check_utf8 (L, 1);
size_t term_len = 0, processed = 0, width = 0, len;
char *term = iconv_xstrdup (plugin->ctx->term_from_utf8,
(char *) line, strlen (line) + 1, &term_len);
mbstate_t ps;
memset (&ps, 0, sizeof ps);
wchar_t wch;
while ((len = mbrtowc (&wch, term + processed, term_len - processed, &ps)))
{
hard_assert (len != (size_t) -2 && len != (size_t) -1);
hard_assert ((processed += len) <= term_len);
int wch_width = wcwidth (wch);
width += MAX (0, wch_width);
}
free (term);
lua_pushinteger (L, width);
return 1;
}
static int static int
lua_ctx_gc (lua_State *L) lua_ctx_gc (lua_State *L)
{ {
@ -10547,6 +10612,7 @@ static luaL_Reg lua_plugin_library[] =
// And these are methods: // And these are methods:
{ "get_screen_size", lua_plugin_get_screen_size }, { "get_screen_size", lua_plugin_get_screen_size },
{ "measure", lua_plugin_measure },
{ "__gc", lua_ctx_gc }, { "__gc", lua_ctx_gc },
{ NULL, NULL }, { NULL, NULL },
}; };
@ -10965,8 +11031,8 @@ plugin_unload (struct app_context *ctx, const char *name)
static void static void
load_plugins (struct app_context *ctx) load_plugins (struct app_context *ctx)
{ {
const char *plugins = get_config_string const char *plugins =
(ctx->config.root, "behaviour.plugin_autoload"); get_config_string (ctx->config.root, "general.plugin_autoload");
if (plugins) if (plugins)
{ {
struct strv v = strv_make (); struct strv v = strv_make ();
@ -11332,6 +11398,8 @@ static bool
handle_command_set_assign handle_command_set_assign
(struct app_context *ctx, struct strv *all, char *arguments) (struct app_context *ctx, struct strv *all, char *arguments)
{ {
hard_assert (all->len > 0);
char *op = cut_word (&arguments); char *op = cut_word (&arguments);
bool add = false; bool add = false;
bool remove = false; bool remove = false;
@ -11366,6 +11434,9 @@ handle_command_set_assign
free (key); free (key);
} }
config_item_destroy (new_); config_item_destroy (new_);
if (get_config_boolean (ctx->config.root, "general.autosave"))
save_configuration (ctx);
return true; return true;
} }
@ -13206,8 +13277,7 @@ static struct strv
build_editor_command (struct app_context *ctx, const char *filename) build_editor_command (struct app_context *ctx, const char *filename)
{ {
struct strv argv = strv_make (); struct strv argv = strv_make ();
const char *editor = get_config_string const char *editor = get_config_string (ctx->config.root, "general.editor");
(ctx->config.root, "behaviour.editor_command");
if (!editor) if (!editor)
{ {
const char *command; const char *command;
@ -13215,8 +13285,8 @@ build_editor_command (struct app_context *ctx, const char *filename)
&& !(command = getenv ("EDITOR"))) && !(command = getenv ("EDITOR")))
command = "vi"; command = "vi";
// Although most visual editors support a "+LINE" argument (every // Although most visual editors support a "+LINE" argument
// editor mentioned in the default value of behaviour.editor_command, // (every editor mentioned in the default value of general.editor,
// plus vi, mcedit, vis, ...), it isn't particularly useful by itself. // plus vi, mcedit, vis, ...), it isn't particularly useful by itself.
// We need to be able to specify the column number. // We need to be able to specify the column number.
// //
@ -13376,28 +13446,27 @@ input_editor_cleanup (struct app_context *ctx)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void static void
launch_backlog_helper (struct app_context *ctx, int backlog_fd, launch_pager (struct app_context *ctx,
const char *name, const char *path) int fd, const char *name, const char *path)
{ {
hard_assert (!ctx->running_backlog_helper); hard_assert (!ctx->running_pager);
switch (spawn_helper_child (ctx)) switch (spawn_helper_child (ctx))
{ {
case 0: case 0:
dup2 (backlog_fd, STDIN_FILENO); dup2 (fd, STDIN_FILENO);
char *localized_name = char *localized_name =
iconv_xstrdup (ctx->term_from_utf8, (char *) name, -1, NULL); iconv_xstrdup (ctx->term_from_utf8, (char *) name, -1, NULL);
execl ("/bin/sh", "/bin/sh", "-c", execl ("/bin/sh", "/bin/sh", "-c",
get_config_string (ctx->config.root, "behaviour.backlog_helper"), get_config_string (ctx->config.root, "general.pager"),
PROGRAM_NAME, localized_name, path, NULL); PROGRAM_NAME, localized_name, path, NULL);
print_error ("%s: %s", print_error ("%s: %s", "Failed to launch pager", strerror (errno));
"Failed to launch backlog helper", strerror (errno));
_exit (EXIT_FAILURE); _exit (EXIT_FAILURE);
case -1: case -1:
log_global_error (ctx, "#s: #l", log_global_error (ctx, "#s: #l",
"Failed to launch backlog helper", strerror (errno)); "Failed to launch pager", strerror (errno));
break; break;
default: default:
ctx->running_backlog_helper = true; ctx->running_pager = true;
} }
} }
@ -13413,7 +13482,7 @@ display_backlog (struct app_context *ctx, int flush_opts)
} }
if (!get_config_boolean (ctx->config.root, if (!get_config_boolean (ctx->config.root,
"behaviour.backlog_helper_strip_formatting")) "general.pager_strip_formatting"))
flush_opts |= FLUSH_OPT_RAW; flush_opts |= FLUSH_OPT_RAW;
struct buffer *buffer = ctx->current_buffer; struct buffer *buffer = ctx->current_buffer;
@ -13433,7 +13502,7 @@ display_backlog (struct app_context *ctx, int flush_opts)
rewind (backlog); rewind (backlog);
set_cloexec (fileno (backlog)); set_cloexec (fileno (backlog));
launch_backlog_helper (ctx, fileno (backlog), buffer->name, NULL); launch_pager (ctx, fileno (backlog), buffer->name, NULL);
fclose (backlog); fclose (backlog);
return true; return true;
} }
@ -13477,7 +13546,7 @@ on_display_full_log (int count, int key, void *user_data)
(void) fflush (buffer->log_file); (void) fflush (buffer->log_file);
set_cloexec (fileno (full_log)); set_cloexec (fileno (full_log));
launch_backlog_helper (ctx, fileno (full_log), buffer->name, path); launch_pager (ctx, fileno (full_log), buffer->name, path);
fclose (full_log); fclose (full_log);
free (path); free (path);
return true; return true;
@ -13874,13 +13943,10 @@ on_editline_return (EditLine *editline, int key)
wchar_t *line = calloc (sizeof *info->buffer, len + 1); wchar_t *line = calloc (sizeof *info->buffer, len + 1);
memcpy (line, info->buffer, sizeof *info->buffer * len); memcpy (line, info->buffer, sizeof *info->buffer * len);
// XXX: Editline seems to remember its position in history,
// so it's not going to work as you'd expect it to
if (*line) if (*line)
{ {
HistEventW ev; HistEventW ev;
history_w (self->current->history, &ev, H_ENTER, line); history_w (self->current->history, &ev, H_ENTER, line);
print_debug ("history: %d %ls", ev.num, ev.str);
} }
free (line); free (line);
@ -13892,6 +13958,7 @@ on_editline_return (EditLine *editline, int key)
el_cursor (editline, len - point); el_cursor (editline, len - point);
el_wdeletestr (editline, len); el_wdeletestr (editline, len);
input_el__bottom (self);
return CC_REFRESH; return CC_REFRESH;
} }
@ -13940,7 +14007,7 @@ static const char *g_first_time_help[] =
"", "",
"To get a list of all commands, type \x02/help\x02. To obtain", "To get a list of all commands, type \x02/help\x02. To obtain",
"more information on a command or option, simply add it as", "more information on a command or option, simply add it as",
"a parameter, e.g. \x02/help set\x02 or \x02/help behaviour.logging\x02.", "a parameter, e.g. \x02/help set\x02 or \x02/help general.logging\x02.",
"", "",
"To switch between buffers, press \x02" "To switch between buffers, press \x02"
"F5/Ctrl-P\x02 or \x02" "F6/Ctrl-N\x02.", "F5/Ctrl-P\x02 or \x02" "F6/Ctrl-N\x02.",
@ -14147,8 +14214,8 @@ try_reap_child (struct app_context *ctx)
return true; return true;
} }
if (ctx->running_backlog_helper) if (ctx->running_pager)
ctx->running_backlog_helper = false; ctx->running_pager = false;
else if (!ctx->running_editor) else if (!ctx->running_editor)
{ {
log_global_debug (ctx, "An unknown child has died"); log_global_debug (ctx, "An unknown child has died");
@ -14265,7 +14332,7 @@ done:
static bool static bool
insert_paste (struct app_context *ctx, char *paste, size_t len) insert_paste (struct app_context *ctx, char *paste, size_t len)
{ {
if (!get_config_boolean (ctx->config.root, "behaviour.process_pasted_text")) if (!get_config_boolean (ctx->config.root, "general.process_pasted_text"))
return CALL_ (ctx->input, insert, paste); return CALL_ (ctx->input, insert, paste);
// Without ICRNL, which Editline keeps but Readline doesn't, // Without ICRNL, which Editline keeps but Readline doesn't,
@ -14352,7 +14419,7 @@ reset_autoaway (struct app_context *ctx)
// And potentially start a new auto-away timer // And potentially start a new auto-away timer
int64_t delay = get_config_integer int64_t delay = get_config_integer
(ctx->config.root, "behaviour.autoaway_delay"); (ctx->config.root, "general.autoaway_delay");
if (delay) if (delay)
poller_timer_set (&ctx->autoaway_tmr, delay * 1000); poller_timer_set (&ctx->autoaway_tmr, delay * 1000);
} }
@ -14362,7 +14429,7 @@ on_autoaway_timer (struct app_context *ctx)
{ {
// An empty message would unset any away status, so let's ignore that // An empty message would unset any away status, so let's ignore that
const char *message = get_config_string const char *message = get_config_string
(ctx->config.root, "behaviour.autoaway_message"); (ctx->config.root, "general.autoaway_message");
if (!message || !*message) if (!message || !*message)
return; return;
@ -14719,9 +14786,6 @@ main (int argc, char *argv[])
CALL (ctx.input, stop); CALL (ctx.input, stop);
if (get_config_boolean (ctx.config.root, "behaviour.save_on_quit"))
save_configuration (&ctx);
app_context_free (&ctx); app_context_free (&ctx);
toggle_bracketed_paste (false); toggle_bracketed_paste (false);
free_terminal (); free_terminal ();