Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
b358f53ec3
|
|||
|
2eb315f5c4
|
|||
|
851c2ee548
|
|||
|
f9848ed627
|
|||
|
686a39df38
|
|||
|
9cea3fca91
|
|||
|
5165f76b7c
|
|||
|
92ac13f3c6
|
|||
|
df4ca74580
|
|||
|
9e297244a4
|
|||
|
d32ba133c0
|
|||
|
ce3976e1ec
|
|||
|
e5ed89646b
|
32
.clang-format
Normal file
32
.clang-format
Normal file
@@ -0,0 +1,32 @@
|
||||
# clang-format is fairly limited, and these rules are approximate:
|
||||
# - array initializers can get terribly mangled with clang-format 12.0,
|
||||
# - sometimes it still aligns with space characters,
|
||||
# - struct name NL { NL ... NL } NL name; is unachievable.
|
||||
BasedOnStyle: GNU
|
||||
ColumnLimit: 80
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
UseTab: ForContinuationAndIndentation
|
||||
BreakBeforeBraces: Allman
|
||||
SpaceAfterCStyleCast: true
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignOperands: DontAlign
|
||||
AlignConsecutiveMacros: Consecutive
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
IndentGotoLabels: false
|
||||
|
||||
# IncludeCategories has some potential, but it may also break the build.
|
||||
# Note that the documentation says the value should be "Never".
|
||||
SortIncludes: false
|
||||
|
||||
# This is a compromise, it generally works out aesthetically better.
|
||||
BinPackArguments: false
|
||||
|
||||
# Unfortunately, this can't be told to align to column 40 or so.
|
||||
SpacesBeforeTrailingComments: 2
|
||||
|
||||
# liberty-specific macro body wrappers.
|
||||
MacroBlockBegin: "BLOCK_START"
|
||||
MacroBlockEnd: "BLOCK_END"
|
||||
ForEachMacros: ["LIST_FOR_EACH"]
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,3 +7,5 @@
|
||||
/uirc3.files
|
||||
/uirc3.creator*
|
||||
/uirc3.includes
|
||||
/uirc3.cflags
|
||||
/uirc3.cxxflags
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
cmake_minimum_required (VERSION 3.0)
|
||||
project (uirc3 VERSION 1.4.0 LANGUAGES C)
|
||||
# Ubuntu 18.04 LTS and OpenBSD 6.4
|
||||
cmake_minimum_required (VERSION 3.10)
|
||||
project (uirc3 VERSION 1.5.0 LANGUAGES C)
|
||||
|
||||
# Options
|
||||
option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
|
||||
option (WANT_LIBEDIT "Use BSD libedit for the UI" OFF)
|
||||
|
||||
# Moar warnings
|
||||
set (CMAKE_C_STANDARD 99)
|
||||
set (CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set (CMAKE_C_EXTENSIONS OFF)
|
||||
|
||||
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
|
||||
# -Wunused-function is pretty annoying here, as everything is static
|
||||
set (wdisabled "-Wno-unused-function")
|
||||
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra ${wdisabled}")
|
||||
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-function")
|
||||
endif ()
|
||||
|
||||
# Version
|
||||
@@ -57,6 +61,8 @@ if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
|
||||
# Need this for SIGWINCH in FreeBSD and OpenBSD respectively;
|
||||
# our POSIX version macros make it undefined
|
||||
add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1)
|
||||
elseif (APPLE)
|
||||
add_definitions (-D_DARWIN_C_SOURCE)
|
||||
endif ()
|
||||
|
||||
# -lrt is only for glibc < 2.17
|
||||
@@ -112,10 +118,16 @@ endif ()
|
||||
if ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT))
|
||||
message (SEND_ERROR "You have to choose either GNU Readline or libedit")
|
||||
elseif (WANT_READLINE)
|
||||
pkg_check_modules (readline readline)
|
||||
|
||||
# OpenBSD's default readline is too old
|
||||
if ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD")
|
||||
include_directories (${OPENBSD_LOCALBASE}/include/ereadline)
|
||||
list (APPEND xC_libraries ereadline)
|
||||
elseif (readline_FOUND)
|
||||
list (APPEND xC_libraries ${readline_LIBRARIES})
|
||||
include_directories (${readline_INCLUDE_DIRS})
|
||||
link_directories (${readline_LIBRARY_DIRS})
|
||||
else ()
|
||||
list (APPEND xC_libraries readline)
|
||||
endif ()
|
||||
|
||||
15
NEWS
15
NEWS
@@ -1,3 +1,18 @@
|
||||
1.5.0 (2021-12-21) "The Show Must Go On"
|
||||
|
||||
* xC: made it possible to pass the cursor position to external editors,
|
||||
in particular VIM and Emacs
|
||||
|
||||
* xC: started quoting text coming from bracketed pastes,
|
||||
to minimize the risk of trying to execute filesystem paths as commands
|
||||
|
||||
* xC: fixed to work with post-2021-08-29 editline
|
||||
|
||||
* xC: extended editline's autocomplete to show all options
|
||||
|
||||
* utm-filter.lua: added Facebook's tracking parameter to the filter
|
||||
|
||||
|
||||
1.4.0 (2021-10-06) "Call Me Scruffy Scruffington"
|
||||
|
||||
* xC: made message autosplitting respect text formatting
|
||||
|
||||
47
README.adoc
47
README.adoc
@@ -1,18 +1,16 @@
|
||||
uirc3
|
||||
=====
|
||||
:compact-option:
|
||||
|
||||
The unreasonable IRC trinity. This project consists of an IRC client, daemon,
|
||||
and bot. It's all you're ever going to need for chatting, as long as you can
|
||||
make do with minimalist software.
|
||||
|
||||
All of them have these potentially interesting properties:
|
||||
They have these potentially interesting properties:
|
||||
|
||||
- IPv6 support
|
||||
- TLS support, including client certificates
|
||||
- lean on dependencies (with the exception of 'xC')
|
||||
- supporting IRCv3, SOCKS, IPv6, TLS (including client certificates)
|
||||
- lean on dependencies
|
||||
- compact and arguably easy to hack on
|
||||
- very permissive license
|
||||
- maximally permissive license
|
||||
|
||||
xC
|
||||
--
|
||||
@@ -22,11 +20,12 @@ weechat or irssi users.
|
||||
|
||||
image::xC.png[align="center"]
|
||||
|
||||
This is the largest application within the project. It has most of the stuff
|
||||
you'd expect of an IRC client, such as being able to set up multiple servers,
|
||||
a powerful configuration system, integrated help, text formatting, CTCP queries,
|
||||
automatic splitting of overlong messages, autocomplete, logging to file,
|
||||
auto-away, command aliases and basic support for Lua scripting.
|
||||
This is the core of the project. It has most of the stuff you'd expect of
|
||||
an IRC client, such as being multiserver, a powerful configuration system,
|
||||
integrated help, text formatting, automatic splitting of overlong messages,
|
||||
multiline editing, bracketed paste support, decent word wrapping, autocomplete,
|
||||
logging, CTCP queries, auto-away, command aliases, and basic support for Lua
|
||||
scripting. As a unique bonus, you can launch a full text editor from within.
|
||||
|
||||
xD
|
||||
--
|
||||
@@ -37,10 +36,8 @@ do it just fine.
|
||||
|
||||
Notable features:
|
||||
|
||||
- TLS autodetection (why doesn't everyone have this?), using secure defaults
|
||||
- TLS autodetection (I'm still wondering why everyone doesn't have this)
|
||||
- IRCop authentication via TLS client certificates
|
||||
- epoll/kqueue support; this means that it should be able to handle quite
|
||||
a number of concurrent user connections
|
||||
- partial IRCv3 support
|
||||
|
||||
Not supported:
|
||||
@@ -58,16 +55,14 @@ and development continues over there.
|
||||
|
||||
xB
|
||||
--
|
||||
The IRC bot. It builds upon the concept of my other VitaminA IRC bot. The main
|
||||
characteristic of these two bots is that they run plugins as coprocesses, which
|
||||
allows for enhanced reliability and programming language freedom.
|
||||
The IRC bot. While originally intended to be a simple rewrite of my old GNU AWK
|
||||
bot in C, it fairly quickly became a playground, and it eventually got me into
|
||||
writing the rest of this package.
|
||||
|
||||
While originally intended to be a simple rewrite of the original AWK bot in C,
|
||||
it fairly quickly became a playground, and it eventually got me into writing
|
||||
the rest of the package.
|
||||
|
||||
It survives crashes, server disconnects and timeouts, and also has native SOCKS
|
||||
support (even though socksify can add that easily to any program).
|
||||
Its main characteristic is that it runs plugins as coprocesses, allowing for
|
||||
enhanced reliability and programming language freedom. Moreover, it recovers
|
||||
from any crashes, and offers native SOCKS support (even though socksify can add
|
||||
that easily to any program).
|
||||
|
||||
Packages
|
||||
--------
|
||||
@@ -87,7 +82,7 @@ acting up and I have no clue about fixing it.
|
||||
$ git clone --recursive https://git.janouch.name/p/uirc3.git
|
||||
$ mkdir uirc3/build
|
||||
$ cd uirc3/build
|
||||
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug \
|
||||
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DWANT_READLINE=ON -DWANT_LIBEDIT=OFF -DWANT_LUA=ON
|
||||
$ make
|
||||
|
||||
@@ -95,9 +90,9 @@ To install the application, you can do either the usual:
|
||||
|
||||
# make install
|
||||
|
||||
Or you can try telling CMake to make a package for you. For Debian it is:
|
||||
Or you can try telling CMake to make a package for you:
|
||||
|
||||
$ cpack -G DEB
|
||||
$ cpack -G DEB # also supported: RPM, FreeBSD
|
||||
# dpkg -i uirc3-*.deb
|
||||
|
||||
Usage
|
||||
|
||||
24
common.c
24
common.c
@@ -22,11 +22,11 @@
|
||||
#define LIBERTY_WANT_PROTO_IRC
|
||||
|
||||
#ifdef WANT_SYSLOG_LOGGING
|
||||
#define print_fatal_data ((void *) LOG_ERR)
|
||||
#define print_error_data ((void *) LOG_ERR)
|
||||
#define print_warning_data ((void *) LOG_WARNING)
|
||||
#define print_status_data ((void *) LOG_INFO)
|
||||
#define print_debug_data ((void *) LOG_DEBUG)
|
||||
#define print_fatal_data ((void *) LOG_ERR)
|
||||
#define print_error_data ((void *) LOG_ERR)
|
||||
#define print_warning_data ((void *) LOG_WARNING)
|
||||
#define print_status_data ((void *) LOG_INFO)
|
||||
#define print_debug_data ((void *) LOG_DEBUG)
|
||||
#endif // WANT_SYSLOG_LOGGING
|
||||
|
||||
#include "liberty/liberty.c"
|
||||
@@ -87,15 +87,15 @@ static time_t
|
||||
unixtime_msec (long *msec)
|
||||
{
|
||||
#ifdef _POSIX_TIMERS
|
||||
struct timespec tp;
|
||||
hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1);
|
||||
*msec = tp.tv_nsec / 1000000;
|
||||
struct timespec tp;
|
||||
hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1);
|
||||
*msec = tp.tv_nsec / 1000000;
|
||||
#else // ! _POSIX_TIMERS
|
||||
struct timeval tp;
|
||||
hard_assert (gettimeofday (&tp, NULL) != -1);
|
||||
*msec = tp.tv_usec / 1000;
|
||||
struct timeval tp;
|
||||
hard_assert (gettimeofday (&tp, NULL) != -1);
|
||||
*msec = tp.tv_usec / 1000;
|
||||
#endif // ! _POSIX_TIMERS
|
||||
return tp.tv_sec;
|
||||
return tp.tv_sec;
|
||||
}
|
||||
|
||||
// --- Logging -----------------------------------------------------------------
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
--
|
||||
-- utm-filter.lua: filter out Google Analytics bullshit from URLs
|
||||
-- utm-filter.lua: filter out Google Analytics bullshit etc. from URLs
|
||||
--
|
||||
-- Copyright (c) 2015, Přemysl Eric Janouch <p@janouch.name>
|
||||
-- Copyright (c) 2015 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software for any
|
||||
-- purpose with or without fee is hereby granted.
|
||||
@@ -18,6 +18,7 @@
|
||||
-- A list of useless URL parameters that don't affect page function
|
||||
local banned = {
|
||||
gclid = 1,
|
||||
fbclid = 1,
|
||||
|
||||
utm_source = 1,
|
||||
utm_medium = 1,
|
||||
|
||||
213
xC.c
213
xC.c
@@ -238,8 +238,8 @@ struct input_vtable
|
||||
/// Bind Alt+key to the given named function
|
||||
void (*bind_meta) (void *input, char key, const char *fn);
|
||||
|
||||
/// Get the current line input
|
||||
char *(*get_line) (void *input);
|
||||
/// Get the current line input and position within
|
||||
char *(*get_line) (void *input, int *position);
|
||||
/// Clear the current line input
|
||||
void (*clear_line) (void *input);
|
||||
/// Insert text at current position
|
||||
@@ -361,9 +361,10 @@ input_rl_insert (void *input, const char *s)
|
||||
}
|
||||
|
||||
static char *
|
||||
input_rl_get_line (void *input)
|
||||
input_rl_get_line (void *input, int *position)
|
||||
{
|
||||
(void) input;
|
||||
if (position) *position = rl_point;
|
||||
return rl_copy_text (0, rl_end);
|
||||
}
|
||||
|
||||
@@ -771,24 +772,13 @@ struct input_el
|
||||
|
||||
static void app_editline_init (struct input_el *self);
|
||||
|
||||
static int
|
||||
input_el__get_termios (int character, int fallback)
|
||||
{
|
||||
if (!g_terminal.initialized)
|
||||
return fallback;
|
||||
|
||||
cc_t value = g_terminal.termios.c_cc[character];
|
||||
if (value == _POSIX_VDISABLE)
|
||||
return fallback;
|
||||
return value;
|
||||
}
|
||||
|
||||
static void
|
||||
input_el__redisplay (void *input)
|
||||
{
|
||||
// See rl_redisplay()
|
||||
// See rl_redisplay(), however NetBSD editline's map.c v1.54 breaks VREPRINT
|
||||
// so we bind redisplay somewhere else in app_editline_init()
|
||||
struct input_el *self = input;
|
||||
char x[] = { input_el__get_termios (VREPRINT, 'R' - 0x40), 0 };
|
||||
char x[] = { 'q' & 31, 0 };
|
||||
el_push (self->editline, x);
|
||||
|
||||
// We have to do this or it gets stuck and nothing is done
|
||||
@@ -871,10 +861,12 @@ input_el_insert (void *input, const char *s)
|
||||
}
|
||||
|
||||
static char *
|
||||
input_el_get_line (void *input)
|
||||
input_el_get_line (void *input, int *position)
|
||||
{
|
||||
struct input_el *self = input;
|
||||
const LineInfo *info = el_line (self->editline);
|
||||
int point = info->cursor - info->buffer;
|
||||
if (position) *position = point;
|
||||
return xstrndup (info->buffer, info->lastchar - info->buffer);
|
||||
}
|
||||
|
||||
@@ -2450,6 +2442,13 @@ static struct config_schema g_config_behaviour[] =
|
||||
.type = CONFIG_ITEM_BOOLEAN,
|
||||
.default_ = "on",
|
||||
.on_change = on_config_word_wrapping_change },
|
||||
{ .name = "editor_command",
|
||||
.comment = "VIM: \"vim +%Bgo %F\", Emacs: \"emacs -nw +%L:%C %F\"",
|
||||
.type = CONFIG_ITEM_STRING },
|
||||
{ .name = "process_pasted_text",
|
||||
.comment = "Normalize newlines and quote the command prefix in pastes",
|
||||
.type = CONFIG_ITEM_BOOLEAN,
|
||||
.default_ = "on" },
|
||||
{ .name = "date_change_line",
|
||||
.comment = "Input to strftime(3) for the date change line",
|
||||
.type = CONFIG_ITEM_STRING,
|
||||
@@ -6905,7 +6904,7 @@ irc_handle_join (struct server *s, const struct irc_message *msg)
|
||||
|
||||
buffer_add (s->ctx, buffer);
|
||||
|
||||
char *input = CALL (s->ctx->input, get_line);
|
||||
char *input = CALL_ (s->ctx->input, get_line, NULL);
|
||||
if (!*input)
|
||||
buffer_activate (s->ctx, buffer);
|
||||
else
|
||||
@@ -12629,8 +12628,6 @@ process_input (struct app_context *ctx, char *user_input)
|
||||
else
|
||||
{
|
||||
struct strv lines = strv_make ();
|
||||
|
||||
// XXX: this interprets commands in pasted text
|
||||
cstr_split (input, "\r\n", false, &lines);
|
||||
for (size_t i = 0; i < lines.len; i++)
|
||||
(void) process_input_utf8 (ctx,
|
||||
@@ -13163,7 +13160,7 @@ dump_input_to_file (struct app_context *ctx, char *template, struct error **e)
|
||||
if (fd < 0)
|
||||
return error_set (e, "%s", strerror (errno));
|
||||
|
||||
char *input = CALL (ctx->input, get_line);
|
||||
char *input = CALL_ (ctx->input, get_line, NULL);
|
||||
bool success = xwrite (fd, input, strlen (input), e);
|
||||
free (input);
|
||||
|
||||
@@ -13191,6 +13188,103 @@ try_dump_input_to_file (struct app_context *ctx)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct strv
|
||||
build_editor_command (struct app_context *ctx, const char *filename)
|
||||
{
|
||||
struct strv argv = strv_make ();
|
||||
const char *editor = get_config_string
|
||||
(ctx->config.root, "behaviour.editor_command");
|
||||
if (!editor)
|
||||
{
|
||||
const char *command;
|
||||
if (!(command = getenv ("VISUAL"))
|
||||
&& !(command = getenv ("EDITOR")))
|
||||
command = "vi";
|
||||
|
||||
strv_append (&argv, command);
|
||||
strv_append (&argv, filename);
|
||||
return argv;
|
||||
}
|
||||
|
||||
int cursor = 0;
|
||||
char *input = CALL_ (ctx->input, get_line, &cursor);
|
||||
hard_assert (cursor >= 0);
|
||||
|
||||
mbstate_t ps;
|
||||
memset (&ps, 0, sizeof ps);
|
||||
|
||||
wchar_t wch;
|
||||
size_t len, processed = 0, line_one_based = 1, column = 0;
|
||||
while (processed < (size_t) cursor
|
||||
&& (len = mbrtowc (&wch, input + processed, cursor - processed, &ps))
|
||||
&& len != (size_t) -2 && len != (size_t) -1)
|
||||
{
|
||||
// Both VIM and Emacs use the caret notation with columns.
|
||||
// Consciously leaving tabs broken, they're too difficult to handle.
|
||||
int width = wcwidth (wch);
|
||||
if (width < 0)
|
||||
width = 2;
|
||||
|
||||
processed += len;
|
||||
if (wch == '\n')
|
||||
{
|
||||
line_one_based++;
|
||||
column = 0;
|
||||
}
|
||||
else
|
||||
column += width;
|
||||
}
|
||||
free (input);
|
||||
|
||||
// Trivially split the command on spaces and substitute our values
|
||||
struct str argument = str_make ();
|
||||
for (; *editor; editor++)
|
||||
{
|
||||
if (*editor == ' ')
|
||||
{
|
||||
if (argument.len)
|
||||
{
|
||||
strv_append_owned (&argv, str_steal (&argument));
|
||||
argument = str_make ();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (*editor != '%' || !editor[1])
|
||||
{
|
||||
str_append_c (&argument, *editor);
|
||||
continue;
|
||||
}
|
||||
|
||||
// None of them are zero-length, thus words don't get lost
|
||||
switch (*++editor)
|
||||
{
|
||||
case 'F':
|
||||
str_append (&argument, filename);
|
||||
break;
|
||||
case 'L':
|
||||
str_append_printf (&argument, "%zu", line_one_based);
|
||||
break;
|
||||
case 'C':
|
||||
str_append_printf (&argument, "%zu", column + 1);
|
||||
break;
|
||||
case 'B':
|
||||
str_append_printf (&argument, "%d", cursor + 1);
|
||||
break;
|
||||
case '%':
|
||||
case ' ':
|
||||
str_append_c (&argument, *editor);
|
||||
break;
|
||||
default:
|
||||
print_warning ("unknown substitution variable");
|
||||
}
|
||||
}
|
||||
if (argument.len)
|
||||
strv_append_owned (&argv, str_steal (&argument));
|
||||
else
|
||||
str_free (&argument);
|
||||
return argv;
|
||||
}
|
||||
|
||||
static bool
|
||||
on_edit_input (int count, int key, void *user_data)
|
||||
{
|
||||
@@ -13202,16 +13296,15 @@ on_edit_input (int count, int key, void *user_data)
|
||||
if (!(filename = try_dump_input_to_file (ctx)))
|
||||
return false;
|
||||
|
||||
const char *command;
|
||||
if (!(command = getenv ("VISUAL"))
|
||||
&& !(command = getenv ("EDITOR")))
|
||||
command = "vi";
|
||||
struct strv argv = build_editor_command (ctx, filename);
|
||||
if (!argv.len)
|
||||
strv_append (&argv, "true");
|
||||
|
||||
hard_assert (!ctx->running_editor);
|
||||
switch (spawn_helper_child (ctx))
|
||||
{
|
||||
case 0:
|
||||
execlp (command, command, filename, NULL);
|
||||
execvp (argv.vector[0], argv.vector);
|
||||
print_error ("%s: %s",
|
||||
"Failed to launch editor", strerror (errno));
|
||||
_exit (EXIT_FAILURE);
|
||||
@@ -13224,6 +13317,7 @@ on_edit_input (int count, int key, void *user_data)
|
||||
ctx->running_editor = true;
|
||||
ctx->editor_filename = filename;
|
||||
}
|
||||
strv_free (&argv);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13709,19 +13803,33 @@ on_editline_complete (EditLine *editline, int key)
|
||||
|
||||
// Insert the best match instead
|
||||
el_insertstr (editline, completions[0]);
|
||||
|
||||
// I'm not sure if Readline's menu-complete can at all be implemented
|
||||
// with Editline--we have no way of detecting what the last executed handler
|
||||
// was. Employ the formatter's wrapping feature to spew all options.
|
||||
bool only_match = !completions[1];
|
||||
if (!only_match)
|
||||
{
|
||||
CALL (ctx->input, hide);
|
||||
redraw_screen (ctx);
|
||||
|
||||
struct formatter f = formatter_make (ctx, NULL);
|
||||
for (char **p = completions; *++p; )
|
||||
formatter_add (&f, " #l", *p);
|
||||
formatter_add (&f, "\n");
|
||||
formatter_flush (&f, stdout, 0);
|
||||
formatter_free (&f);
|
||||
|
||||
CALL (ctx->input, show);
|
||||
}
|
||||
|
||||
for (char **p = completions; *p; p++)
|
||||
free (*p);
|
||||
free (completions);
|
||||
|
||||
// I'm not sure if Readline's menu-complete can at all be implemented
|
||||
// with Editline. Spamming the terminal with possible completions
|
||||
// probably isn't what the user wants and we have no way of detecting
|
||||
// what the last executed handler was.
|
||||
if (!only_match)
|
||||
return CC_REFRESH_BEEP;
|
||||
|
||||
// But if there actually is just one match, finish the word
|
||||
// If there actually is just one match, finish the word
|
||||
el_insertstr (editline, " ");
|
||||
return CC_REFRESH;
|
||||
}
|
||||
@@ -13782,8 +13890,11 @@ app_editline_init (struct input_el *self)
|
||||
CALL_ (input, bind_control, 'w', "ed-delete-prev-word");
|
||||
// Just what are you doing?
|
||||
CALL_ (input, bind_control, 'u', "vi-kill-line-prev");
|
||||
// See input_el__redisplay(), functionally important
|
||||
CALL_ (input, bind_control, 'q', "ed-redisplay");
|
||||
|
||||
// We need to hide the prompt and input first
|
||||
CALL_ (input, bind, "\r", "send-line");
|
||||
CALL_ (input, bind, "\n", "send-line");
|
||||
|
||||
CALL_ (input, bind_control, 'i', "complete");
|
||||
@@ -14125,6 +14236,40 @@ done:
|
||||
|
||||
#define BRACKETED_PASTE_LIMIT 102400 ///< How much text can be pasted
|
||||
|
||||
static bool
|
||||
insert_paste (struct app_context *ctx, char *paste, size_t len)
|
||||
{
|
||||
if (!get_config_boolean (ctx->config.root, "behaviour.process_pasted_text"))
|
||||
return CALL_ (ctx->input, insert, paste);
|
||||
|
||||
// Without ICRNL, which Editline keeps but Readline doesn't,
|
||||
// the terminal sends newlines as carriage returns (seen on urxvt)
|
||||
for (size_t i = 0; i < len; i++)
|
||||
if (paste[i] == '\r')
|
||||
paste[i] = '\n';
|
||||
|
||||
int position = 0;
|
||||
char *input = CALL_ (ctx->input, get_line, &position);
|
||||
bool quote_first_slash = !position || strchr ("\r\n", input[position - 1]);
|
||||
free (input);
|
||||
|
||||
// Executing commands by accident is much more common than pasting them
|
||||
// intentionally, although the latter may also have security consequences
|
||||
struct str processed = str_make ();
|
||||
str_reserve (&processed, len);
|
||||
for (size_t i = 0; i < len; i++)
|
||||
{
|
||||
if (paste[i] == '/'
|
||||
&& ((!i && quote_first_slash) || (i && paste[i - 1] == '\n')))
|
||||
str_append_c (&processed, paste[i]);
|
||||
str_append_c (&processed, paste[i]);
|
||||
}
|
||||
|
||||
bool success = CALL_ (ctx->input, insert, processed.str);
|
||||
str_free (&processed);
|
||||
return success;
|
||||
}
|
||||
|
||||
static void
|
||||
process_bracketed_paste (const struct pollfd *fd, struct app_context *ctx)
|
||||
{
|
||||
@@ -14149,7 +14294,7 @@ process_bracketed_paste (const struct pollfd *fd, struct app_context *ctx)
|
||||
(int) (text_len = BRACKETED_PASTE_LIMIT));
|
||||
|
||||
buf->str[text_len] = '\0';
|
||||
if (CALL_ (ctx->input, insert, buf->str))
|
||||
if (insert_paste (ctx, buf->str, text_len))
|
||||
goto done;
|
||||
|
||||
error:
|
||||
|
||||
Reference in New Issue
Block a user