degesch: allow launching an editor for input

Useful for editing multiline text (such as making it single-line).

Some refactoring and cleanup.
This commit is contained in:
Přemysl Eric Janouch 2015-12-25 04:21:18 +01:00
parent 37e9165548
commit e101afab38
3 changed files with 251 additions and 48 deletions

2
NEWS
View File

@ -11,6 +11,8 @@
* degesch: libedit backend works again * degesch: libedit backend works again
* degesch: added capability to edit the input line using VISUAL/EDITOR
* degesch: correctly respond to stopping and resuming (SIGTSTP) * degesch: correctly respond to stopping and resuming (SIGTSTP)
* degesch: fixed decoding of text formatting * degesch: fixed decoding of text formatting

View File

@ -54,6 +54,49 @@ str_vector_find (const struct str_vector *v, const char *s)
return -1; return -1;
} }
/// This differs from the non-unique version in that we expect the filename
/// to be something like a pattern for mkstemp(), so the resulting path can
/// reside in a system-wide directory with no risk of a conflict.
static char *
resolve_relative_runtime_unique_filename (const char *filename)
{
struct str path;
str_init (&path);
const char *runtime_dir = getenv ("XDG_RUNTIME_DIR");
if (runtime_dir && *runtime_dir == '/')
str_append (&path, runtime_dir);
else
str_append (&path, "/tmp");
str_append_printf (&path, "/%s/%s", PROGRAM_NAME, filename);
// Try to create the file's ancestors;
// typically the user will want to immediately create a file in there
const char *last_slash = strrchr (path.str, '/');
if (last_slash && last_slash != path.str)
{
char *copy = xstrndup (path.str, last_slash - path.str);
(void) mkdir_with_parents (copy, NULL);
free (copy);
}
return str_steal (&path);
}
static bool
xwrite (int fd, const char *data, size_t len, struct error **e)
{
size_t written = 0;
while (written < len)
{
ssize_t res = write (fd, data + written, len - written);
if (res >= 0)
written += res;
else if (errno != EINTR)
FAIL ("%s", strerror (errno));
}
return true;
}
// --- Logging ----------------------------------------------------------------- // --- Logging -----------------------------------------------------------------
static void static void

254
degesch.c
View File

