16 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
83764d1e1b Fix xB.adoc parsing with current libasciidoc 2022-08-24 03:17:05 +02:00
a717782480 Build with AsciiDoc as well as Asciidoctor 2022-08-24 00:13:51 +02:00
c50c959f4d Bump copyright years 2022-08-17 18:27:52 +02:00
0dd7536b5a Update README 2022-08-15 15:49:59 +02:00
0750096827 xC: expand behaviour.editor_command examples 2022-08-14 20:27:30 +02:00
49d9980662 xC: improve backlog helper capabilities
Snippets now receive positional parameters in the form of the buffer's
name in the locale encoding, and a filename if applicable
(we keep passing stdin along with the filename, which happens to
work out well for less(1)).

The default value of the configuration option also no longer uses
the "long prompt", which used to unhelpfully tell position in terms
of lines, but rather sets its own prompt that counts pages,
and makes sure to indicate the source buffer.

The main motivation behind this change is to make the 'v' command
work in less(1).  LESSSECURE must be omitted from the snippet
for this to work.

Bump liberty to receive a config parser that allows for less
convoluted escaping.
2022-08-14 18:52:26 +02:00
2f7fbcdc5d CMakeLists.txt: fix a typo 2022-08-12 13:21:46 +02:00
ef0cbe9a59 Rename the project
It is about to see some extensions, obsoleting the number three.
2022-08-07 10:40:42 +02:00
2d8808d795 utm-filter.lua: mention the passing of fbclid 2022-07-18 17:59:28 +02:00
60d52ad479 xC, xD: add basic WALLOPS support 2022-02-04 22:48:54 +01:00
13 changed files with 374 additions and 221 deletions

12
.gitignore vendored
View File

@@ -3,9 +3,9 @@
# Qt Creator files # Qt Creator files
/CMakeLists.txt.user* /CMakeLists.txt.user*
/uirc3.config /xK.config
/uirc3.files /xK.files
/uirc3.creator* /xK.creator*
/uirc3.includes /xK.includes
/uirc3.cflags /xK.cflags
/uirc3.cxxflags /xK.cxxflags

View File

@@ -1,6 +1,6 @@
# Ubuntu 18.04 LTS and OpenBSD 6.4 # Ubuntu 18.04 LTS and OpenBSD 6.4
cmake_minimum_required (VERSION 3.10) cmake_minimum_required (VERSION 3.10)
project (uirc3 VERSION 1.5.0 LANGUAGES C) project (xK VERSION 1.5.0 DESCRIPTION "IRC client, daemon and bot" LANGUAGES C)
# Options # Options
option (WANT_READLINE "Use GNU Readline for the UI (better)" ON) option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
@@ -196,7 +196,7 @@ add_custom_target (clang-tidy
# Installation # Installation
install (TARGETS xB xC xD DESTINATION ${CMAKE_INSTALL_BINDIR}) install (TARGETS xB xC xD DESTINATION ${CMAKE_INSTALL_BINDIR})
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
# XXX: our defaults for XDG_DATA_DIRS expect /usr/local/shore or /usr/share # XXX: our defaults for XDG_DATA_DIRS expect /usr/local/share or /usr/share
install (DIRECTORY plugins/xB/ install (DIRECTORY plugins/xB/
DESTINATION ${CMAKE_INSTALL_DATADIR}/xB/plugins USE_SOURCE_PERMISSIONS) DESTINATION ${CMAKE_INSTALL_DATADIR}/xB/plugins USE_SOURCE_PERMISSIONS)
install (DIRECTORY plugins/xC/ install (DIRECTORY plugins/xC/
@@ -204,20 +204,31 @@ install (DIRECTORY plugins/xC/
# Generate documentation from text markup # Generate documentation from text markup
find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor) find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor)
if (NOT ASCIIDOCTOR_EXECUTABLE) find_program (A2X_EXECUTABLE a2x)
message (FATAL_ERROR "asciidoctor not found") if (NOT ASCIIDOCTOR_EXECUTABLE AND NOT A2X_EXECUTABLE)
message (FATAL_ERROR "Neither asciidoctor nor a2x were found")
endif () endif ()
foreach (page xB xC xD) foreach (page xB xC xD)
set (page_output "${PROJECT_BINARY_DIR}/${page}.1") set (page_output "${PROJECT_BINARY_DIR}/${page}.1")
list (APPEND project_MAN_PAGES "${page_output}") list (APPEND project_MAN_PAGES "${page_output}")
add_custom_command (OUTPUT ${page_output} if (ASCIIDOCTOR_EXECUTABLE)
COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage add_custom_command (OUTPUT ${page_output}
-a release-version=${project_version} COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage
"${PROJECT_SOURCE_DIR}/${page}.adoc" -a release-version=${project_version}
-o "${page_output}" -o "${page_output}"
DEPENDS ${page}.adoc "${PROJECT_SOURCE_DIR}/${page}.adoc"
COMMENT "Generating man page for ${page}" VERBATIM) DEPENDS ${page}.adoc
COMMENT "Generating man page for ${page}" VERBATIM)
elseif (A2X_EXECUTABLE)
add_custom_command (OUTPUT ${page_output}
COMMAND ${A2X_EXECUTABLE} --doctype manpage --format manpage
-a release-version=${project_version}
-D "${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/${page}.adoc"
DEPENDS ${page}.adoc
COMMENT "Generating man page for ${page}" VERBATIM)
endif ()
endforeach () endforeach ()
add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES}) add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES})
@@ -229,7 +240,6 @@ foreach (page ${project_MAN_PAGES})
endforeach () endforeach ()
# CPack # CPack
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Unreasonable IRC client, daemon and bot")
set (CPACK_PACKAGE_VERSION "${project_version_safe}") set (CPACK_PACKAGE_VERSION "${project_version_safe}")
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch") set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>") set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")

