xC: allow passing the cursor position to editors
Add a configuration option to set a custom editor command, different from EDITOR or VISUAL--those remain as defaults. Implement substitutions allowing to convey cursor information to VIM and Emacs (the latter of which is fairly painful to cater to), and put usage hints in the configuration option's description. This should make the editing experience a bit more seamless for users, even though the position is carried over in one way only. No sophisticated quoting capabilities were deemed necessary, it is a lot of code already. The particular syntax is inspired by .desktop files and systemd. ["/bin/sh", "-c", "vim +$2go \"$1\"", filename, position, line, column] would be a slightly simpler but cryptic way of implementing this.
This commit is contained in:
parent
df4ca74580
commit
92ac13f3c6
125
xC.c
125
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,9 @@ static struct config_schema g_config_behaviour[] =
|
||||||
.type = CONFIG_ITEM_BOOLEAN,
|
.type = CONFIG_ITEM_BOOLEAN,
|
||||||
.default_ = "on",
|
.default_ = "on",
|
||||||
.on_change = on_config_word_wrapping_change },
|
.on_change = on_config_word_wrapping_change },
|
||||||
|
{ .name = "editor_command",
|
||||||
|
.comment = "VIM: \"vim +%Bgo %F\", Emacs: \"emacs -nw +%L:%C %F\"",
|
||||||
|
.type = CONFIG_ITEM_STRING },
|
||||||
{ .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 +6900,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
|
||||||
|
@ -13152,7 +13158,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 +13186,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 +13294,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 +13315,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue