diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2021-05-30 02:38:43 +0200 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2021-05-30 03:12:56 +0200 |
commit | 432ad24e382681f1c13b07e8486e91063dd96e2e (patch) | |
tree | 51838adac822767bd5884b9383cd4c72f29d3840 /src/spawn.cpp | |
parent | 524bc3960e6a6e5645ce904e20f72479e24e0a23 (diff) | |
download | sciteco-432ad24e382681f1c13b07e8486e91063dd96e2e.tar.gz |
THE GREAT CEEIFICATION EVENT
This is a total conversion of SciTECO to plain C (GNU C11).
The chance was taken to improve a lot of internal datastructures,
fix fundamental bugs and lay the foundations of future features.
The GTK user interface is now in an useable state!
All changes have been squashed together.
The language itself has almost not changed at all, except for:
* Detection of string terminators (usually Escape) now takes
the string building characters into account.
A string is only terminated outside of string building characters.
In other words, you can now for instance write
I^EQ[Hello$world]$
This removes one of the last bits of shellisms which is out of
place in SciTECO where no tokenization/lexing is performed.
Consequently, the current termination character can also be
escaped using ^Q/^R.
This is used by auto completions to make sure that strings
are inserted verbatim and without unwanted sideeffects.
* All strings can now safely contain null-characters
(see also: 8-bit cleanliness).
The null-character itself (^@) is not (yet) a valid SciTECO
command, though.
An incomplete list of changes:
* We got rid of the BSD headers for RB trees and lists/queues.
The problem with them was that they used a form of metaprogramming
only to gain a bit of type safety. It also resulted in less
readble code. This was a C++ desease.
The new code avoids metaprogramming only to gain type safety.
The BSD tree.h has been replaced by rb3ptr by Jens Stimpfle
(https://github.com/jstimpfle/rb3ptr).
This implementation is also more memory efficient than BSD's.
The BSD list.h and queue.h has been replaced with a custom
src/list.h.
* Fixed crashes, performance issues and compatibility issues with
the Gtk 3 User Interface.
It is now more or less ready for general use.
The GDK lock is no longer used to avoid using deprecated functions.
On the downside, the new implementation (driving the Gtk event loop
stepwise) is even slower than the old one.
A few glitches remain (see TODO), but it is hoped that they will
be resolved by the Scintilla update which will be performed soon.
* A lot of program units have been split up, so they are shorter
and easier to maintain: core-commands.c, qreg-commands.c,
goto-commands.c, file-utils.h.
* Parser states are simply structs of callbacks now.
They still use a kind of polymorphy using a preprocessor trick.
TECO_DEFINE_STATE() takes an initializer list that will be
merged with the default list of field initializers.
To "subclass" states, you can simply define new macros that add
initializers to existing macros.
* Parsers no longer have a "transitions" table but the input_cb()
may use switch-case statements.
There are also teco_machine_main_transition_t now which can
be used to implement simple transitions. Additionally, you
can specify functions to execute during transitions.
This largely avoids long switch-case-statements.
* Parsers are embeddable/reusable now, at least in parse-only mode.
This does not currently bring any advantages but may later
be used to write a Scintilla lexer for TECO syntax highlighting.
Once parsers are fully embeddable, it will also be possible
to run TECO macros in a kind of coroutine which would allow
them to process string arguments in real time.
* undo.[ch] still uses metaprogramming extensively but via
the C preprocessor of course. On the downside, most undo
token generators must be initiated explicitly (theoretically
we could have used embedded functions / trampolines to
instantiate automatically but this has turned out to be
dangereous).
There is a TECO_DEFINE_UNDO_CALL() to generate closures for
arbitrary functions now (ie. to call an arbitrary function
at undo-time). This simplified a lot of code and is much
shorter than manually pushing undo tokens in many cases.
* Instead of the ridiculous C++ Curiously Recurring Template
Pattern to achieve static polymorphy for user interface
implementations, we now simply declare all functions to
implement in interface.h and link in the implementations.
This is possible since we no longer hace to define
interface subclasses (all state is static variables in
the interface's *.c files).
* Headers are now significantly shorter than in C++ since
we can often hide more of our "class" implementations.
* Memory counting is based on dlmalloc for most platforms now.
Unfortunately, there is no malloc implementation that
provides an efficient constant-time memory counter that
is guaranteed to decrease when freeing memory.
But since we use a defined malloc implementation now,
malloc_usable_size() can be used safely for tracking memory use.
malloc() replacement is very tricky on Windows, so we
use a poll thread on Windows. This can also be enabled
on other supported platforms using --disable-malloc-replacement.
All in all, I'm still not pleased with the state of memory
limiting. It is a mess.
* Error handling uses GError now. This has the advantage that
the GError codes can be reused once we support error catching
in the SciTECO language.
* Added a few more test suite cases.
* Haiku is no longer supported as builds are instable and
I did not manage to debug them - quite possibly Haiku bugs
were responsible.
* Glib v2.44 or later are now required.
The GTK UI requires Gtk+ v3.12 or later now.
The GtkFlowBox fallback and sciteco-wrapper workaround are
no longer required.
* We now extensively use the GCC/Clang-specific g_auto
feature (automatic deallocations when leaving the current
code block).
* Updated copyright to 2021.
SciTECO has been in continuous development, even though there
have been no commits since 2018.
* Since these changes are so significant, the target release has
been set to v2.0.
It is planned that beginning with v3.0, the language will be
kept stable.
Diffstat (limited to 'src/spawn.cpp')
-rw-r--r-- | src/spawn.cpp | 662 |
1 files changed, 0 insertions, 662 deletions
diff --git a/src/spawn.cpp b/src/spawn.cpp deleted file mode 100644 index b3a8823..0000000 --- a/src/spawn.cpp +++ /dev/null @@ -1,662 +0,0 @@ -/* - * Copyright (C) 2012-2017 Robin Haberkorn - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include <glib.h> - -#include "sciteco.h" -#include "interface.h" -#include "undo.h" -#include "expressions.h" -#include "qregisters.h" -#include "eol.h" -#include "ring.h" -#include "parser.h" -#include "error.h" -#include "spawn.h" - -/* - * Debian 7 is still at libglib v2.33, so - * for the time being we support this UNIX-only - * implementation of g_spawn_check_exit_status() - * partially emulating libglib v2.34 - */ -#ifndef G_SPAWN_EXIT_ERROR -#ifdef G_OS_UNIX -#warning "libglib v2.34 or later recommended." -#else -#error "libglib v2.34 or later required." -#endif - -#include <sys/types.h> -#include <sys/wait.h> - -#define G_SPAWN_EXIT_ERROR \ - g_quark_from_static_string("g-spawn-exit-error-quark") - -static gboolean -g_spawn_check_exit_status(gint exit_status, GError **error) -{ - if (!WIFEXITED(exit_status)) { - g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, - "Abnormal process termination (%d)", - exit_status); - return FALSE; - } - - if (WEXITSTATUS(exit_status) != 0) { - g_set_error(error, G_SPAWN_EXIT_ERROR, WEXITSTATUS(exit_status), - "Unsuccessful exit status %d", - WEXITSTATUS(exit_status)); - return FALSE; - } - - return TRUE; -} - -#endif - -namespace SciTECO { - -namespace States { - StateExecuteCommand executecommand; - StateEGCommand egcommand; -} - -extern "C" { -static void child_watch_cb(GPid pid, gint status, gpointer data); -static gboolean stdin_watch_cb(GIOChannel *chan, - GIOCondition condition, gpointer data); -static gboolean stdout_watch_cb(GIOChannel *chan, - GIOCondition condition, gpointer data); -} - -static QRegister *register_argument = NULL; - -gchar ** -parse_shell_command_line(const gchar *cmdline, GError **error) -{ - gchar **argv; - -#ifdef G_OS_WIN32 - if (!(Flags::ed & Flags::ED_SHELLEMU)) { - QRegister *reg = QRegisters::globals["$COMSPEC"]; - argv = (gchar **)g_malloc(5*sizeof(gchar *)); - argv[0] = reg->get_string(); - argv[1] = g_strdup("/q"); - argv[2] = g_strdup("/c"); - argv[3] = g_strdup(cmdline); - argv[4] = NULL; - return argv; - } -#elif defined(G_OS_UNIX) || defined(G_OS_HAIKU) - if (!(Flags::ed & Flags::ED_SHELLEMU)) { - QRegister *reg = QRegisters::globals["$SHELL"]; - argv = (gchar **)g_malloc(4*sizeof(gchar *)); - argv[0] = reg->get_string(); - argv[1] = g_strdup("-c"); - argv[2] = g_strdup(cmdline); - argv[3] = NULL; - return argv; - } -#endif - - if (!g_shell_parse_argv(cmdline, NULL, &argv, error)) - return NULL; - - return argv; -} - -/*$ EC pipe filter - * EC[command]$ -- Execute operating system command and filter buffer contents - * linesEC[command]$ - * -EC[command]$ - * from,toEC[command]$ - * :EC[command]$ -> Success|Failure - * lines:EC[command]$ -> Success|Failure - * -:EC[command]$ -> Success|Failure - * from,to:EC[command]$ -> Success|Failure - * - * The EC command allows you to interface with the operating - * system shell and external programs. - * The external program is spawned as a background process - * and its standard input stream is fed with data from the - * current document, i.e. text is piped into the external - * program. - * When automatic EOL translation is enabled, this will - * translate all end of line sequences according to the - * source document's EOL mode (see \fBEL\fP command). - * For instance when piping from a document with DOS - * line breaks, the receiving program will only be sent - * DOS line breaks. - * The process' standard output stream is also redirected - * and inserted into the current document. - * End of line sequences are normalized accordingly - * but the EOL mode guessed from the program's output is - * \fBnot\fP set on the document. - * The process' standard error stream is discarded. - * If data is piped into the external program, its output - * replaces that data in the buffer. - * Dot is always left at the end of the insertion. - * - * If invoked without parameters, no data is piped into - * the process (and no characters are removed) and its - * output is inserted at the current buffer position. - * This is equivalent to invoking \(lq.,.EC\(rq. - * If invoked with one parameter, the next or previous number - * of <lines> are piped from the buffer into the program and - * its output replaces these <lines>. - * This effectively runs <command> as a filter over <lines>. - * \(lq-EC\(rq may be written as a short-cut for \(lq-1EC\(rq. - * When invoked with two parameters, the characters beginning - * at position <from> up to the character at position <to> - * are piped into the program and replaced with its output. - * This effectively runs <command> as a filter over a buffer - * range. - * - * Errors are thrown not only for invalid buffer ranges - * but also for errors during process execution. - * If the external <command> has an unsuccessful exit code, - * the EC command will also fail. - * If the EC command is colon-modified, it will instead return - * a TECO boolean signifying success or failure. - * In case of an unsuccessful exit code, a colon-modified EC - * will return the absolute value of the process exit - * code (which is also a TECO failure boolean) and 0 for all - * other failures. - * This feature may be used to take action depending on a - * specific process exit code. - * - * <command> execution is by default platform-dependent. - * On DOS-like systems like Windows, <command> is passed to - * the command interpreter specified in the \fB$COMSPEC\fP - * environment variable with the \(lq/q\(rq and \(lq/c\(rq - * command-line arguments. - * On UNIX-like systems, <command> is passed to the interpreter - * specified by the \fB$SHELL\fP environment variable - * with the \(lq-c\(rq command-line argument. - * Therefore the default shell can be configured using - * the corresponding environment registers. - * The operating system restrictions on the maximum - * length of command-line arguments apply to <command> and - * quoting of parameters within <command> is somewhat platform - * dependent. - * On all other platforms, \*(ST will uniformly parse - * <command> just as an UNIX98 \(lq/bin/sh\(rq would, but without - * performing any expansions. - * The program specified in <command> is searched for in - * standard locations (according to the \fB$PATH\fP environment - * variable). - * This mode of operation can also be enforced on all platforms - * by enabling bit 7 in the ED flag, e.g. by executing - * \(lq0,128ED\(rq, and is recommended when writing cross-platform - * macros using the EC command. - * - * When using an UNIX-compatible shell or the UNIX98 shell emulation, - * you might want to use the \fB^E@\fP string-building character - * to pass Q-Register contents reliably as single arguments to - * the spawned process. - * - * The spawned process inherits both \*(ST's current working - * directory and its environment variables. - * More precisely, \*(ST uses its environment registers - * to construct the spawned process' environment. - * Therefore it is also straight forward to change the working - * directory or some environment variable temporarily - * for a spawned process. - * - * Note that when run interactively and subsequently rubbed - * out, \*(ST can easily undo all changes to the editor - * state. - * It \fBcannot\fP however undo any other side-effects that the - * execution of <command> might have had on your system. - * - * Note also that the EC command blocks indefinitely until - * the <command> completes, which may result in editor hangs. - * You may however interrupt the spawned process by sending - * the \fBSIGINT\fP signal to \*(ST, e.g. by pressing CTRL+C. - * - * In interactive mode, \*(ST performs TAB-completion - * of filenames in the <command> string parameter but - * does not attempt any escaping of shell-relevant - * characters like whitespaces. - */ -StateExecuteCommand::StateExecuteCommand() : StateExpectString() -{ - /* - * Context and loop can be reused between EC invocations. - * However we should not use the default context, since it - * may be used by GTK - */ - ctx.mainctx = g_main_context_new(); - ctx.mainloop = g_main_loop_new(ctx.mainctx, FALSE); -} - -StateExecuteCommand::~StateExecuteCommand() -{ - g_main_loop_unref(ctx.mainloop); -#ifndef G_OS_HAIKU - /* - * Apparently, there's some kind of double-free - * bug in Haiku's glib-2.38. - * It is unknown whether this is has - * already been fixed and affects other platforms - * (but I never observed any segfaults). - */ - g_main_context_unref(ctx.mainctx); -#endif - - delete ctx.error; -} - -void -StateExecuteCommand::initial(void) -{ - tecoBool rc = SUCCESS; - - expressions.eval(); - - /* - * By evaluating arguments here, the command may fail - * before the string argument is typed - */ - switch (expressions.args()) { - case 0: - if (expressions.num_sign > 0) { - /* pipe nothing, insert at dot */ - ctx.from = ctx.to = interface.ssm(SCI_GETCURRENTPOS); - break; - } - /* fall through if prefix sign is "-" */ - - case 1: { - /* pipe and replace line range */ - sptr_t line; - - ctx.from = interface.ssm(SCI_GETCURRENTPOS); - line = interface.ssm(SCI_LINEFROMPOSITION, ctx.from) + - expressions.pop_num_calc(); - ctx.to = interface.ssm(SCI_POSITIONFROMLINE, line); - rc = TECO_BOOL(Validate::line(line)); - - if (ctx.to < ctx.from) { - tecoInt temp = ctx.from; - ctx.from = ctx.to; - ctx.to = temp; - } - - break; - } - - default: - /* pipe and replace character range */ - ctx.to = expressions.pop_num_calc(); - ctx.from = expressions.pop_num_calc(); - rc = TECO_BOOL(ctx.from <= ctx.to && - Validate::pos(ctx.from) && - Validate::pos(ctx.to)); - break; - } - - if (IS_FAILURE(rc)) { - if (eval_colon()) { - expressions.push(rc); - ctx.from = ctx.to = -1; - /* done() will still be called */ - } else { - throw RangeError("EC"); - } - } -} - -State * -StateExecuteCommand::done(const gchar *str) -{ - BEGIN_EXEC(&States::start); - - if (ctx.from < 0) - /* - * initial() failed without throwing - * error (colon-modified) - */ - return &States::start; - - GError *error = NULL; - gchar **argv, **envp; - static const gint flags = G_SPAWN_DO_NOT_REAP_CHILD | - G_SPAWN_SEARCH_PATH | - G_SPAWN_STDERR_TO_DEV_NULL; - - GPid pid; - gint stdin_fd, stdout_fd; - GIOChannel *stdin_chan, *stdout_chan; - - /* - * We always read from the current view, - * so we use its EOL mode. - * - * NOTE: We do not declare the writer/reader objects as part of - * StateExecuteCommand::Context so we do not have to - * reset it. It's only required for the life time of this call - * anyway. - * I do not see a more elegant way out of this. - */ - EOLWriterGIO stdin_writer(interface.ssm(SCI_GETEOLMODE)); - EOLReaderGIO stdout_reader; - - ctx.text_added = false; - - ctx.stdin_writer = &stdin_writer; - ctx.stdout_reader = &stdout_reader; - - delete ctx.error; - ctx.error = NULL; - ctx.rc = FAILURE; - - argv = parse_shell_command_line(str, &error); - if (!argv) - goto gerror; - - envp = QRegisters::globals.get_environ(); - - g_spawn_async_with_pipes(NULL, argv, envp, (GSpawnFlags)flags, - NULL, NULL, &pid, - &stdin_fd, &stdout_fd, NULL, - &error); - - g_strfreev(envp); - g_strfreev(argv); - - if (error) - goto gerror; - - ctx.child_src = g_child_watch_source_new(pid); - g_source_set_callback(ctx.child_src, (GSourceFunc)child_watch_cb, - &ctx, NULL); - g_source_attach(ctx.child_src, ctx.mainctx); - -#ifdef G_OS_WIN32 - stdin_chan = g_io_channel_win32_new_fd(stdin_fd); - stdout_chan = g_io_channel_win32_new_fd(stdout_fd); -#else /* the UNIX constructors should work everywhere else */ - stdin_chan = g_io_channel_unix_new(stdin_fd); - stdout_chan = g_io_channel_unix_new(stdout_fd); -#endif - g_io_channel_set_flags(stdin_chan, G_IO_FLAG_NONBLOCK, NULL); - g_io_channel_set_encoding(stdin_chan, NULL, NULL); - /* - * EOLWriterGIO expects the channel to be buffered - * for performance reasons - */ - g_io_channel_set_buffered(stdin_chan, TRUE); - g_io_channel_set_flags(stdout_chan, G_IO_FLAG_NONBLOCK, NULL); - g_io_channel_set_encoding(stdout_chan, NULL, NULL); - g_io_channel_set_buffered(stdout_chan, FALSE); - - stdin_writer.set_channel(stdin_chan); - stdout_reader.set_channel(stdout_chan); - - ctx.stdin_src = g_io_create_watch(stdin_chan, - (GIOCondition)(G_IO_OUT | G_IO_ERR | G_IO_HUP)); - g_source_set_callback(ctx.stdin_src, (GSourceFunc)stdin_watch_cb, - &ctx, NULL); - g_source_attach(ctx.stdin_src, ctx.mainctx); - - ctx.stdout_src = g_io_create_watch(stdout_chan, - (GIOCondition)(G_IO_IN | G_IO_ERR | G_IO_HUP)); - g_source_set_callback(ctx.stdout_src, (GSourceFunc)stdout_watch_cb, - &ctx, NULL); - g_source_attach(ctx.stdout_src, ctx.mainctx); - - if (!register_argument) { - if (current_doc_must_undo()) - interface.undo_ssm(SCI_GOTOPOS, interface.ssm(SCI_GETCURRENTPOS)); - interface.ssm(SCI_GOTOPOS, ctx.to); - } - - interface.ssm(SCI_BEGINUNDOACTION); - ctx.start = ctx.from; - g_main_loop_run(ctx.mainloop); - if (!register_argument) - interface.ssm(SCI_DELETERANGE, ctx.from, ctx.to - ctx.from); - interface.ssm(SCI_ENDUNDOACTION); - - if (register_argument) { - if (stdout_reader.eol_style >= 0) { - register_argument->undo_set_eol_mode(); - register_argument->set_eol_mode(stdout_reader.eol_style); - } - } else if (ctx.from != ctx.to || ctx.text_added) { - /* undo action is only effective if it changed anything */ - if (current_doc_must_undo()) - interface.undo_ssm(SCI_UNDO); - interface.ssm(SCI_SCROLLCARET); - ring.dirtify(); - } - - if (!g_source_is_destroyed(ctx.stdin_src)) - g_io_channel_shutdown(stdin_chan, TRUE, NULL); - g_io_channel_unref(stdin_chan); - g_source_unref(ctx.stdin_src); - g_io_channel_shutdown(stdout_chan, TRUE, NULL); - g_io_channel_unref(stdout_chan); - g_source_unref(ctx.stdout_src); - - g_source_unref(ctx.child_src); - g_spawn_close_pid(pid); - - if (ctx.error) { - if (!eval_colon()) - throw *ctx.error; - - /* - * This may contain the exit status - * encoded as a tecoBool. - */ - expressions.push(ctx.rc); - goto cleanup; - } - - if (interface.is_interrupted()) - throw Error("Interrupted"); - - if (eval_colon()) - expressions.push(SUCCESS); - - goto cleanup; - -gerror: - if (!eval_colon()) - throw GlibError(error); - g_error_free(error); - - expressions.push(ctx.rc); - -cleanup: - undo.push_var(register_argument) = NULL; - return &States::start; -} - -/*$ EG EGq - * EGq[command]$ -- Set Q-Register to output of operating system command - * linesEGq[command]$ - * -EGq[command]$ - * from,toEGq[command]$ - * :EGq[command]$ -> Success|Failure - * lines:EGq[command]$ -> Success|Failure - * -:EGq[command]$ -> Success|Failure - * from,to:EGq[command]$ -> Success|Failure - * - * Runs an operating system <command> and set Q-Register - * <q> to the data read from its standard output stream. - * Data may be fed to <command> from the current buffer/document. - * The interpretation of the parameters and <command> as well - * as the colon-modification is analoguous to the EC command. - * - * The EG command only differs from EC in not deleting any - * characters from the current buffer, not changing - * the current buffer position and writing process output - * to the Q-Register <q>. - * In other words, the current buffer is not modified by EG. - * Also since EG replaces the string value of <q>, the register's - * EOL mode is set to the mode guessed from the external program's - * output. - * - * The register <q> is defined if it does not already exist. - */ -State * -StateEGCommand::got_register(QRegister *reg) -{ - machine.reset(); - - BEGIN_EXEC(&States::executecommand); - undo.push_var(register_argument) = reg; - return &States::executecommand; -} - -/* - * Glib callbacks - */ - -static void -child_watch_cb(GPid pid, gint status, gpointer data) -{ - StateExecuteCommand::Context &ctx = - *(StateExecuteCommand::Context *)data; - GError *error = NULL; - - /* - * Writing stdin or reading stdout might have already - * failed. We preserve the earliest GError. - */ - if (!ctx.error && !g_spawn_check_exit_status(status, &error)) { - ctx.rc = error->domain == G_SPAWN_EXIT_ERROR - ? ABS(error->code) : FAILURE; - ctx.error = new GlibError(error); - } - - if (g_source_is_destroyed(ctx.stdout_src)) - g_main_loop_quit(ctx.mainloop); -} - -static gboolean -stdin_watch_cb(GIOChannel *chan, GIOCondition condition, gpointer data) -{ - StateExecuteCommand::Context &ctx = - *(StateExecuteCommand::Context *)data; - - sptr_t gap; - gsize convert_len; - const gchar *buffer; - gsize bytes_written; - - if (!(condition & G_IO_OUT)) - /* stdin might be closed prematurely */ - goto remove; - - /* we always read from the current view */ - gap = interface.ssm(SCI_GETGAPPOSITION); - convert_len = ctx.start < gap && gap < ctx.to - ? gap - ctx.start : ctx.to - ctx.start; - buffer = (const gchar *)interface.ssm(SCI_GETRANGEPOINTER, - ctx.start, convert_len); - - try { - /* - * This cares about automatic EOL conversion and - * returns the number of consumed bytes. - * If it can only write a part of the EOL sequence (ie. CR of CRLF) - * it may return a short byte count (possibly 0) which ensures that - * we do not yet remove the source. - */ - bytes_written = ctx.stdin_writer->convert(buffer, convert_len); - } catch (Error &e) { - ctx.error = new Error(e); - /* do not yet quit -- we still have to reap the child */ - goto remove; - } - - ctx.start += bytes_written; - - if (ctx.start == ctx.to) - /* this will signal EOF to the process */ - goto remove; - - return G_SOURCE_CONTINUE; - -remove: - /* - * Channel is always shut down here (fd is closed), - * so it's always shut down IF the GSource has been - * destroyed. It is not guaranteed to be destroyed - * during the main loop run however since it quits - * as soon as the child was reaped and stdout was read. - */ - g_io_channel_shutdown(chan, TRUE, NULL); - return G_SOURCE_REMOVE; -} - -static gboolean -stdout_watch_cb(GIOChannel *chan, GIOCondition condition, gpointer data) -{ - StateExecuteCommand::Context &ctx = - *(StateExecuteCommand::Context *)data; - - for (;;) { - const gchar *buffer; - gsize data_len; - - try { - buffer = ctx.stdout_reader->convert(data_len); - } catch (Error &e) { - ctx.error = new Error(e); - goto remove; - } - if (!buffer) - /* EOF */ - goto remove; - - if (!data_len) - return G_SOURCE_CONTINUE; - - if (register_argument) { - if (ctx.text_added) { - register_argument->undo_append_string(); - register_argument->append_string(buffer, data_len); - } else { - register_argument->undo_set_string(); - register_argument->set_string(buffer, data_len); - } - } else { - interface.ssm(SCI_ADDTEXT, data_len, (sptr_t)buffer); - } - ctx.text_added = true; - } - - /* not reached */ - return G_SOURCE_CONTINUE; - -remove: - if (g_source_is_destroyed(ctx.child_src)) - g_main_loop_quit(ctx.mainloop); - return G_SOURCE_REMOVE; -} - -} /* namespace SciTECO */ |