diff --git a/xC.c b/xC.c index 1f4ff0f..3de9f26 100644 --- a/xC.c +++ b/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); } @@ -860,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); } @@ -2439,6 +2442,9 @@ 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 = "date_change_line", .comment = "Input to strftime(3) for the date change line", .type = CONFIG_ITEM_STRING, @@ -6894,7 +6900,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 @@ -13152,7 +13158,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); @@ -13180,6 +13186,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) { @@ -13191,16 +13294,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); @@ -13213,6 +13315,7 @@ on_edit_input (int count, int key, void *user_data) ctx->running_editor = true; ctx->editor_filename = filename; } + strv_free (&argv); return true; }