Compare commits
3 Commits
9e297244a4
...
5165f76b7c
Author | SHA1 | Date | |
---|---|---|---|
5165f76b7c | |||
92ac13f3c6 | |||
df4ca74580 |
193
xC.c
193
xC.c
@ -238,8 +238,8 @@ struct input_vtable
|
|||||||
/// Bind Alt+key to the given named function
|
/// Bind Alt+key to the given named function
|
||||||
void (*bind_meta) (void *input, char key, const char *fn);
|
void (*bind_meta) (void *input, char key, const char *fn);
|
||||||
|
|
||||||
/// Get the current line input
|
/// Get the current line input and position within
|
||||||
char *(*get_line) (void *input);
|
char *(*get_line) (void *input, int *position);
|
||||||
/// Clear the current line input
|
/// Clear the current line input
|
||||||
void (*clear_line) (void *input);
|
void (*clear_line) (void *input);
|
||||||
/// Insert text at current position
|
/// Insert text at current position
|
||||||
@ -361,9 +361,10 @@ input_rl_insert (void *input, const char *s)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
input_rl_get_line (void *input)
|
input_rl_get_line (void *input, int *position)
|
||||||
{
|
{
|
||||||
(void) input;
|
(void) input;
|
||||||
|
if (position) *position = rl_point;
|
||||||
return rl_copy_text (0, rl_end);
|
return rl_copy_text (0, rl_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -860,10 +861,12 @@ input_el_insert (void *input, const char *s)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
input_el_get_line (void *input)
|
input_el_get_line (void *input, int *position)
|
||||||
{
|
{
|
||||||
struct input_el *self = input;
|
struct input_el *self = input;
|
||||||
const LineInfo *info = el_line (self->editline);
|
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);
|
return xstrndup (info->buffer, info->lastchar - info->buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2439,6 +2442,13 @@ 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\"",
|
||||||
|
.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",
|
{ .name = "date_change_line",
|
||||||
.comment = "Input to strftime(3) for the date change line",
|
.comment = "Input to strftime(3) for the date change line",
|
||||||
.type = CONFIG_ITEM_STRING,
|
.type = CONFIG_ITEM_STRING,
|
||||||
@ -6894,7 +6904,7 @@ irc_handle_join (struct server *s, const struct irc_message *msg)
|
|||||||
|
|
||||||
buffer_add (s->ctx, buffer);
|
buffer_add (s->ctx, buffer);
|
||||||
|
|
||||||
char *input = CALL (s->ctx->input, get_line);
|
char *input = CALL_ (s->ctx->input, get_line, NULL);
|
||||||
if (!*input)
|
if (!*input)
|
||||||
buffer_activate (s->ctx, buffer);
|
buffer_activate (s->ctx, buffer);
|
||||||
else
|
else
|
||||||
@ -12618,8 +12628,6 @@ process_input (struct app_context *ctx, char *user_input)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
struct strv lines = strv_make ();
|
struct strv lines = strv_make ();
|
||||||
|
|
||||||
// XXX: this interprets commands in pasted text
|
|
||||||
cstr_split (input, "\r\n", false, &lines);
|
cstr_split (input, "\r\n", false, &lines);
|
||||||
for (size_t i = 0; i < lines.len; i++)
|
for (size_t i = 0; i < lines.len; i++)
|
||||||
(void) process_input_utf8 (ctx,
|
(void) process_input_utf8 (ctx,
|
||||||
@ -13152,7 +13160,7 @@ dump_input_to_file (struct app_context *ctx, char *template, struct error **e)
|
|||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return error_set (e, "%s", strerror (errno));
|
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);
|
bool success = xwrite (fd, input, strlen (input), e);
|
||||||
free (input);
|
free (input);
|
||||||
|
|
||||||
@ -13180,6 +13188,103 @@ try_dump_input_to_file (struct app_context *ctx)
|
|||||||
return NULL;
|
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
|
static bool
|
||||||
on_edit_input (int count, int key, void *user_data)
|
on_edit_input (int count, int key, void *user_data)
|
||||||
{
|
{
|
||||||
@ -13191,16 +13296,15 @@ on_edit_input (int count, int key, void *user_data)
|
|||||||
if (!(filename = try_dump_input_to_file (ctx)))
|
if (!(filename = try_dump_input_to_file (ctx)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const char *command;
|
struct strv argv = build_editor_command (ctx, filename);
|
||||||
if (!(command = getenv ("VISUAL"))
|
if (!argv.len)
|
||||||
&& !(command = getenv ("EDITOR")))
|
strv_append (&argv, "true");
|
||||||
command = "vi";
|
|
||||||
|
|
||||||
hard_assert (!ctx->running_editor);
|
hard_assert (!ctx->running_editor);
|
||||||
switch (spawn_helper_child (ctx))
|
switch (spawn_helper_child (ctx))
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
execlp (command, command, filename, NULL);
|
execvp (argv.vector[0], argv.vector);
|
||||||
print_error ("%s: %s",
|
print_error ("%s: %s",
|
||||||
"Failed to launch editor", strerror (errno));
|
"Failed to launch editor", strerror (errno));
|
||||||
_exit (EXIT_FAILURE);
|
_exit (EXIT_FAILURE);
|
||||||
@ -13213,6 +13317,7 @@ on_edit_input (int count, int key, void *user_data)
|
|||||||
ctx->running_editor = true;
|
ctx->running_editor = true;
|
||||||
ctx->editor_filename = filename;
|
ctx->editor_filename = filename;
|
||||||
}
|
}
|
||||||
|
strv_free (&argv);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13698,19 +13803,33 @@ on_editline_complete (EditLine *editline, int key)
|
|||||||
|
|
||||||
// Insert the best match instead
|
// Insert the best match instead
|
||||||
el_insertstr (editline, completions[0]);
|
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];
|
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++)
|
for (char **p = completions; *p; p++)
|
||||||
free (*p);
|
free (*p);
|
||||||
free (completions);
|
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)
|
if (!only_match)
|
||||||
return CC_REFRESH_BEEP;
|
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, " ");
|
el_insertstr (editline, " ");
|
||||||
return CC_REFRESH;
|
return CC_REFRESH;
|
||||||
}
|
}
|
||||||
@ -14117,6 +14236,40 @@ done:
|
|||||||
|
|
||||||
#define BRACKETED_PASTE_LIMIT 102400 ///< How much text can be pasted
|
#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
|
static void
|
||||||
process_bracketed_paste (const struct pollfd *fd, struct app_context *ctx)
|
process_bracketed_paste (const struct pollfd *fd, struct app_context *ctx)
|
||||||
{
|
{
|
||||||
@ -14141,7 +14294,7 @@ process_bracketed_paste (const struct pollfd *fd, struct app_context *ctx)
|
|||||||
(int) (text_len = BRACKETED_PASTE_LIMIT));
|
(int) (text_len = BRACKETED_PASTE_LIMIT));
|
||||||
|
|
||||||
buf->str[text_len] = '\0';
|
buf->str[text_len] = '\0';
|
||||||
if (CALL_ (ctx->input, insert, buf->str))
|
if (insert_paste (ctx, buf->str, text_len))
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user