@ -149,8 +149,6 @@ struct input
int saved_mark; ///< Saved mark int saved_mark; ///< Saved mark
#elif defined HAVE_EDITLINE #elif defined HAVE_EDITLINE
EditLine *editline; ///< The EditLine object EditLine *editline; ///< The EditLine object
char *(*saved_prompt) (EditLine *); ///< Saved prompt function
char saved_char; ///< Saved char for the prompt
#endif // HAVE_EDITLINE #endif // HAVE_EDITLINE
char *prompt; ///< The prompt we use char *prompt; ///< The prompt we use
@ -220,14 +218,22 @@ input_set_prompt (struct input *self, char *prompt)
rl_redisplay (); rl_redisplay ();
} }
static void
input_erase_content (struct input *self)
{
(void) self;
rl_replace_line ("", false);
rl_redisplay ();
}
static void static void
input_erase (struct input *self) input_erase (struct input *self)
{ {
(void) self; (void) self;
rl_set_prompt (""); rl_set_prompt ("");
rl_replace_line ("", false); input_erase_content (self);
rl_redisplay ();
} }
static void static void
@ -269,6 +275,13 @@ input_insert (struct input *self, const char *s)
return true; return true;
} }
static char *
input_get_content (struct input *self)
{
(void) self;
return rl_copy_text (0, rl_end);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static int app_readline_init (void); static int app_readline_init (void);
@ -560,29 +573,39 @@ input_make_empty_prompt (EditLine *editline)
} }
static void static void
input_erase (struct input *self) input_erase_content (struct input *self)
{ {
const LineInfoW *info = el_wline (self->editline); const LineInfoW *info = el_wline (self->editline);
int len = info->lastchar - info->buffer; int len = info->lastchar - info->buffer;
int point = info->cursor - info->buffer; int point = info->cursor - info->buffer;
el_cursor (self->editline, len - point); el_cursor (self->editline, len - point);
el_wdeletestr (self->editline, len); el_wdeletestr (self->editline, len);
// XXX: this doesn't seem to save the escape character
el_get (self->editline, EL_PROMPT, &self->saved_prompt, &self->saved_char);
el_set (self->editline, EL_PROMPT, input_make_empty_prompt);
input_redisplay (self); input_redisplay (self);
} }
static void
input_erase (struct input *self)
{
el_set (self->editline, EL_PROMPT, input_make_empty_prompt);
input_erase_content (self);
}
static bool static bool
input_insert (struct input *self, const char *s) input_insert (struct input *self, const char *s)
{ {
bool success = !el_insertstr (self->editline, s); bool success = !*s || !el_insertstr (self->editline, s);
if (self->prompt_shown > 0) if (self->prompt_shown > 0)
input_redisplay (self); input_redisplay (self);
return success; return success;
} }
static char *
input_get_content (struct input *self)
{
const LineInfo *info = el_line (self->editline);
return xstrndup (info->buffer, info->lastchar - info->buffer);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void static void
@ -699,8 +722,7 @@ input_show (struct input *self)
return; return;
input_restore (self); input_restore (self);
// Would have used "saved_char" but it doesn't seem to work. // XXX: the ignore doesn't quite work, see https://gnats.netbsd.org/47539
// And it doesn't even when it does anyway (it seems to just strip it).
el_set (self->editline, el_set (self->editline,
EL_PROMPT_ESC, input_make_prompt, INPUT_START_IGNORE); EL_PROMPT_ESC, input_make_prompt, INPUT_START_IGNORE);
input_redisplay (self); input_redisplay (self);
@ -1518,6 +1540,8 @@ struct app_context
struct str input_buffer; ///< Buffered pasted content struct str input_buffer; ///< Buffered pasted content
bool running_backlog_helper; ///< Running a backlog helper bool running_backlog_helper; ///< Running a backlog helper
bool running_editor; ///< Running editor for the input
char *editor_filename; ///< The file being edited by user
int terminal_suspended; ///< Terminal suspension level int terminal_suspended; ///< Terminal suspension level
struct plugin *plugins; ///< Loaded plugins struct plugin *plugins; ///< Loaded plugins
@ -1624,6 +1648,8 @@ app_context_free (struct app_context *self)
input_free (&self->input); input_free (&self->input);
str_free (&self->input_buffer); str_free (&self->input_buffer);
free (self->editor_filename);
} }
static void refresh_prompt (struct app_context *ctx); static void refresh_prompt (struct app_context *ctx);
@ -10107,47 +10133,52 @@ jump_to_buffer (struct app_context *ctx, int n)
return true; return true;
} }
static void static pid_t
exec_backlog_helper (const char *command, FILE *backlog) spawn_helper_child (struct app_context *ctx)
{ {
dup2 (fileno (backlog), STDIN_FILENO); suspend_terminal (ctx);
pid_t child = fork ();
// Put the child into a new foreground process group switch (child)
hard_assert (setpgid (0, 0)!= -1); {
hard_assert (tcsetpgrp (STDOUT_FILENO, getpgid (0)) != -1); case -1:
{
execl ("/bin/sh", "/bin/sh", "-c", command, NULL); int saved_errno = errno;
print_error ("%s: %s", "Failed to launch backlog helper", strerror (errno)); resume_terminal (ctx);
_exit (EXIT_FAILURE); errno = saved_errno;
break;
}
case 0:
// Put the child in a new foreground process group
hard_assert (setpgid (0, 0) != -1);
hard_assert (tcsetpgrp (STDOUT_FILENO, getpgid (0)) != -1);
break;
default:
// Make sure of it in the parent as well before continuing
(void) setpgid (child, child);
}
return child;
} }
static void static void
launch_backlog_helper (struct app_context *ctx, FILE *backlog) launch_backlog_helper (struct app_context *ctx, FILE *backlog)
{ {
hard_assert (!ctx->running_backlog_helper); hard_assert (!ctx->running_backlog_helper);
suspend_terminal (ctx); switch (spawn_helper_child (ctx))
pid_t child = fork ();
if (child == 0)
exec_backlog_helper (get_config_string
(ctx->config.root, "behaviour.backlog_helper"), backlog);
if (child == -1)
{ {
int saved_errno = errno; case 0:
resume_terminal (ctx); dup2 (fileno (backlog), STDIN_FILENO);
execl ("/bin/sh", "/bin/sh", "-c", get_config_string
(ctx->config.root, "behaviour.backlog_helper"), NULL);
print_error ("%s: %s",
"Failed to launch backlog helper", strerror (errno));
_exit (EXIT_FAILURE);
case -1:
log_global_error (ctx, "#s: #s", log_global_error (ctx, "#s: #s",
"Failed to launch backlog helper", strerror (saved_errno)); "Failed to launch backlog helper", strerror (errno));
} break;
else default:
{
// Make sure the child has its own process group
(void) setpgid (child, child);
ctx->running_backlog_helper = true; ctx->running_backlog_helper = true;
} }
fclose (backlog);
} }
static void static void
@ -10168,6 +10199,7 @@ display_backlog (struct app_context *ctx)
rewind (backlog); rewind (backlog);
set_cloexec (fileno (backlog)); set_cloexec (fileno (backlog));
launch_backlog_helper (ctx, backlog); launch_backlog_helper (ctx, backlog);
fclose (backlog);
} }
static void static void
@ -10189,6 +10221,103 @@ display_full_log (struct app_context *ctx)
set_cloexec (fileno (full_log)); set_cloexec (fileno (full_log));
launch_backlog_helper (ctx, full_log); launch_backlog_helper (ctx, full_log);
fclose (full_log);
}
static bool
dump_input_to_file (struct app_context *ctx, char *template, struct error **e)
{
int fd = mkstemp (template);
if (fd < 0)
FAIL ("%s", strerror (errno));
char *input = input_get_content (&ctx->input);
bool success = xwrite (fd, input, strlen (input), e);
free (input);
if (!success)
(void) unlink (template);
xclose (fd);
return success;
}
static char *
try_dump_input_to_file (struct app_context *ctx)
{
char *template = resolve_filename
("input.XXXXXX", resolve_relative_runtime_unique_filename);
struct error *e = NULL;
if (dump_input_to_file (ctx, template, &e))
return template;
log_global_error (ctx, "#s: #s",
"Failed to create a temporary file for editing", e->message);
error_free (e);
free (template);
return NULL;
}
static void
launch_input_editor (struct app_context *ctx)
{
char *filename;
if (!(filename = try_dump_input_to_file (ctx)))
return;
const char *command;
if (!(command = getenv ("VISUAL"))
&& !(command = getenv ("EDITOR")))
command = "vi";
hard_assert (!ctx->running_editor);
switch (spawn_helper_child (ctx))
{
case 0:
execlp (command, command, filename, NULL);
print_error ("%s: %s",
"Failed to launch editor", strerror (errno));
_exit (EXIT_FAILURE);
case -1:
log_global_error (ctx, "#s: #s",
"Failed to launch editor", strerror (errno));
free (filename);
break;
default:
ctx->running_editor = true;
ctx->editor_filename = filename;
}
}
static void
process_edited_input (struct app_context *ctx)
{
struct str input;
str_init (&input);
struct error *e = NULL;
if (!read_file (ctx->editor_filename, &input, &e))
{
log_global_error (ctx, "#s: #s", "Input editing failed", e->message);
error_free (e);
}
else
input_erase_content (&ctx->input);
if (!input_insert (&ctx->input, input.str))
log_global_error (ctx, "#s: #s", "Input editing failed",
"could not re-insert the modified text");
if (unlink (ctx->editor_filename))
log_global_error (ctx, "Could not unlink `#s': #s",
ctx->editor_filename, strerror (errno));
free (ctx->editor_filename);
ctx->editor_filename = NULL;
str_free (&input);
ctx->running_editor = false;
} }
static void static void
@ -10204,6 +10333,7 @@ bind_common_keys (struct app_context *ctx)
input_bind_meta (self, 'm', "insert-attribute"); input_bind_meta (self, 'm', "insert-attribute");
input_bind_meta (self, 'h', "display-full-log"); input_bind_meta (self, 'h', "display-full-log");
input_bind_meta (self, 'e', "edit-input");
if (key_f5) if (key_f5)
input_bind (self, key_f5, "previous-buffer"); input_bind (self, key_f5, "previous-buffer");
@ -10275,6 +10405,17 @@ on_readline_display_full_log (int count, int key)
return 0; return 0;
} }
static int
on_readline_edit_input (int count, int key)
{
(void) count;
(void) key;
struct app_context *ctx = g_ctx;
launch_input_editor (ctx);
return 0;
}
static int static int
on_readline_redraw_screen (int count, int key) on_readline_redraw_screen (int count, int key)
{ {
@ -10380,6 +10521,7 @@ app_readline_init (void)
rl_add_defun ("goto-buffer", on_readline_goto_buffer, -1); rl_add_defun ("goto-buffer", on_readline_goto_buffer, -1);
rl_add_defun ("display-backlog", on_readline_display_backlog, -1); rl_add_defun ("display-backlog", on_readline_display_backlog, -1);
rl_add_defun ("display-full-log", on_readline_display_full_log, -1); rl_add_defun ("display-full-log", on_readline_display_full_log, -1);
rl_add_defun ("edit-input", on_readline_edit_input, -1);
rl_add_defun ("redraw-screen", on_readline_redraw_screen, -1); rl_add_defun ("redraw-screen", on_readline_redraw_screen, -1);
rl_add_defun ("insert-attribute", on_readline_insert_attribute, -1); rl_add_defun ("insert-attribute", on_readline_insert_attribute, -1);
rl_add_defun ("start-paste-mode", on_readline_start_paste_mode, -1); rl_add_defun ("start-paste-mode", on_readline_start_paste_mode, -1);
@ -10460,6 +10602,16 @@ on_editline_display_full_log (EditLine *editline, int key)
return CC_NORM; return CC_NORM;
} }
static unsigned char
on_editline_edit_input (EditLine *editline, int key)
{
(void) editline;
(void) key;
launch_input_editor (g_ctx);
return CC_NORM;
}
static unsigned char static unsigned char
on_editline_redraw_screen (EditLine *editline, int key) on_editline_redraw_screen (EditLine *editline, int key)
{ {
@ -10588,6 +10740,7 @@ app_editline_init (struct input *self)
{ "next-buffer", "Next buffer", on_editline_next_buffer }, { "next-buffer", "Next buffer", on_editline_next_buffer },
{ "display-backlog", "Show backlog", on_editline_display_backlog }, { "display-backlog", "Show backlog", on_editline_display_backlog },
{ "display-full-log", "Show full log", on_editline_display_full_log }, { "display-full-log", "Show full log", on_editline_display_full_log },
{ "edit-input", "Edit input", on_editline_edit_input },
{ "redraw-screen", "Redraw screen", on_editline_redraw_screen }, { "redraw-screen", "Redraw screen", on_editline_redraw_screen },
{ "insert-attribute", "mIRC formatting", on_editline_insert_attribute }, { "insert-attribute", "mIRC formatting", on_editline_insert_attribute },
{ "start-paste-mode", "Bracketed paste", on_editline_start_paste_mode }, { "start-paste-mode", "Bracketed paste", on_editline_start_paste_mode },
@ -10846,19 +10999,22 @@ try_reap_child (struct app_context *ctx)
if (!zombie) if (!zombie)
return false; return false;
if (!ctx->running_backlog_helper)
{
print_debug ("an unknown child has died");
return true;
}
if (WIFSTOPPED (status)) if (WIFSTOPPED (status))
{ {
// We could also send SIGCONT but what's the point // We could also send SIGCONT but what's the point
print_debug ("a child has been stopped, killing its process group");
kill (-zombie, SIGKILL); kill (-zombie, SIGKILL);
return true; return true;
} }
ctx->running_backlog_helper = false; if (ctx->running_backlog_helper)
ctx->running_backlog_helper = false;
else if (!ctx->running_editor)
{
print_debug ("an unknown child has died");
return true;
}
hard_assert (tcsetpgrp (STDOUT_FILENO, getpgid (0)) != -1); hard_assert (tcsetpgrp (STDOUT_FILENO, getpgid (0)) != -1);
resume_terminal (ctx); resume_terminal (ctx);
@ -10868,6 +11024,8 @@ try_reap_child (struct app_context *ctx)
else if (WIFEXITED (status) && WEXITSTATUS (status) != 0) else if (WIFEXITED (status) && WEXITSTATUS (status) != 0)
log_global_error (ctx, log_global_error (ctx,
"Child returned status #d", WEXITSTATUS (status)); "Child returned status #d", WEXITSTATUS (status));
else if (ctx->running_editor)
process_edited_input (ctx);
return true; return true;
} }