View File

@@ -1,4 +1,4 @@
Copyright (c) 2014 - 2021, Přemysl Eric Janouch <p@janouch.name> Copyright (c) 2014 - 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.

17
NEWS
View File

@@ -1,3 +1,20 @@
Unreleased
* 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
* xD: implemented WALLOPS, choosing to make it target even non-operators
1.5.0 (2021-12-21) "The Show Must Go On" 1.5.0 (2021-12-21) "The Show Must Go On"
* xC: made it possible to pass the cursor position to external editors, * xC: made it possible to pass the cursor position to external editors,

View File

@@ -1,11 +1,11 @@
uirc3 xK
===== ==
The unreasonable IRC trinity. This project consists of an IRC client, daemon, 'xK' (chat kit) is an IRC software suite consisting of a terminal client,
and bot. It's all you're ever going to need for chatting, as long as you can daemon, and bot. It's all you're ever going to need for chatting,
make do with minimalist software. so long as you can make do with slightly minimalist software.
They have these potentially interesting properties: They come with these potentially interesting properties:
- supporting IRCv3, SOCKS, IPv6, TLS (including client certificates) - supporting IRCv3, SOCKS, IPv6, TLS (including client certificates)
- lean on dependencies - lean on dependencies
@@ -14,18 +14,18 @@ They have these potentially interesting properties:
xC xC
-- --
The IRC client. It is largely defined by being built on top of GNU Readline The IRC client, and the core of 'xK'. It is largely defined by being built
that has been hacked to death. Its interface should feel somewhat familiar for on top of GNU Readline that has been hacked to death. Its interface should feel
weechat or irssi users. somewhat familiar for weechat or irssi users.
image::xC.png[align="center"] image::xC.png[align="center"]
This is the core of the project. It has most of the stuff you'd expect of It has most of the stuff you'd expect of an IRC client, such as being
an IRC client, such as being multiserver, a powerful configuration system, multiserver, a powerful configuration system, integrated help, text formatting,
integrated help, text formatting, automatic splitting of overlong messages, automatic splitting of overlong messages, multiline editing, bracketed paste
multiline editing, bracketed paste support, decent word wrapping, autocomplete, support, decent word wrapping, autocomplete, logging, CTCP queries, auto-away,
logging, CTCP queries, auto-away, command aliases, and basic support for Lua command aliases, and basic support for Lua scripting. As a unique bonus,
scripting. As a unique bonus, you can launch a full text editor from within. you can launch a full text editor from within.
xD xD
-- --
@@ -71,17 +71,15 @@ a package with the latest development version from Archlinux's AUR.
Building Building
-------- --------
Build dependencies: CMake, pkg-config, asciidoctor, awk, liberty (included) + Build dependencies: CMake, pkg-config, asciidoctor or asciidoc, awk,
liberty (included) +
Runtime dependencies: openssl + 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 $ git clone --recursive https://git.janouch.name/p/xK.git
acting up and I have no clue about fixing it. $ mkdir xK/build
$ cd xK/build
$ git clone --recursive https://git.janouch.name/p/uirc3.git
$ mkdir uirc3/build
$ cd uirc3/build
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \ $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DWANT_READLINE=ON -DWANT_LIBEDIT=OFF -DWANT_LUA=ON -DWANT_READLINE=ON -DWANT_LIBEDIT=OFF -DWANT_LUA=ON
$ make $ make
@@ -93,7 +91,7 @@ To install the application, you can do either the usual:
Or you can try telling CMake to make a package for you: Or you can try telling CMake to make a package for you:
$ cpack -G DEB # also supported: RPM, FreeBSD $ cpack -G DEB # also supported: RPM, FreeBSD
# dpkg -i uirc3-*.deb # dpkg -i xK-*.deb
Usage Usage
----- -----
@@ -151,14 +149,18 @@ Beware that you can easily break the program if you're not careful.
How do I make xC look like the screenshot? How do I make xC look like the screenshot?
------------------------------------------ ------------------------------------------
First of all, you must build it with Lua support. With the defaults, 'xC' With the defaults, 'xC' doesn't look too fancy because I don't want to have
doesn't look too fancy because I don't want to depend on Lua or 256-colour a hard dependency on either Lua for the bundled script that provides an easily
terminals. In addition to that, I appear to be one of the few people who use adjustable enhanced prompt, or on 256-colour terminals. Moreover, it's nearly
black on white terminals. impossible to come up with a colour theme that would work well with both
black-on-white and white-on-black terminals, or anything wild in between.
/set behaviour.date_change_line = "%a %e %b %Y" Assuming that your build supports Lua plugins, and that you have a decent,
/set behaviour.plugin_autoload += "fancy-prompt.lua" properly set-up terminal emulator, it suffices to run:
/set behaviour.backlog_helper = "LESSSECURE=1 less -R +Gb1d -Ps'Backlog ?ltlines %lt-%lb?L/%L. .?e(END):?pB%pB\\%..'"
/set general.pager = Press Tab here and change +Gb to +Gb1d
/set general.date_change_line = "%a %e %b %Y"
/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"
@@ -179,7 +181,7 @@ configurations accordingly, but I consider it rather messy and unnecessary.
Contributing and Support Contributing and Support
------------------------ ------------------------
Use https://git.janouch.name/p/uirc3 to report any bugs, request features, Use https://git.janouch.name/p/xK to report any bugs, request features,
or submit pull requests. `git send-email` is tolerated. If you want to discuss or submit pull requests. `git send-email` is tolerated. If you want to discuss
the project, feel free to join me at ircs://irc.janouch.name, channel #dev. the project, feel free to join me at ircs://irc.janouch.name, channel #dev.

