Altered tools.c to work with the new spawning module

The tools (from Edit -> Format -> Send Selection to) are now spawned
synchronously, which guarantees that both the document and it's
selection will be unchanged when the tool completes.
This commit is contained in:
Dimitar Zhekov 2015-03-23 19:46:04 +02:00
parent 3566baa608
commit 6e92c5f64a

View File

@ -33,6 +33,7 @@
#include "document.h"
#include "keybindings.h"
#include "sciwrappers.h"
#include "spawn.h"
#include "support.h"
#include "ui_utils.h"
#include "utils.h"
@ -48,7 +49,6 @@
#ifdef G_OS_UNIX
# include <sys/types.h>
# include <sys/wait.h>
# include <signal.h>
#endif
@ -76,26 +76,6 @@ struct cc_dialog
GtkWidget *button_down;
};
/* data required by the custom command callbacks */
struct cc_data
{
const gchar *command; /* command launched */
GeanyDocument *doc; /* document in which replace the selection */
GString *buffer; /* buffer holding stdout content, or NULL */
gboolean error; /* whether and error occurred */
gboolean finished; /* whether the command has finished */
};
static gboolean cc_exists_command(const gchar *command)
{
gchar *path = g_find_program_in_path(command);
g_free(path);
return path != NULL;
}
/* update STATUS and TOOLTIP columns according to cmd */
static void cc_dialog_update_row_status(GtkListStore *store, GtkTreeIter *iter, const gchar *cmd)
@ -103,19 +83,9 @@ static void cc_dialog_update_row_status(GtkListStore *store, GtkTreeIter *iter,
GError *err = NULL;
const gchar *stock_id = GTK_STOCK_NO;
gchar *tooltip = NULL;
gint argc;
gchar **argv;
if (EMPTY(cmd))
if (EMPTY(cmd) || spawn_check_command(cmd, TRUE, &err))
stock_id = GTK_STOCK_YES;
else if (g_shell_parse_argv(cmd, &argc, &argv, &err))
{
if (argc > 0 && cc_exists_command(argv[0]))
stock_id = GTK_STOCK_YES;
else
tooltip = g_strdup_printf(_("Invalid command: %s"), _("Command not found"));
g_strfreev(argv);
}
else
{
tooltip = g_strdup_printf(_("Invalid command: %s"), err->message);
@ -227,218 +197,59 @@ static void cc_on_dialog_move_down_clicked(GtkButton *button, struct cc_dialog *
}
static gboolean cc_iofunc(GIOChannel *ioc, GIOCondition cond, gpointer user_data)
{
struct cc_data *data = user_data;
if (cond & (G_IO_IN | G_IO_PRI))
{
gchar *msg = NULL;
GIOStatus rv;
GError *err = NULL;
if (! data->buffer)
data->buffer = g_string_sized_new(256);
do
{
rv = g_io_channel_read_line(ioc, &msg, NULL, NULL, &err);
if (msg != NULL)
{
g_string_append(data->buffer, msg);
g_free(msg);
}
if (G_UNLIKELY(err != NULL))
{
geany_debug("%s: %s", G_STRFUNC, err->message);
g_error_free(err);
err = NULL;
}
} while (rv == G_IO_STATUS_NORMAL || rv == G_IO_STATUS_AGAIN);
if (G_UNLIKELY(rv != G_IO_STATUS_EOF))
{ /* Something went wrong? */
g_warning("%s: %s\n", G_STRFUNC, "Incomplete command output");
}
}
return FALSE;
}
static gboolean cc_iofunc_err(GIOChannel *ioc, GIOCondition cond, gpointer user_data)
{
struct cc_data *data = user_data;
if (cond & (G_IO_IN | G_IO_PRI))
{
gchar *msg = NULL;
GString *str = g_string_sized_new(256);
GIOStatus rv;
do
{
rv = g_io_channel_read_line(ioc, &msg, NULL, NULL, NULL);
if (msg != NULL)
{
g_string_append(str, msg);
g_free(msg);
}
} while (rv == G_IO_STATUS_NORMAL || rv == G_IO_STATUS_AGAIN);
if (!EMPTY(str->str))
{
g_warning("%s: %s\n", data->command, str->str);
ui_set_statusbar(TRUE,
_("The executed custom command returned an error. "
"Your selection was not changed. Error message: %s"),
str->str);
data->error = TRUE;
}
g_string_free(str, TRUE);
}
data->finished = TRUE;
return FALSE;
}
static gboolean cc_replace_sel_cb(gpointer user_data)
{
struct cc_data *data = user_data;
if (! data->finished)
{ /* keep this function in the main loop until cc_iofunc_err() has finished */
return TRUE;
}
if (! data->error && data->buffer != NULL && DOC_VALID(data->doc))
{ /* Command completed successfully */
sci_replace_sel(data->doc->editor->sci, data->buffer->str);
}
if (data->buffer)
g_string_free(data->buffer, TRUE);
g_slice_free1(sizeof *data, data);
return FALSE;
}
/* check whether the executed command failed and if so do nothing.
* If it returned with a sucessful exit code, replace the selection. */
static void cc_exit_cb(GPid child_pid, gint status, gpointer user_data)
{
struct cc_data *data = user_data;
/* if there was already an error, skip further checks */
if (! data->error)
{
#ifdef G_OS_UNIX
if (WIFEXITED(status))
{
if (WEXITSTATUS(status) != EXIT_SUCCESS)
data->error = TRUE;
}
else if (WIFSIGNALED(status))
{ /* the terminating signal: WTERMSIG (status)); */
data->error = TRUE;
}
else
{ /* any other failure occured */
data->error = TRUE;
}
#else
data->error = ! win32_get_exit_status(child_pid);
#endif
if (data->error)
{ /* here we are sure data->error was set due to an unsuccessful exit code
* and so we add an error message */
/* TODO maybe include the exit code in the error message */
ui_set_statusbar(TRUE,
_("The executed custom command exited with an unsuccessful exit code."));
}
}
g_idle_add(cc_replace_sel_cb, data);
g_spawn_close_pid(child_pid);
}
/* Executes command (which should include all necessary command line args) and passes the current
* selection through the standard input of command. The whole output of command replaces the
* current selection. */
void tools_execute_custom_command(GeanyDocument *doc, const gchar *command)
{
GError *error = NULL;
GPid pid;
gchar **argv;
gint stdin_fd;
gint stdout_fd;
gint stderr_fd;
g_return_if_fail(DOC_VALID(doc) && command != NULL);
gchar *sel;
SpawnWriteData input;
GString *output = g_string_sized_new(256);
GString *errors = g_string_new(NULL);
gint status;
g_return_if_fail(doc != NULL && command != NULL);
if (! sci_has_selection(doc->editor->sci))
editor_select_lines(doc->editor, FALSE);
if (!g_shell_parse_argv(command, NULL, &argv, &error))
{
ui_set_statusbar(TRUE, _("Custom command failed: %s"), error->message);
g_error_free(error);
return;
}
sel = sci_get_selection_contents(doc->editor->sci);
input.ptr = sel;
input.size = strlen(sel);
ui_set_statusbar(TRUE, _("Passing data and executing custom command: %s"), command);
if (g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
NULL, NULL, &pid, &stdin_fd, &stdout_fd, &stderr_fd, &error))
if (spawn_sync(NULL, command, NULL, NULL, &input, output, errors, &status, &error))
{
gchar *sel;
gint remaining, wrote;
struct cc_data *data = g_slice_alloc(sizeof *data);
data->error = FALSE;
data->finished = FALSE;
data->buffer = NULL;
data->doc = doc;
data->command = command;
g_child_watch_add(pid, cc_exit_cb, data);
/* use GIOChannel to monitor stdout */
utils_set_up_io_channel(stdout_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
FALSE, cc_iofunc, data);
/* copy program's stderr to Geany's stdout to help error tracking */
utils_set_up_io_channel(stderr_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
FALSE, cc_iofunc_err, data);
/* get selection */
sel = sci_get_selection_contents(doc->editor->sci);
/* write data to the command */
remaining = strlen(sel);
do
if (*errors->str)
{
wrote = write(stdin_fd, sel, remaining);
if (G_UNLIKELY(wrote < 0))
{
g_warning("%s: %s: %s\n", G_STRFUNC, "Failed sending data to command",
g_strerror(errno));
break;
}
remaining -= wrote;
} while (remaining > 0);
close(stdin_fd);
g_free(sel);
g_warning("%s: %s\n", command, errors->str);
ui_set_statusbar(TRUE,
_("The executed custom command returned an error. "
"Your selection was not changed. Error message: %s"),
errors->str);
}
else if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS)
{
/* TODO maybe include the exit code in the error message */
ui_set_statusbar(TRUE,
_("The executed custom command exited with an unsuccessful exit code."));
}
else if (output)
{ /* Command completed successfully */
sci_replace_sel(doc->editor->sci, output->str);
}
}
else
{
geany_debug("g_spawn_async_with_pipes() failed: %s", error->message);
geany_debug("spawn_sync() failed: %s", error->message);
ui_set_statusbar(TRUE, _("Custom command failed: %s"), error->message);
g_error_free(error);
}
g_strfreev(argv);
g_string_free(output, TRUE);
g_string_free(errors, TRUE);
g_free(sel);
}