Submodule liberty updated: 1b9d89cab3...f545be725d

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

@@ -18,6 +18,9 @@
-- A list of useless URL parameters that don't affect page function -- A list of useless URL parameters that don't affect page function
local banned = { local banned = {
gclid = 1, gclid = 1,
-- Alas, Facebook no longer uses this parameter, see:
-- https://news.ycombinator.com/item?id=32117489
fbclid = 1, fbclid = 1,
utm_source = 1, utm_source = 1,

12
xB.adoc
View File

@@ -1,8 +1,8 @@
xB(1) xB(1)
===== =====
:doctype: manpage :doctype: manpage
:manmanual: uirc3 Manual :manmanual: xK Manual
:mansource: uirc3 {release-version} :mansource: xK {release-version}
Name Name
---- ----
@@ -60,14 +60,14 @@ using the IRC protocol. (Caveat: the standard C library doesn't automatically
flush FILE streams for pipes on newlines.) A special *XB* command is introduced flush FILE streams for pipes on newlines.) A special *XB* command is introduced
for RPC, with the following subcommands: for RPC, with the following subcommands:
*XB get_config* _key_:: *XB get_config* __key__::
Request the value of the given configuration option. If no such option Request the value of the given configuration option. If no such option
exists, the value will be empty. The response will be delivered in exists, the value will be empty. The response will be delivered in
the following format: the following format:
+ +
``` ....
XB :value XB :value
``` ....
+ +
This is particularly useful for retrieving the *prefix* string. This is particularly useful for retrieving the *prefix* string.
@@ -100,5 +100,5 @@ _/usr/share/xB/plugins/_::
Reporting bugs Reporting bugs
-------------- --------------
Use https://git.janouch.name/p/uirc3 to report bugs, request features, Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests. or submit pull requests.

15
xC.adoc
View File

@@ -1,8 +1,8 @@
xC(1) xC(1)
===== =====
:doctype: manpage :doctype: manpage
:manmanual: uirc3 Manual :manmanual: xK Manual
:mansource: uirc3 {release-version} :mansource: xK {release-version}
Name Name
---- ----
@@ -25,9 +25,9 @@ Options
other formatting marks to ANSI codes retrieved from the *terminfo*(5) other formatting marks to ANSI codes retrieved from the *terminfo*(5)
database: database:
+ +
``` ....
printf '\x02bold\x02\n' | xC -f printf '\x02bold\x02\n' | xC -f
``` ....
+ +
This feature may be used to preview server MOTD files. This feature may be used to preview server MOTD files.
@@ -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,12 +114,11 @@ _/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
-------------- --------------
Use https://git.janouch.name/p/uirc3 to report bugs, request features, Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests. or submit pull requests.
See also See also

366
xC.c
View File

@@ -1,7 +1,7 @@
/* /*
* xC.c: a terminal-based IRC client * xC.c: a terminal-based IRC client
* *
* Copyright (c) 2015 - 2021, Přemysl Eric Janouch <p@janouch.name> * Copyright (c) 2015 - 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.
@@ -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,63 +2535,18 @@ 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",
.comment = "VIM: \"vim +%Bgo %F\", Emacs: \"emacs -nw +%L:%C %F\"", // User input:
{ .name = "editor",
.comment = "VIM: \"vim +%Bgo %F\", Emacs: \"emacs -nw +%L:%C %F\", "
"nano/micro/kakoune: \"nano/micro/kak +%L:%C %F\"",
.type = CONFIG_ITEM_STRING }, .type = CONFIG_ITEM_STRING },
{ .name = "process_pasted_text", { .name = "process_pasted_text",
.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 display a buffer's history",
.type = CONFIG_ITEM_STRING,
.default_ = "\"LESSSECURE=1 less -M -R +Gb\"" },
{ .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,
@@ -2508,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" },
{} {}
}; };
@@ -2528,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
@@ -2547,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);
} }
@@ -2782,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
@@ -3773,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);
@@ -3782,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;
@@ -3921,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));
@@ -4298,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
@@ -4909,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)
@@ -4918,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);
} }
@@ -7488,6 +7529,16 @@ irc_handle_topic (struct server *s, const struct irc_message *msg)
} }
} }
static void
irc_handle_wallops (struct server *s, const struct irc_message *msg)
{
if (!msg->prefix || msg->params.len < 1)
return;
const char *message = msg->params.vector[0];
log_server (s, s->buffer, 0, "<#n> #m", msg->prefix, message);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static struct irc_handler g_irc_handlers[] = static struct irc_handler g_irc_handlers[] =
@@ -7511,6 +7562,7 @@ static struct irc_handler g_irc_handlers[] =
{ "QUIT", irc_handle_quit }, { "QUIT", irc_handle_quit },
{ "TAGMSG", irc_handle_tagmsg }, { "TAGMSG", irc_handle_tagmsg },
{ "TOPIC", irc_handle_topic }, { "TOPIC", irc_handle_topic },
{ "WALLOPS", irc_handle_wallops },
}; };
static bool static bool
@@ -10513,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)
{ {
@@ -10533,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 },
}; };
@@ -10951,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 ();
@@ -11318,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;
@@ -11352,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;
} }
@@ -13192,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;
@@ -13201,6 +13285,13 @@ 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 editor mentioned in the default value of general.editor,
// plus vi, mcedit, vis, ...), it isn't particularly useful by itself.
// We need to be able to specify the column number.
//
// Seeing as less popular software may try to process this as a filename
// and fail, do not bother with this "undocumented standard feature".
strv_append (&argv, command); strv_append (&argv, command);
strv_append (&argv, filename); strv_append (&argv, filename);
return argv; return argv;
@@ -13355,24 +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,
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);
execl ("/bin/sh", "/bin/sh", "-c", get_config_string char *localized_name =
(ctx->config.root, "behaviour.backlog_helper"), NULL); iconv_xstrdup (ctx->term_from_utf8, (char *) name, -1, NULL);
print_error ("%s: %s", execl ("/bin/sh", "/bin/sh", "-c",
"Failed to launch backlog helper", strerror (errno)); get_config_string (ctx->config.root, "general.pager"),
PROGRAM_NAME, localized_name, path, NULL);
print_error ("%s: %s", "Failed to launch pager", 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;
} }
} }
@@ -13388,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;
@@ -13408,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)); launch_pager (ctx, fileno (backlog), buffer->name, NULL);
fclose (backlog); fclose (backlog);
return true; return true;
} }
@@ -13436,24 +13530,25 @@ on_display_full_log (int count, int key, void *user_data)
(void) key; (void) key;
struct app_context *ctx = user_data; struct app_context *ctx = user_data;
char *path = buffer_get_log_path (ctx->current_buffer); struct buffer *buffer = ctx->current_buffer;
char *path = buffer_get_log_path (buffer);
FILE *full_log = fopen (path, "rb"); FILE *full_log = fopen (path, "rb");
free (path);
if (!full_log) if (!full_log)
{ {
log_global_error (ctx, "Failed to open log file for #s: #l", log_global_error (ctx, "Failed to open log file for #s: #l",
ctx->current_buffer->name, strerror (errno)); ctx->current_buffer->name, strerror (errno));
free (path);
return false; return false;
} }
if (ctx->current_buffer->log_file) if (buffer->log_file)
// The regular flush will log any error eventually // The regular flush will log any error eventually
(void) fflush (ctx->current_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)); launch_pager (ctx, fileno (full_log), buffer->name, path);
fclose (full_log); fclose (full_log);
free (path);
return true; return true;
} }
@@ -13848,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);
@@ -13866,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;
} }
@@ -13914,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.",
@@ -14121,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");
@@ -14239,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,
@@ -14326,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);
} }
@@ -14336,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;
@@ -14693,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 ();

View File

@@ -1,8 +1,8 @@
xD(1) xD(1)
===== =====
:doctype: manpage :doctype: manpage
:manmanual: uirc3 Manual :manmanual: xK Manual
:mansource: uirc3 {release-version} :mansource: xK {release-version}
Name Name
---- ----
@@ -49,5 +49,5 @@ _/etc/xdg/xD/xD.conf_::
Reporting bugs Reporting bugs
-------------- --------------
Use https://git.janouch.name/p/uirc3 to report bugs, request features, Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests. or submit pull requests.

26
xD.c
View File

@@ -1,7 +1,7 @@
/* /*
* xD.c: an IRC daemon * xD.c: an IRC daemon
* *
* Copyright (c) 2014 - 2021, Přemysl Eric Janouch <p@janouch.name> * Copyright (c) 2014 - 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.
@@ -2932,6 +2932,29 @@ irc_handle_links (const struct irc_message *msg, struct client *c)
irc_send_reply (c, IRC_RPL_ENDOFLINKS, mask); irc_send_reply (c, IRC_RPL_ENDOFLINKS, mask);
} }
static void
irc_handle_wallops (const struct irc_message *msg, struct client *c)
{
if (msg->params.len < 1)
RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
if (!(c->mode & IRC_USER_MODE_OPERATOR))
RETURN_WITH_REPLY (c, IRC_ERR_NOPRIVILEGES);
const char *message = msg->params.vector[0];
// Our interpretation: anonymize the sender,
// and target all users who want to receive these messages
struct str_map_iter iter = str_map_iter_make (&c->ctx->users);
struct client *target;
while ((target = str_map_iter_next (&iter)))
{
if (target != c && !(target->mode & IRC_USER_MODE_RX_WALLOPS))
continue;
client_send (target, ":%s WALLOPS :%s", c->ctx->server_name, message);
}
}
static void static void
irc_handle_kill (const struct irc_message *msg, struct client *c) irc_handle_kill (const struct irc_message *msg, struct client *c)
{ {
@@ -2994,6 +3017,7 @@ irc_register_handlers (struct server_context *ctx)
{ "ADMIN", true, irc_handle_admin, 0, 0 }, { "ADMIN", true, irc_handle_admin, 0, 0 },
{ "STATS", true, irc_handle_stats, 0, 0 }, { "STATS", true, irc_handle_stats, 0, 0 },
{ "LINKS", true, irc_handle_links, 0, 0 }, { "LINKS", true, irc_handle_links, 0, 0 },
{ "WALLOPS", true, irc_handle_wallops, 0, 0 },
{ "MODE", true, irc_handle_mode, 0, 0 }, { "MODE", true, irc_handle_mode, 0, 0 },
{ "PRIVMSG", true, irc_handle_privmsg, 0, 0 }, { "PRIVMSG", true, irc_handle_privmsg, 0, 0 },