aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2016-08-16 05:04:54 +0200
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2016-08-19 03:29:11 +0200
commit61ff6e97c57f62ee3ad4ffc2166e433bc060e7cb (patch)
tree64c032ebdd040809d1b7e822e627e199a9c2476e
parent94b041ec331427fd63cdae3e943efe825d1bbf14 (diff)
downloadsciteco-61ff6e97c57f62ee3ad4ffc2166e433bc060e7cb.tar.gz
Integrated clipboard support
* mapped to different registers beginning with "~" * on supported platforms accessing the clipboard is as easy as X~ or G~. Naturally this also allows clipboards to be pasted in string arguments/insertions (^EQ~). * Currently, Gtk+, PDCurses and ncurses/XTerm are supported. For XTerm clipboard support, users must set 0,256ED to enable it since we cannot check for XTerm window ops programmatically (at least without libX11). * When clipboard regs exist, the clipboard can also be deemed functional. This allows macros to fall back to xclip(1) if necessary. * EOL handling has been moved into a new file eol.c and eol.h. EOL translation no longer depends on GIOChannels but can be memory-backed as well.
-rw-r--r--TODO22
-rw-r--r--doc/sciteco.7.template47
-rw-r--r--sample.teco_ini3
-rw-r--r--src/Makefile.am1
-rw-r--r--src/eol.cpp360
-rw-r--r--src/eol.h160
-rw-r--r--src/error.h2
-rw-r--r--src/interface-curses/interface-curses.cpp364
-rw-r--r--src/interface-curses/interface-curses.h8
-rw-r--r--src/interface-gtk/interface-gtk.cpp74
-rw-r--r--src/interface-gtk/interface-gtk.h6
-rw-r--r--src/interface.h16
-rw-r--r--src/ioview.cpp353
-rw-r--r--src/ioview.h14
-rw-r--r--src/parser.cpp4
-rw-r--r--src/qregisters.cpp187
-rw-r--r--src/qregisters.h79
-rw-r--r--src/sciteco.h9
-rw-r--r--src/spawn.cpp180
-rw-r--r--src/spawn.h12
20 files changed, 1460 insertions, 441 deletions
diff --git a/TODO b/TODO
index 5374869..733ba9c 100644
--- a/TODO
+++ b/TODO
@@ -29,6 +29,13 @@ Known Bugs:
We should fall back silently to an (inefficient) memory copy or temporary
file strategy if this is detected.
* crashes on large files: S^EM^X$ (regexp: .*)
+ Happens because the Glib regex engine is based on a recursive
+ Perl regex library.
+ This is apparently impossible to fix as long as we do not
+ have control over the regex engine build. We should either use C++11
+ regex support, UNIX regex (unportable) or some other library.
+ Perhaps allowing us to interpret the SciTECO matching language
+ directly.
* the glib allocators are fundamentally broken:
throwing exceptions is unsafe from C-linkage callbacks.
We should abandon the custom allocators and rely on
@@ -43,6 +50,13 @@ Known Bugs:
savepoint restoration.
On Windows we could work around this using
MoveFileEx(file, NULL, MOVEFILE_DELAY_UNTIL_REBOOT)
+ * Clipboard registers are prone to race conditions if the
+ contents change between get_size() and get_string() calls.
+ Also it's a common idiom to query a string and its size,
+ so the internal API must be changed.
+ * Setting window title is broken on ncurses/XTerm.
+ Perhaps do some XTerm magic here. We can also restore
+ window titles on exit using XTerm.
Features:
* :$ and :$$ to pop/return only single values
@@ -172,14 +186,6 @@ Features:
Each Scintilla view could then be associated with at most
one curses screen.
GTK+ would simply manage a list of windows.
- * Add a command for manipulating the clipboard.
- Can be done with ECxclip$ on Unix, but other platforms have
- better methods (e.g. PDCurses has clipboard extensions, GTK+
- has native clipboard support). On ncurses/UNIX we can fall back
- to spawning xclip or copy xclip's functionality.
- Unfortunately, not every UNIX has X11 (e.g. linux console,
- OS X). What do we do on those systems?
- This will also allow us to reverse clipboard modifications.
Optimizations:
* Add G_UNLIKELY to all error throws.
diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template
index 9b31c7d..6ed89db 100644
--- a/doc/sciteco.7.template
+++ b/doc/sciteco.7.template
@@ -1267,6 +1267,53 @@ Their numeric parts are currently unused.
The mechanisms involved are documented more elaborately in
.BR sciteco (1).
.TP
+.BI ~ clipboard
+These registers constitute \*(ST's support for system clipboards.
+Clipboard support is highly UI-specific, so different
+UIs might support different clipboards (or X11 selections)
+or no clipboard at all.
+\*(ST thus initializes registers beginning with \(lq~\(rq for
+every available clipboard either on startup or only when
+entering interactive mode.
+The register \(lq~\(rq refers to the default clipboard which
+will always exist if clipboards are supported.
+Other commonly used clipboard registers are \(lq~P\(rq for the
+primary selection, \(lq~S\(rq for the secondary selection and
+\(lq~C\(rq for the clipboard selection.
+The existence of a clipboard register can thus be checked
+in macros to determine whether getting and modifying that
+particular clipboard is supported natively.
+.br
+\*(ST does \fBnot\fP generally support clipboards on ncurses,
+but has special support when used with a sufficiently recent version
+of \fBxterm\fP(1).
+Since the operability of XTerm clipboards cannot be tested
+automatically, users will have to set the flag 256 of the
+\fBED\fP flags if and only if their XTerm is configured for allowing
+the \fISetSelection\fP and \fIGetSelection\fP window operations.
+\*(ST will still check whether XTerm is actually used in
+a particular session.
+If native clipboard support is unavailable, users may
+still fall back to using external tools like \fBxclip\fP(1)
+with the \fBEC\fP command.
+.br
+Setting the string part of a clipboard register will set that
+clipboard. \*(ST will perform automatic EOL-translation according
+to the EOL-mode of the Q-Register view, so e.g. on Windows
+clipboards will usually be set with the expected DOS linebreaks.
+Appending to clipboard registers is currently not supported.
+Furthermore, \*(ST will restore the contents of the clipboard
+at the time of the operation when an assignment is rubbed out in
+interactive mode.
+When retrieving a clipboard register, the contents of the
+clipboard at the time of the operation are returned.
+EOL normalization will take place (if enabled), so that pasting
+clipboards does not introduce unexpected EOL sequences.
+The Q-Register view's EOL mode will \fBnot\fP be guessed from
+the original clipboard contents, though.
+The numeric parts of the clipboard registers are currently
+not used by \*(ST.
+.TP
.BI ^F key
Function key registers as documented in section
\fBKEY TRANSLATION\fP.
diff --git a/sample.teco_ini b/sample.teco_ini
index fb02d34..6360d14 100644
--- a/sample.teco_ini
+++ b/sample.teco_ini
@@ -46,6 +46,9 @@ EMQ[$SCITECOPATH]/session.tes
! Uncomment to enable default keyboard macros and function keys !
! EMQ[$SCITECOPATH]/fnkeys.tes !
+! Uncomment if XTerm allows clipboard operations !
+! 0,256ED !
+
! Uncomment to tweak the undo stack memory limit !
! 500*1024*1024,2EJ !
diff --git a/src/Makefile.am b/src/Makefile.am
index 6da8d84..515accd 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -34,6 +34,7 @@ libsciteco_base_la_SOURCES = main.cpp sciteco.h \
undo.cpp undo.h \
expressions.cpp expressions.h \
document.cpp document.h \
+ eol.cpp eol.h \
ioview.cpp ioview.h \
qregisters.cpp qregisters.h \
ring.cpp ring.h \
diff --git a/src/eol.cpp b/src/eol.cpp
new file mode 100644
index 0000000..3b42547
--- /dev/null
+++ b/src/eol.cpp
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2012-2016 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 "error.h"
+#include "eol.h"
+
+namespace SciTECO {
+
+/**
+ * Read data with automatic EOL translation.
+ *
+ * This gets the next data block from the converter
+ * implementation, performs EOL translation (if enabled)
+ * in a more or less efficient manner and returns
+ * a chunk of EOL-normalized data.
+ *
+ * Since the underlying data source may have to be
+ * queried repeatedly and because EOLReader avoids
+ * reassembling the EOL-normalized data by returning
+ * references into the modified data source, it is
+ * necessary to call this function repeatedly until
+ * it returns NULL.
+ *
+ * Errors reading the data source are propagated
+ * (as exceptions).
+ *
+ * @param data_len The length of the data chunk returned
+ * by this function. Set on return.
+ * @return A pointer to a chunk of EOL-normalized
+ * data of length data_len.
+ * It is NOT null-terminated.
+ * NULL is returned when all data has been converted.
+ */
+const gchar *
+EOLReader::convert(gsize &data_len)
+{
+ if (last_char < 0) {
+ /* a CRLF was last translated */
+ block_len++;
+ last_char = '\n';
+ }
+ offset += block_len;
+
+ if (offset == read_len) {
+ offset = 0;
+
+ /*
+ * NOTE: This throws in case of errors
+ */
+ if (!this->read(buffer, read_len)) {
+ /* EOF */
+ if (last_char == '\r') {
+ /*
+ * Very last character read is CR.
+ * If this is the only EOL so far, the
+ * EOL style is MAC.
+ * This is also executed if auto-eol is disabled
+ * but it doesn't hurt.
+ */
+ if (eol_style < 0)
+ eol_style = SC_EOL_CR;
+ else if (eol_style != SC_EOL_CR)
+ eol_style_inconsistent = TRUE;
+ }
+
+ return NULL;
+ }
+
+ if (!(Flags::ed & Flags::ED_AUTOEOL)) {
+ /*
+ * No EOL translation - always return entire
+ * buffer
+ */
+ data_len = block_len = read_len;
+ return buffer;
+ }
+ }
+
+ /*
+ * Return data with automatic EOL translation.
+ * Every EOL sequence is normalized to LF and
+ * the first sequence determines the documents
+ * EOL style.
+ * This loop is executed for every byte of the
+ * file/stream, so it was important to optimize
+ * it. Specifically, the number of returns
+ * is minimized by keeping a pointer to
+ * the beginning of a block of data in the buffer
+ * which already has LFs (offset).
+ * Mac EOLs can be converted to UNIX EOLs directly
+ * in the buffer.
+ * So if their EOLs are consistent, the function
+ * will return one block for the entire buffer.
+ * When reading a file with DOS EOLs, there will
+ * be one call per line which is significantly slower.
+ */
+ for (guint i = offset; i < read_len; i++) {
+ switch (buffer[i]) {
+ case '\n':
+ if (last_char == '\r') {
+ if (eol_style < 0)
+ eol_style = SC_EOL_CRLF;
+ else if (eol_style != SC_EOL_CRLF)
+ eol_style_inconsistent = TRUE;
+
+ /*
+ * Return block. CR has already
+ * been made LF in `buffer`.
+ */
+ data_len = block_len = i-offset;
+ /* next call will skip the CR */
+ last_char = -1;
+ return buffer + offset;
+ }
+
+ if (eol_style < 0)
+ eol_style = SC_EOL_LF;
+ else if (eol_style != SC_EOL_LF)
+ eol_style_inconsistent = TRUE;
+ /*
+ * No conversion necessary and no need to
+ * return block yet.
+ */
+ last_char = '\n';
+ break;
+
+ case '\r':
+ if (last_char == '\r') {
+ if (eol_style < 0)
+ eol_style = SC_EOL_CR;
+ else if (eol_style != SC_EOL_CR)
+ eol_style_inconsistent = TRUE;
+ }
+
+ /*
+ * Convert CR to LF in `buffer`.
+ * This way more than one line using
+ * Mac EOLs can be returned at once.
+ */
+ buffer[i] = '\n';
+ last_char = '\r';
+ break;
+
+ default:
+ if (last_char == '\r') {
+ if (eol_style < 0)
+ eol_style = SC_EOL_CR;
+ else if (eol_style != SC_EOL_CR)
+ eol_style_inconsistent = TRUE;
+ }
+ last_char = buffer[i];
+ break;
+ }
+ }
+
+ /*
+ * Return remaining block.
+ * With UNIX/MAC EOLs, this will usually be the
+ * entire `buffer`
+ */
+ data_len = block_len = read_len-offset;
+ return buffer + offset;
+}
+
+bool
+EOLReaderGIO::read(gchar *buffer, gsize &read_len)
+{
+ GError *error = NULL;
+
+ switch (g_io_channel_read_chars(channel, buffer,
+ sizeof(EOLReaderGIO::buffer),
+ &read_len, &error)) {
+ case G_IO_STATUS_ERROR:
+ throw GlibError(error);
+ case G_IO_STATUS_EOF:
+ return false;
+ case G_IO_STATUS_NORMAL:
+ case G_IO_STATUS_AGAIN:
+ break;
+ }
+
+ return true;
+}
+
+bool
+EOLReaderMem::read(gchar *buffer, gsize &read_len)
+{
+ read_len = buffer_len;
+ buffer_len = 0;
+ /*
+ * On the first call, returns true,
+ * later false (no more data).
+ */
+ return read_len != 0;
+}
+
+/*
+ * This could be in EOLReader as well, but this way, we
+ * make use of the buffer_len to avoid unnecessary allocations.
+ */
+gchar *
+EOLReaderMem::convert_all(gsize *out_len)
+{
+ GString *str = g_string_sized_new(buffer_len);
+ const gchar *data;
+ gsize data_len;
+
+ try {
+ while ((data = convert(data_len)))
+ g_string_append_len(str, data, data_len);
+ } catch (...) {
+ g_string_free(str, TRUE);
+ throw; /* forward */
+ }
+
+ if (out_len)
+ *out_len = str->len;
+ return g_string_free(str, FALSE);
+}
+
+/**
+ * Perform EOL-normalization on a buffer (if enabled) and
+ * pass it to the underlying data sink.
+ *
+ * This can be called repeatedly to transform a larger
+ * document - the buffer provided does not have to be
+ * well-formed with regard to EOL sequences.
+ *
+ * @param buffer The buffer to convert.
+ * @parem buffer_len The length of the data in buffer.
+ * @return The number of bytes written to the data sink,
+ * i.e. the size of the EOL-normalized data written.
+ */
+gsize
+EOLWriter::convert(const gchar *buffer, gsize buffer_len)
+{
+ gsize bytes_written;
+ guint i = 0;
+ guint block_start;
+ gsize block_written;
+
+ if (!(Flags::ed & Flags::ED_AUTOEOL))
+ /*
+ * Write without EOL-translation:
+ * `state` is not required
+ * NOTE: This throws in case of errors
+ */
+ return this->write(buffer, buffer_len);
+
+ /*
+ * Write to stream with EOL-translation.
+ * The document's EOL mode tells us what was guessed
+ * when its content was read in (presumably from a file)
+ * but might have been changed manually by the user.
+ * NOTE: This code assumes that the output stream is
+ * buffered, since otherwise it would be slower
+ * (has been benchmarked).
+ * NOTE: The loop is executed for every character
+ * in `buffer` and has been optimized for minimal
+ * function (i.e. GIOChannel) calls.
+ */
+ bytes_written = 0;
+ if (state == STATE_WRITE_LF) {
+ /* complete writing a CRLF sequence */
+ if (this->write("\n", 1) < 1)
+ return 0;
+ state = STATE_START;
+ bytes_written++;
+ i++;
+ }
+
+ block_start = i;
+ while (i < buffer_len) {
+ switch (buffer[i]) {
+ case '\n':
+ if (last_c == '\r') {
+ /* EOL sequence already written */
+ bytes_written++;
+ block_start = i+1;
+ break;
+ }
+ /* fall through */
+ case '\r':
+ block_written = this->write(buffer+block_start, i-block_start);
+ bytes_written += block_written;
+ if (block_written < i-block_start)
+ return bytes_written;
+
+ block_written = this->write(eol_seq, eol_seq_len);
+ if (block_written == 0)
+ return bytes_written;
+ if (block_written < eol_seq_len) {
+ /* incomplete EOL seq - we have written CR of CRLF */
+ state = STATE_WRITE_LF;
+ return bytes_written;
+ }
+ bytes_written++;
+
+ block_start = i+1;
+ break;
+ }
+
+ last_c = buffer[i++];
+ }
+
+ /*
+ * Write out remaining block (i.e. line)
+ */
+ bytes_written += this->write(buffer+block_start, buffer_len-block_start);
+ return bytes_written;
+}
+
+gsize
+EOLWriterGIO::write(const gchar *buffer, gsize buffer_len)
+{
+ gsize bytes_written;
+ GError *error = NULL;
+
+ switch (g_io_channel_write_chars(channel, buffer, buffer_len,
+ &bytes_written, &error)) {
+ case G_IO_STATUS_ERROR:
+ throw GlibError(error);
+ case G_IO_STATUS_EOF:
+ case G_IO_STATUS_NORMAL:
+ case G_IO_STATUS_AGAIN:
+ break;
+ }
+
+ return bytes_written;
+}
+
+gsize
+EOLWriterMem::write(const gchar *buffer, gsize buffer_len)
+{
+ g_string_append_len(str, buffer, buffer_len);
+ return buffer_len;
+}
+
+} /* namespace SciTECO */
diff --git a/src/eol.h b/src/eol.h
new file mode 100644
index 0000000..81ed4ef
--- /dev/null
+++ b/src/eol.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2012-2016 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/>.
+ */
+
+#ifndef __EOL_H
+#define __EOL_H
+
+#include <string.h>
+
+#include <glib.h>
+
+#include "sciteco.h"
+
+namespace SciTECO {
+
+class EOLReader {
+ gchar *buffer;
+ gsize read_len;
+ guint offset;
+ gsize block_len;
+ gint last_char;
+
+public:
+ gint eol_style;
+ gboolean eol_style_inconsistent;
+
+ EOLReader(gchar *_buffer)
+ : buffer(_buffer),
+ read_len(0), offset(0), block_len(0),
+ last_char(0), eol_style(-1),
+ eol_style_inconsistent(FALSE) {}
+ virtual ~EOLReader() {}
+
+ const gchar *convert(gsize &data_len);
+
+protected:
+ virtual bool read(gchar *buffer, gsize &read_len) = 0;
+};
+
+class EOLReaderGIO : public EOLReader {
+ gchar buffer[1024];
+ GIOChannel *channel;
+
+ bool read(gchar *buffer, gsize &read_len);
+
+public:
+ EOLReaderGIO(GIOChannel *_channel = NULL)
+ : EOLReader(buffer), channel(NULL)
+ {
+ set_channel(_channel);
+ }
+
+ inline void
+ set_channel(GIOChannel *_channel = NULL)
+ {
+ if (channel)
+ g_io_channel_unref(channel);
+ channel = _channel;
+ if (channel)
+ g_io_channel_ref(channel);
+ }
+
+ ~EOLReaderGIO()
+ {
+ set_channel();
+ }
+};
+
+class EOLReaderMem : public EOLReader {
+ gsize buffer_len;
+
+ bool read(gchar *buffer, gsize &read_len);
+
+public:
+ EOLReaderMem(gchar *buffer, gsize _buffer_len)
+ : EOLReader(buffer), buffer_len(_buffer_len) {}
+
+ gchar *convert_all(gsize *out_len = NULL);
+};
+
+class EOLWriter {
+ enum {
+ STATE_START = 0,
+ STATE_WRITE_LF
+ } state;
+ gchar last_c;
+ const gchar *eol_seq;
+ gsize eol_seq_len;
+
+public:
+ EOLWriter(gint eol_mode) : state(STATE_START), last_c('\0')
+ {
+ eol_seq = get_eol_seq(eol_mode);
+ eol_seq_len = strlen(eol_seq);
+ }
+ virtual ~EOLWriter() {}
+
+ gsize convert(const gchar *buffer, gsize buffer_len);
+
+protected:
+ virtual gsize write(const gchar *buffer, gsize buffer_len) = 0;
+};
+
+class EOLWriterGIO : public EOLWriter {
+ GIOChannel *channel;
+
+ gsize write(const gchar *buffer, gsize buffer_len);
+
+public:
+ EOLWriterGIO(gint eol_mode)
+ : EOLWriter(eol_mode), channel(NULL) {}
+
+ EOLWriterGIO(GIOChannel *_channel, gint eol_mode)
+ : EOLWriter(eol_mode), channel(NULL)
+ {
+ set_channel(_channel);
+ }
+
+ inline void
+ set_channel(GIOChannel *_channel = NULL)
+ {
+ if (channel)
+ g_io_channel_unref(channel);
+ channel = _channel;
+ if (channel)
+ g_io_channel_ref(channel);
+ }
+
+ ~EOLWriterGIO()
+ {
+ set_channel();
+ }
+};
+
+class EOLWriterMem : public EOLWriter {
+ GString *str;
+
+ gsize write(const gchar *buffer, gsize buffer_len);
+
+public:
+ EOLWriterMem(GString *_str, gint eol_mode)
+ : EOLWriter(eol_mode), str(_str) {}
+};
+
+} /* namespace SciTECO */
+
+#endif
diff --git a/src/error.h b/src/error.h
index f4dbfbf..62063ff 100644
--- a/src/error.h
+++ b/src/error.h
@@ -47,10 +47,10 @@ public:
};
class Error {
- gchar *description;
GSList *frames;
public:
+ gchar *description;
gint pos;
gint line, column;
diff --git a/src/interface-curses/interface-curses.cpp b/src/interface-curses/interface-curses.cpp
index fa1cc97..d592b89 100644
--- a/src/interface-curses/interface-curses.cpp
+++ b/src/interface-curses/interface-curses.cpp
@@ -54,6 +54,7 @@
#include "cmdline.h"
#include "qregisters.h"
#include "ring.h"
+#include "error.h"
#include "interface.h"
#include "interface-curses.h"
#include "curses-utils.h"
@@ -158,6 +159,8 @@ console_ctrl_handler(DWORD type)
} /* extern "C" */
+static gint xterm_version(void) G_GNUC_UNUSED;
+
#define UNNAMED_FILE "(Unnamed)"
/**
@@ -245,6 +248,46 @@ rgb2curses(guint32 rgb)
return COLOR_WHITE;
}
+static gint
+xterm_version(void)
+{
+ static gint xterm_patch = -2;
+
+ const gchar *term = g_getenv("TERM");
+ const gchar *xterm_version;
+
+ /*
+ * The XTerm patch level (version) is cached.
+ */
+ if (xterm_patch != -2)
+ return xterm_patch;
+ xterm_patch = -1;
+
+ if (!term || !g_str_has_prefix(term, "xterm"))
+ /* no XTerm */
+ return -1;
+
+ /*
+ * Terminal might claim to be XTerm-compatible,
+ * but this only refers to the terminfo database.
+ * XTERM_VERSION however should be sufficient to tell
+ * whether we are running under a real XTerm.
+ */
+ xterm_version = g_getenv("XTERM_VERSION");
+ if (!xterm_version)
+ /* no XTerm */
+ return -1;
+ xterm_patch = 0;
+
+ xterm_version = strrchr(xterm_version, '(');
+ if (!xterm_version)
+ /* Invalid XTERM_VERSION, assume some XTerm */
+ return 0;
+
+ xterm_patch = atoi(xterm_version+1);
+ return xterm_patch;
+}
+
void
ViewCurses::initialize_impl(void)
{
@@ -286,6 +329,14 @@ InterfaceCurses::main_impl(int &argc, char **&argv)
* even if info_update() is never called.
*/
info_current = g_strdup(PACKAGE_NAME);
+
+ /*
+ * On all platforms except NCurses/XTerm, it's
+ * safe to initialize the clipboards now.
+ */
+#ifndef NCURSES_UNIX
+ init_clipboard();
+#endif
}
void
@@ -341,7 +392,7 @@ InterfaceCurses::restore_colors(void)
* will return bogus "default" values and only for the first 8 colors.
* It would do more damage to restore the palette returned by
* color_content() than it helps.
- * xterm has the escape sequence "\e]104\x07" which restores
+ * xterm has the escape sequence "\e]104\a" which restores
* the palette from Xdefaults but not all terminal emulators
* claiming to be "xterm" via $TERM support this escape sequence.
* lxterminal for instance will print gibberish instead.
@@ -359,15 +410,14 @@ InterfaceCurses::restore_colors(void)
void
InterfaceCurses::restore_colors(void)
{
- if (g_str_has_prefix(g_getenv("TERM") ? : "", "xterm") &&
- g_getenv("XTERM_VERSION")) {
- /*
- * Looks like a real xterm. $TERM alone is not
- * sufficient to tell.
- */
- fputs("\e]104\x07", screen_tty);
- fflush(screen_tty);
- }
+ if (xterm_version() < 0)
+ return;
+
+ /*
+ * Looks like a real XTerm
+ */
+ fputs("\e]104\a", screen_tty);
+ fflush(screen_tty);
}
#else /* !PDCURSES_WIN32 && !NCURSES_UNIX */
@@ -537,8 +587,6 @@ InterfaceCurses::init_interactive(void)
/*
* Disable all magic function keys.
- * NOTE: This could also be used to assign
- * a "shutdown" key when program termination is requested.
*/
for (int i = 0; i < N_FUNCTION_KEYS; i++)
PDC_set_function_key(i, 0);
@@ -591,6 +639,16 @@ InterfaceCurses::init_interactive(void)
init_color_safe(i, (guint32)color_table[i]);
}
}
+
+ /*
+ * Only now (in interactive mode), it's safe to initialize
+ * the clipboard Q-Registers on ncurses with a compatible terminal
+ * emulator since clipboard operations will no longer interfer
+ * with stdout.
+ */
+#ifdef NCURSES_UNIX
+ init_clipboard();
+#endif
}
void
@@ -983,6 +1041,288 @@ InterfaceCurses::draw_cmdline(void)
0, disp_offset, 0, 1, 0, disp_len, FALSE);
}
+#ifdef __PDCURSES__
+
+/*
+ * At least on PDCurses, a single clipboard
+ * can be supported. We register it as the
+ * default clipboard ("~") as we do not know whether
+ * it corresponds to the X11 PRIMARY, SECONDARY or
+ * CLIPBOARD selections.
+ */
+void
+InterfaceCurses::init_clipboard(void)
+{
+ char *contents;
+ long length;
+ int rc;
+
+ /*
+ * Even on PDCurses, while the clipboard functions are
+ * available, the clipboard might not actually be supported.
+ * Since the existence of the QReg serves as an indication
+ * of clipboard support in SciTECO, we must first probe the
+ * usability of the clipboard.
+ * This could be done at compile time, but this way is more
+ * generic (albeit inefficient).
+ */
+ rc = PDC_getclipboard(&contents, &length);
+ if (rc == PDC_CLIP_ACCESS_ERROR)
+ return;
+ if (rc == PDC_CLIP_SUCCESS)
+ PDC_freeclipboard(contents);
+
+ QRegisters::globals.insert(new QRegisterClipboard());
+}
+
+void
+InterfaceCurses::set_clipboard(const gchar *name, const gchar *str, gssize str_len)
+{
+ int rc;
+
+ if (str) {
+ if (str_len < 0)
+ str_len = strlen(str);
+
+ rc = PDC_setclipboard(str, str_len);
+ } else {
+ rc = PDC_clearclipboard();
+ }
+
+ if (rc != PDC_CLIP_SUCCESS)
+ throw Error("Error %d copying to clipboard", rc);
+}
+
+gchar *
+InterfaceCurses::get_clipboard(const gchar *name, gsize *str_len)
+{
+ char *contents;
+ long length = 0;
+ int rc;
+ gchar *str;
+
+ /*
+ * NOTE: It is undefined whether we can pass in NULL for length.
+ */
+ rc = PDC_getclipboard(&contents, &length);
+ if (str_len)
+ *str_len = length;
+ if (rc == PDC_CLIP_EMPTY)
+ return NULL;
+ if (rc != PDC_CLIP_SUCCESS)
+ throw Error("Error %d retrieving clipboard", rc);
+
+ /*
+ * PDCurses defines its own free function and there is no
+ * way to find out which allocator was used.
+ * We must therefore copy the memory to be on the safe side.
+ * At least we can null-terminate the return string in the
+ * process (PDCurses does not guarantee that either).
+ */
+ str = g_malloc(length + 1);
+ memcpy(str, contents, length);
+ str[length] = '\0';
+
+ PDC_freeclipboard(contents);
+ return str;
+}
+
+#elif defined(NCURSES_UNIX)
+
+void
+InterfaceCurses::init_clipboard(void)
+{
+ /*
+ * At least on XTerm, there are escape sequences
+ * for modifying the clipboard (OSC-52).
+ * This is not standardized in terminfo, so we add special
+ * XTerm support here. Unfortunately, it is pretty hard to find out
+ * whether clipboard operations will actually work.
+ * XTerm must be at least at v203 and the corresponding window operations
+ * must be enabled.
+ * There is no way to find out if they are but we must
+ * not register the clipboard registers if they aren't.
+ * Therefore, a special XTerm clipboard ED flag an be set by the user.
+ */
+ if (!(Flags::ed & Flags::ED_XTERM_CLIPBOARD) || xterm_version() < 203)
+ return;
+
+ QRegisters::globals.insert(new QRegisterClipboard());
+ QRegisters::globals.insert(new QRegisterClipboard("P"));
+ QRegisters::globals.insert(new QRegisterClipboard("S"));
+ QRegisters::globals.insert(new QRegisterClipboard("C"));
+}
+
+static inline gchar
+get_selection_by_name(const gchar *name)
+{
+ /*
+ * Only the first letter of name is significant.
+ * We allow to address the XTerm cut buffers as well
+ * (everything gets passed down), but currently we
+ * only register the three standard registers
+ * "~", "~P", "~S" and "~C".
+ */
+ return g_ascii_tolower(*name) ? : 'c';
+}
+
+void
+InterfaceCurses::set_clipboard(const gchar *name, const gchar *str, gssize str_len)
+{
+ /*
+ * Enough space for 1024 Base64-encoded bytes.
+ */
+ gchar buffer[(1024 / 3) * 4 + 4];
+ gsize out_len;
+
+ /* g_base64_encode_step() state: */
+ gint state = 0;
+ gint save = 0;
+
+ fputs("\e]52;", screen_tty);
+ fputc(get_selection_by_name(name), screen_tty);
+ fputc(';', screen_tty);
+
+ if (!str)
+ str_len = 0;
+ else if (str_len < 0)
+ str_len = strlen(str);
+
+ while (str_len > 0) {
+ gsize step_len = MIN(1024, str_len);
+
+ /*
+ * This could be simplified using g_base64_encode().
+ * However, doing it step-wise avoids an allocation.
+ */
+ out_len = g_base64_encode_step((const guchar *)str,
+ step_len, FALSE,
+ buffer, &state, &save);
+ fwrite(buffer, 1, out_len, screen_tty);
+
+ str_len -= step_len;
+ str += step_len;
+ }
+
+ out_len = g_base64_encode_close(FALSE, buffer, &state, &save);
+ fwrite(buffer, 1, out_len, screen_tty);
+
+ fputc('\a', screen_tty);
+ fflush(screen_tty);
+}
+
+gchar *
+InterfaceCurses::get_clipboard(const gchar *name, gsize *str_len)
+{
+ /*
+ * Space for storing one group of decoded Base64 characters
+ * and the OSC-52 response.
+ */
+ gchar buffer[MAX(3, 7)];
+ GString *str_base64;
+
+ /* g_base64_decode_step() state: */
+ gint state = 0;
+ guint save = 0;
+
+ /*
+ * Query the clipboard -- XTerm will reply with the
+ * OSC-52 command that would set the current selection.
+ */
+ fputs("\e]52;", screen_tty);
+ fputc(get_selection_by_name(name), screen_tty);
+ fputs(";?\a", screen_tty);
+ fflush(screen_tty);
+
+ /*
+ * It is very well possible that the XTerm clipboard
+ * is not working because it is disabled, so we
+ * must be prepared for timeouts when reading.
+ * That's why we're using the Curses API here, instead
+ * of accessing screen_tty directly. It gives us a relatively
+ * simple way to read with timeouts.
+ * We restore all changed Curses settings before returning
+ * to be on the safe side.
+ */
+ halfdelay(1); /* 100ms timeout */
+ keypad(stdscr, FALSE);
+
+ /*
+ * Skip "\e]52;x;" (7 characters).
+ */
+ for (gint i = 0; i < 7; i++) {
+ if (getch() == ERR) {
+ /* timeout */
+ cbreak();
+ throw Error("Timed out reading XTerm clipboard");
+ }
+ }
+
+ str_base64 = g_string_new("");
+
+ for (;;) {
+ gchar c;
+ gsize out_len;
+
+ c = (gchar)getch();
+ if (c == ERR) {
+ /* timeout */
+ cbreak();
+ g_string_free(str_base64, TRUE);
+ throw Error("Timed out reading XTerm clipboard");
+ }
+ if (c == '\a')
+ break;
+
+ /*
+ * This could be simplified using sscanf() and
+ * g_base64_decode(), but we avoid one allocation
+ * to get the entire Base64 string.
+ * (Also to allow for timeouts, we must should
+ * read character-wise using getch() anyway.)
+ */
+ out_len = g_base64_decode_step(&c, sizeof(c),
+ (guchar *)buffer,
+ &state, &save);
+ g_string_append_len(str_base64, buffer, out_len);
+ }
+
+ cbreak();
+
+ if (str_len)
+ *str_len = str_base64->len;
+
+ /*
+ * If the clipboard answer is empty, return NULL.
+ */
+ return g_string_free(str_base64, str_base64->len == 0);
+}
+
+#else
+
+void
+InterfaceCurses::init_clipboard(void)
+{
+ /*
+ * No native clipboard support, so no clipboard Q-Regs are
+ * registered.
+ */
+}
+
+void
+InterfaceCurses::set_clipboard(const gchar *name, const gchar *str, gssize str_len)
+{
+ throw Error("Setting clipboard unsupported");
+}
+
+gchar *
+InterfaceCurses::get_clipboard(const gchar *name, gsize *str_len)
+{
+ throw Error("Getting clipboard unsupported");
+}
+
+#endif /* !__PDCURSES__ && !NCURSES_UNIX */
+
void
InterfaceCurses::popup_show_impl(void)
{
diff --git a/src/interface-curses/interface-curses.h b/src/interface-curses/interface-curses.h
index f29b1b4..d036d37 100644
--- a/src/interface-curses/interface-curses.h
+++ b/src/interface-curses/interface-curses.h
@@ -142,6 +142,12 @@ public:
/* implementation of Interface::cmdline_update() */
void cmdline_update_impl(const Cmdline *cmdline);
+ /* override of Interface::set_clipboard() */
+ void set_clipboard(const gchar *name,
+ const gchar *str = NULL, gssize str_len = -1);
+ /* override of Interface::get_clipboard() */
+ gchar *get_clipboard(const gchar *name, gsize *str_len = NULL);
+
/* implementation of Interface::popup_add() */
inline void
popup_add_impl(PopupEntryType type,
@@ -177,6 +183,8 @@ private:
void init_interactive(void);
void restore_batch(void);
+ void init_clipboard(void);
+
void resize_all_windows(void);
void set_window_title(const gchar *title);
diff --git a/src/interface-gtk/interface-gtk.cpp b/src/interface-gtk/interface-gtk.cpp
index 49c3154..4ef0b38 100644
--- a/src/interface-gtk/interface-gtk.cpp
+++ b/src/interface-gtk/interface-gtk.cpp
@@ -211,6 +211,17 @@ InterfaceGtk::main_impl(int &argc, char **&argv)
gtk_init(&argc, &argv);
/*
+ * Register clipboard registers.
+ * Unfortunately, we cannot find out which
+ * clipboards/selections are supported on this system,
+ * so we register only some default ones.
+ */
+ QRegisters::globals.insert(new QRegisterClipboard());
+ QRegisters::globals.insert(new QRegisterClipboard("P"));
+ QRegisters::globals.insert(new QRegisterClipboard("S"));
+ QRegisters::globals.insert(new QRegisterClipboard("C"));
+
+ /*
* The event queue is initialized now, so we can
* pass it as user data to C-linkage callbacks.
*/
@@ -381,7 +392,7 @@ void
InterfaceGtk::refresh_info(void)
{
GtkStyleContext *style = gtk_widget_get_style_context(info_bar_widget);
- const gchar *info_type_str;
+ const gchar *info_type_str = PACKAGE;
gchar *info_current_temp = g_strdup(info_current);
gchar *info_current_canon;
GIcon *icon;
@@ -523,6 +534,67 @@ InterfaceGtk::cmdline_update_impl(const Cmdline *cmdline)
gdk_threads_leave();
}
+static GdkAtom
+get_selection_by_name(const gchar *name)
+{
+ /*
+ * We can use gdk_atom_intern() to support arbitrary X11 selection
+ * names. However, since we cannot find out which selections are
+ * registered, we are only providing QRegisters for the three default
+ * selections.
+ * Checking them here avoids expensive X server roundtrips.
+ */
+ switch (*name) {
+ case '\0': return GDK_NONE;
+ case 'P': return GDK_SELECTION_PRIMARY;
+ case 'S': return GDK_SELECTION_SECONDARY;
+ case 'C': return GDK_SELECTION_CLIPBOARD;
+ default: break;
+ }
+
+ return gdk_atom_intern(name, FALSE);
+}
+
+void
+InterfaceGtk::set_clipboard(const gchar *name, const gchar *str, gssize str_len)
+{
+ GtkClipboard *clipboard;
+
+ gdk_threads_enter();
+
+ clipboard = gtk_clipboard_get(get_selection_by_name(name));
+
+ /*
+ * NOTE: function has compatible semantics for str_len < 0.
+ */
+ gtk_clipboard_set_text(clipboard, str, str_len);
+
+ gdk_threads_leave();
+}
+
+gchar *
+InterfaceGtk::get_clipboard(const gchar *name, gsize *str_len)
+{
+ GtkClipboard *clipboard;
+ gchar *str;
+
+ gdk_threads_enter();
+
+ clipboard = gtk_clipboard_get(get_selection_by_name(name));
+ /*
+ * Could return NULL for an empty clipboard.
+ * NOTE: This converts to UTF8 and we loose the ability
+ * to get clipboard with embedded nulls.
+ */
+ str = gtk_clipboard_wait_for_text(clipboard);
+
+ gdk_threads_leave();
+
+ if (str_len)
+ *str_len = str ? strlen(str) : 0;
+ return str;
+}
+
void
InterfaceGtk::popup_add_impl(PopupEntryType type,
const gchar *name, bool highlight)
diff --git a/src/interface-gtk/interface-gtk.h b/src/interface-gtk/interface-gtk.h
index 80692f9..0145ff4 100644
--- a/src/interface-gtk/interface-gtk.h
+++ b/src/interface-gtk/interface-gtk.h
@@ -131,6 +131,12 @@ public:
/* implementation of Interface::cmdline_update() */
void cmdline_update_impl(const Cmdline *cmdline);
+ /* override of Interface::set_clipboard() */
+ void set_clipboard(const gchar *name,
+ const gchar *str = NULL, gssize str_len = -1);
+ /* override of Interface::get_clipboard() */
+ gchar *get_clipboard(const gchar *name, gsize *str_len = NULL);
+
/* implementation of Interface::popup_add() */
void popup_add_impl(PopupEntryType type,
const gchar *name, bool highlight = false);
diff --git a/src/interface.h b/src/interface.h
index 84182ea..98254ac 100644
--- a/src/interface.h
+++ b/src/interface.h
@@ -26,6 +26,7 @@
#include <Scintilla.h>
#include "undo.h"
+#include "error.h"
namespace SciTECO {
@@ -287,6 +288,21 @@ public:
impl().cmdline_update_impl(cmdline);
}
+ /* default implementation */
+ inline void
+ set_clipboard(const gchar *name,
+ const gchar *str = NULL, gssize str_len = -1)
+ {
+ throw Error("Setting clipboard unsupported");
+ }
+
+ /* default implementation */
+ inline gchar *
+ get_clipboard(const gchar *name, gsize *str_len = NULL)
+ {
+ throw Error("Getting clipboard unsupported");
+ }
+
enum PopupEntryType {
POPUP_PLAIN,
POPUP_FILE,
diff --git a/src/ioview.cpp b/src/ioview.cpp
index 1643a29..a5685fe 100644
--- a/src/ioview.cpp
+++ b/src/ioview.cpp
@@ -37,6 +37,7 @@
#include "undo.h"
#include "error.h"
#include "qregisters.h"
+#include "eol.h"
#include "ioview.h"
#ifdef HAVE_WINDOWS_H
@@ -86,173 +87,6 @@ set_file_attributes(const gchar *filename, FileAttributes attrs)
#endif /* !G_OS_WIN32 */
/**
- * A wrapper around g_io_channel_read_chars() that also
- * performs automatic EOL translation (if enabled) in a
- * more or less efficient manner.
- * Unlike g_io_channel_read_chars(), this returns an
- * offset and length into the buffer with normalized
- * EOL character.
- * The function must therefore be called iteratively on
- * on the same buffer while it returns G_IO_STATUS_NORMAL.
- *
- * @param channel The GIOChannel to read from.
- ' @param buffer Used to store blocks.
- * @param buffer_len Size of buffer.
- * @param read_len Total number of bytes read into buffer.
- * Must be provided over the lifetime of buffer
- * and initialized with 0.
- * @param offset If a block could be read (G_IO_STATUS_NORMAL),
- * this will be set to indicate its beginning in
- * buffer. Should be initialized to 0.
- * @param block_len Will be set to the block length.
- * Should be initialized to 0.
- * @param state Opaque state that must persist for the lifetime
- * of the channel. Must be initialized with 0.
- * @param eol_style Will be set to the EOL style guessed from
- * the data in channel (if the data allows it).
- * Should be initialized with -1 (unknown).
- * @param eol_style_inconsistent Will be set to TRUE if
- * inconsistent EOL styles are detected.
- * @param error If not NULL and an error occurred, it is set to
- * the error. It should be initialized to -1.
- * @return A GIOStatus as returned by g_io_channel_read_chars()
- */
-GIOStatus
-IOView::channel_read_with_eol(GIOChannel *channel,
- gchar *buffer, gsize buffer_len,
- gsize &read_len,
- guint &offset, gsize &block_len,
- gint &state, gint &eol_style,
- gboolean &eol_style_inconsistent,
- GError **error)
-{
- GIOStatus status;
-
- if (state < 0) {
- /* a CRLF was last translated */
- block_len++;
- state = '\n';
- }
- offset += block_len;
-
- if (offset == read_len) {
- offset = 0;
-
- status = g_io_channel_read_chars(channel, buffer, buffer_len,
- &read_len, error);
- if (status == G_IO_STATUS_EOF && state == '\r') {
- /*
- * Very last character read is CR.
- * If this is the only EOL so far, the
- * EOL style is MAC.
- * This is also executed if auto-eol is disabled
- * but it doesn't hurt.
- */
- if (eol_style < 0)
- eol_style = SC_EOL_CR;
- else if (eol_style != SC_EOL_CR)
- eol_style_inconsistent = TRUE;
- }
- if (status != G_IO_STATUS_NORMAL)
- return status;
-
- if (!(Flags::ed & Flags::ED_AUTOEOL)) {
- /*
- * No EOL translation - always return entire
- * buffer
- */
- block_len = read_len;
- return G_IO_STATUS_NORMAL;
- }
- }
-
- /*
- * Return data with automatic EOL translation.
- * Every EOL sequence is normalized to LF and
- * the first sequence determines the documents
- * EOL style.
- * This loop is executed for every byte of the
- * file/stream, so it was important to optimize
- * it. Specifically, the number of returns
- * is minimized by keeping a pointer to
- * the beginning of a block of data in the buffer
- * which already has LFs (offset).
- * Mac EOLs can be converted to UNIX EOLs directly
- * in the buffer.
- * So if their EOLs are consistent, the function
- * will return one block for the entire buffer.
- * When reading a file with DOS EOLs, there will
- * be one call per line which is significantly slower.
- */
- for (guint i = offset; i < read_len; i++) {
- switch (buffer[i]) {
- case '\n':
- if (state == '\r') {
- if (eol_style < 0)
- eol_style = SC_EOL_CRLF;
- else if (eol_style != SC_EOL_CRLF)
- eol_style_inconsistent = TRUE;
-
- /*
- * Return block. CR has already
- * been made LF in `buffer`.
- */
- block_len = i-offset;
- /* next call will skip the CR */
- state = -1;
- return G_IO_STATUS_NORMAL;
- }
-
- if (eol_style < 0)
- eol_style = SC_EOL_LF;
- else if (eol_style != SC_EOL_LF)
- eol_style_inconsistent = TRUE;
- /*
- * No conversion necessary and no need to
- * return block yet.
- */
- state = '\n';
- break;
-
- case '\r':
- if (state == '\r') {
- if (eol_style < 0)
- eol_style = SC_EOL_CR;
- else if (eol_style != SC_EOL_CR)
- eol_style_inconsistent = TRUE;
- }
-
- /*
- * Convert CR to LF in `buffer`.
- * This way more than one line using
- * Mac EOLs can be returned at once.
- */
- buffer[i] = '\n';
- state = '\r';
- break;
-
- default:
- if (state == '\r') {
- if (eol_style < 0)
- eol_style = SC_EOL_CR;
- else if (eol_style != SC_EOL_CR)
- eol_style_inconsistent = TRUE;
- }
- state = buffer[i];
- break;
- }
- }
-
- /*
- * Return remaining block.
- * With UNIX/MAC EOLs, this will usually be the
- * entire `buffer`
- */
- block_len = read_len-offset;
- return G_IO_STATUS_NORMAL;
-}
-
-/**
* Loads the view's document by reading all data from
* a GIOChannel.
* The EOL style is guessed from the channel's data
@@ -261,23 +95,17 @@ IOView::channel_read_with_eol(GIOChannel *channel,
* Also it tries to guess the size of the file behind
* channel in order to preallocate memory in Scintilla.
*
+ * Any error reading the GIOChannel is propagated as
+ * an exception.
+ *
* @param channel Channel to read from.
- * @param error Glib error or NULL.
- * @returns A GIOStatus as returned by g_io_channel_read_chars()
*/
-GIOStatus
-IOView::load(GIOChannel *channel, GError **error)
+void
+IOView::load(GIOChannel *channel)
{
- GIOStatus status;
GStatBuf stat_buf;
- gchar buffer[1024];
- gsize read_len = 0;
- guint offset = 0;
- gsize block_len = 0;
- gint state = 0; /* opaque state */
- gint eol_style = -1; /* yet unknown */
- gboolean eol_style_inconsistent = FALSE;
+ EOLReaderGIO reader(channel);
ssm(SCI_BEGINUNDOACTION);
ssm(SCI_CLEARALL);
@@ -294,17 +122,15 @@ IOView::load(GIOChannel *channel, GError **error)
stat_buf.st_size > 0)
ssm(SCI_ALLOCATE, stat_buf.st_size);
- for (;;) {
- status = channel_read_with_eol(
- channel, buffer, sizeof(buffer),
- read_len, offset, block_len, state,
- eol_style, eol_style_inconsistent,
- error
- );
- if (status != G_IO_STATUS_NORMAL)
- break;
-
- ssm(SCI_APPENDTEXT, block_len, (sptr_t)(buffer+offset));
+ try {
+ const gchar *data;
+ gsize data_len;
+
+ while ((data = reader.convert(data_len)))
+ ssm(SCI_APPENDTEXT, data_len, (sptr_t)data);
+ } catch (...) {
+ ssm(SCI_ENDUNDOACTION);
+ throw; /* forward */
}
/*
@@ -317,15 +143,14 @@ IOView::load(GIOChannel *channel, GError **error)
* If it is enabled but the stream does not contain any
* EOL characters, the platform default is still assumed.
*/
- if (eol_style >= 0)
- ssm(SCI_SETEOLMODE, eol_style);
+ if (reader.eol_style >= 0)
+ ssm(SCI_SETEOLMODE, reader.eol_style);
- if (eol_style_inconsistent)
+ if (reader.eol_style_inconsistent)
interface.msg(InterfaceCurrent::MSG_WARNING,
"Inconsistent EOL styles normalized");
ssm(SCI_ENDUNDOACTION);
- return status;
}
/**
@@ -336,7 +161,6 @@ IOView::load(const gchar *filename)
{
GError *error = NULL;
GIOChannel *channel;
- GIOStatus status;
channel = g_io_channel_new_file(filename, "r", &error);
if (!channel) {
@@ -354,15 +178,17 @@ IOView::load(const gchar *filename)
g_io_channel_set_encoding(channel, NULL, NULL);
g_io_channel_set_buffered(channel, FALSE);
- status = load(channel, &error);
- /* also closes file: */
- g_io_channel_unref(channel);
- if (status == G_IO_STATUS_ERROR) {
+ try {
+ load(channel);
+ } catch (Error &e) {
Error err("Error reading file \"%s\": %s",
- filename, error->message);
- g_error_free(error);
+ filename, e.description);
+ g_io_channel_unref(channel);
throw err;
}
+
+ /* also closes file: */
+ g_io_channel_unref(channel);
}
#if 0
@@ -471,132 +297,30 @@ make_savepoint(const gchar *filename)
#endif
-GIOStatus
-IOView::save(GIOChannel *channel, guint position, gsize len,
- gsize *bytes_written, gint &state, GError **error)
-{
- const gchar *buffer;
- const gchar *eol_seq;
- gchar last_c;
- guint i = 0;
- guint block_start;
- gsize block_written;
-
- GIOStatus status;
-
- enum {
- SAVE_STATE_START = 0,
- SAVE_STATE_WRITE_LF
- };
-
- buffer = (const gchar *)ssm(SCI_GETRANGEPOINTER,
- position, (sptr_t)len);
-
- if (!(Flags::ed & Flags::ED_AUTOEOL))
- /*
- * Write without EOL-translation:
- * `state` is not required
- */
- return g_io_channel_write_chars(channel, buffer, len,
- bytes_written, error);
-
- /*
- * Write to stream with EOL-translation.
- * The document's EOL mode tells us what was guessed
- * when its content was read in (presumably from a file)
- * but might have been changed manually by the user.
- * NOTE: This code assumes that the output stream is
- * buffered, since otherwise it would be slower
- * (has been benchmarked).
- * NOTE: The loop is executed for every character
- * in `buffer` and has been optimized for minimal
- * function (i.e. GIOChannel) calls.
- */
- *bytes_written = 0;
- if (state == SAVE_STATE_WRITE_LF) {
- /* complete writing a CRLF sequence */
- status = g_io_channel_write_chars(channel, "\n", 1, NULL, error);
- if (status != G_IO_STATUS_NORMAL)
- return status;
- state = SAVE_STATE_START;
- (*bytes_written)++;
- i++;
- }
-
- eol_seq = get_eol_seq(ssm(SCI_GETEOLMODE));
- last_c = ssm(SCI_GETCHARAT, position-1);
-
- block_start = i;
- while (i < len) {
- switch (buffer[i]) {
- case '\n':
- if (last_c == '\r') {
- /* EOL sequence already written */
- (*bytes_written)++;
- block_start = i+1;
- break;
- }
- /* fall through */
- case '\r':
- status = g_io_channel_write_chars(channel, buffer+block_start,
- i-block_start, &block_written, error);
- *bytes_written += block_written;
- if (status != G_IO_STATUS_NORMAL ||
- block_written < i-block_start)
- return status;
-
- status = g_io_channel_write_chars(channel, eol_seq,
- -1, &block_written, error);
- if (status != G_IO_STATUS_NORMAL)
- return status;
- if (eol_seq[block_written]) {
- /* incomplete EOL seq - we have written CR of CRLF */
- state = SAVE_STATE_WRITE_LF;
- return G_IO_STATUS_NORMAL;
- }
- (*bytes_written)++;
-
- block_start = i+1;
- break;
- }
-
- last_c = buffer[i++];
- }
-
- /*
- * Write out remaining block (i.e. line)
- */
- status = g_io_channel_write_chars(channel, buffer+block_start,
- len-block_start, &block_written, error);
- *bytes_written += block_written;
- return status;
-}
-
-gboolean
-IOView::save(GIOChannel *channel, GError **error)
+void
+IOView::save(GIOChannel *channel)
{
+ EOLWriterGIO writer(channel, ssm(SCI_GETEOLMODE));
sptr_t gap;
gsize size;
+ const gchar *buffer;
gsize bytes_written;
- gint state = 0;
/* write part of buffer before gap */
gap = ssm(SCI_GETGAPPOSITION);
if (gap > 0) {
- if (save(channel, 0, gap, &bytes_written, state, error) == G_IO_STATUS_ERROR)
- return FALSE;
+ buffer = (const gchar *)ssm(SCI_GETRANGEPOINTER, 0, gap);
+ bytes_written = writer.convert(buffer, gap);
g_assert(bytes_written == (gsize)gap);
}
/* write part of buffer after gap */
size = ssm(SCI_GETLENGTH) - gap;
if (size > 0) {
- if (save(channel, gap, size, &bytes_written, state, error) == G_IO_STATUS_ERROR)
- return FALSE;
+ buffer = (const gchar *)ssm(SCI_GETRANGEPOINTER, gap, (sptr_t)size);
+ bytes_written = writer.convert(buffer, size);
g_assert(bytes_written == size);
}
-
- return TRUE;
}
void
@@ -636,9 +360,10 @@ IOView::save(const gchar *filename)
g_io_channel_set_encoding(channel, NULL, NULL);
g_io_channel_set_buffered(channel, TRUE);
- if (!save(channel, &error)) {
- Error err("Error writing file \"%s\": %s", filename, error->message);
- g_error_free(error);
+ try {
+ save(channel);
+ } catch (Error &e) {
+ Error err("Error writing file \"%s\": %s", filename, e.description);
g_io_channel_unref(channel);
throw err;
}
diff --git a/src/ioview.h b/src/ioview.h
index 83517b5..18a3be6 100644
--- a/src/ioview.h
+++ b/src/ioview.h
@@ -124,20 +124,10 @@ class IOView : public ViewCurrent {
};
public:
- static GIOStatus channel_read_with_eol(GIOChannel *channel,
- gchar *buffer, gsize buffer_len,
- gsize &read_len,
- guint &offset, gsize &block_len,
- gint &state, gint &eol_style,
- gboolean &eol_style_inconsistent,
- GError **error = NULL);
-
- GIOStatus load(GIOChannel *channel, GError **error = NULL);
+ void load(GIOChannel *channel);
void load(const gchar *filename);
- GIOStatus save(GIOChannel *channel, guint position, gsize len,
- gsize *bytes_written, gint &state, GError **error = NULL);
- gboolean save(GIOChannel *channel, GError **error = NULL);
+ void save(GIOChannel *channel);
void save(const gchar *filename);
};
diff --git a/src/parser.cpp b/src/parser.cpp
index 941db49..a9e9213 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -2228,6 +2228,10 @@ StateECommand::custom(gchar chr)
* - 128: Enable/Disable enforcement of UNIX98
* \(lq/bin/sh\(rq emulation for operating system command
* executions
+ * - 256: Enable/Disable \fBxterm\fP(1) clipboard support.
+ * Should only be enabled if XTerm allows the
+ * \fIGetSelection\fP and \fISetSelection\fP window
+ * operations.
*
* The features controlled thus are discribed in other sections
* of this manual.
diff --git a/src/qregisters.cpp b/src/qregisters.cpp
index 195521c..b6fa472 100644
--- a/src/qregisters.cpp
+++ b/src/qregisters.cpp
@@ -37,6 +37,7 @@
#include "document.h"
#include "ring.h"
#include "ioview.h"
+#include "eol.h"
#include "error.h"
#include "qregisters.h"
@@ -509,6 +510,192 @@ QRegisterWorkingDir::undo_exchange_string(QRegisterData &reg)
reg.undo_set_string();
}
+void
+QRegisterClipboard::UndoTokenSetClipboard::run(void)
+{
+ interface.set_clipboard(name, str, str_len);
+}
+
+void
+QRegisterClipboard::set_string(const gchar *str, gsize len)
+{
+ if (Flags::ed & Flags::ED_AUTOEOL) {
+ GString *str_converted = g_string_sized_new(len);
+ /*
+ * This will convert to the Q-Register view's EOL mode.
+ */
+ EOLWriterMem writer(str_converted,
+ QRegisters::view.ssm(SCI_GETEOLMODE));
+ gsize bytes_written;
+
+ /*
+ * NOTE: Shouldn't throw any error, ever.
+ */
+ bytes_written = writer.convert(str, len);
+ g_assert(bytes_written == len);
+
+ interface.set_clipboard(get_clipboard_name(),
+ str_converted->str, str_converted->len);
+
+ g_string_free(str_converted, TRUE);
+ } else {
+ /*
+ * No EOL conversion necessary. The EOLWriter can handle
+ * this as well, but will result in unnecessary allocations.
+ */
+ interface.set_clipboard(get_clipboard_name(), str, len);
+ }
+}
+
+void
+QRegisterClipboard::undo_set_string(void)
+{
+ gchar *str;
+ gsize str_len;
+
+ /*
+ * Upon rubout, the current contents of the clipboard are
+ * restored.
+ * We are checking for undo.enabled instead of relying on
+ * undo.push_own(), since getting the clipboard
+ * is an expensive operation that we want to avoid.
+ */
+ if (!undo.enabled)
+ return;
+
+ /*
+ * Ownership of str (may be NULL) is passed to
+ * the undo token. We do not need undo.push_own() since
+ * we checked for undo.enabled before.
+ * This avoids any EOL translation as that would be cumbersome
+ * and could also modify the clipboard in unexpected ways.
+ */
+ str = interface.get_clipboard(get_clipboard_name(), &str_len);
+ undo.push<UndoTokenSetClipboard>(get_clipboard_name(), str, str_len);
+}
+
+gchar *
+QRegisterClipboard::get_string(gsize *out_len)
+{
+ if (!(Flags::ed & Flags::ED_AUTOEOL)) {
+ /*
+ * No auto-eol conversion - avoid unnecessary copying
+ * and allocations.
+ * NOTE: get_clipboard() already returns a null-terminated string.
+ */
+ return interface.get_clipboard(get_clipboard_name(), out_len);
+ }
+
+ gsize str_len;
+ gchar *str = interface.get_clipboard(get_clipboard_name(), &str_len);
+ EOLReaderMem reader(str, str_len);
+ gchar *str_converted;
+
+ try {
+ str_converted = reader.convert_all(out_len);
+ } catch (...) {
+ g_free(str);
+ throw; /* forward */
+ }
+ g_free(str);
+
+ return str_converted;
+}
+
+gchar *
+QRegisterClipboard::get_string(void)
+{
+ return QRegisterClipboard::get_string(NULL);
+}
+
+gsize
+QRegisterClipboard::get_string_size(void)
+{
+ gsize str_len;
+ gchar *str = interface.get_clipboard(get_clipboard_name(), &str_len);
+ /*
+ * Using the EOLReader does not hurt much if AutoEOL is disabled
+ * since we use it only for counting the bytes.
+ */
+ EOLReaderMem reader(str, str_len);
+ gsize data_len;
+ gsize converted_len = 0;
+
+ try {
+ while (reader.convert(data_len))
+ converted_len += data_len;
+ } catch (...) {
+ g_free(str);
+ throw; /* forward */
+ }
+ g_free(str);
+
+ return converted_len;
+}
+
+gint
+QRegisterClipboard::get_character(gint position)
+{
+ gsize str_len;
+ gchar *str = QRegisterClipboard::get_string(&str_len);
+ gint ret = -1;
+
+ /*
+ * `str` may be NULL, but only if str_len == 0 as well.
+ */
+ if (position >= 0 &&
+ position < (gint)str_len)
+ ret = str[position];
+
+ g_free(str);
+ return ret;
+}
+
+void
+QRegisterClipboard::edit(void)
+{
+ gchar *str;
+ gsize str_len;
+
+ QRegister::edit();
+
+ QRegisters::view.ssm(SCI_BEGINUNDOACTION);
+ QRegisters::view.ssm(SCI_CLEARALL);
+ str = QRegisterClipboard::get_string(&str_len);
+ QRegisters::view.ssm(SCI_APPENDTEXT, str_len, (sptr_t)str);
+ g_free(str);
+ QRegisters::view.ssm(SCI_ENDUNDOACTION);
+
+ QRegisters::view.undo_ssm(SCI_UNDO);
+}
+
+void
+QRegisterClipboard::exchange_string(QRegisterData &reg)
+{
+ gchar *own_str;
+ gsize own_str_len;
+ gchar *other_str = reg.get_string();
+ gsize other_str_len = reg.get_string_size();
+
+ /*
+ * FIXME: What if `reg` is a clipboard and it changes
+ * between the two calls?
+ * QRegister::get_string() should always return the length as well.
+ */
+ QRegisterData::set_string(other_str, other_str_len);
+ g_free(other_str);
+ own_str = QRegisterClipboard::get_string(&own_str_len);
+ reg.set_string(own_str, own_str_len);
+ g_free(own_str);
+}
+
+void
+QRegisterClipboard::undo_exchange_string(QRegisterData &reg)
+{
+ QRegisterClipboard::undo_set_string();
+ reg.undo_set_string();
+}
+
QRegisterTable::QRegisterTable(bool _undo) : RBTree(), must_undo(_undo)
{
/* general purpose registers */
diff --git a/src/qregisters.h b/src/qregisters.h
index 563a774..22fa93d 100644
--- a/src/qregisters.h
+++ b/src/qregisters.h
@@ -135,11 +135,18 @@ public:
};
class QRegister : public RBTree::RBEntry, public QRegisterData {
+protected:
+ /**
+ * The default constructor for subclasses.
+ * This leaves the name uninitialized.
+ */
+ QRegister(void) : name(NULL) {}
+
public:
gchar *name;
QRegister(const gchar *_name)
- : QRegisterData(), name(g_strdup(_name)) {}
+ : name(g_strdup(_name)) {}
virtual
~QRegister()
{
@@ -223,6 +230,76 @@ public:
void undo_exchange_string(QRegisterData &reg);
};
+class QRegisterClipboard : public QRegister {
+ class UndoTokenSetClipboard : public UndoToken {
+ gchar *name;
+ gchar *str;
+ gsize str_len;
+
+ public:
+ /**
+ * Construct undo token.
+ *
+ * This passes ownership of the clipboard content string
+ * to the undo token object.
+ */
+ UndoTokenSetClipboard(const gchar *_name, gchar *_str, gsize _str_len)
+ : name(g_strdup(_name)), str(_str), str_len(_str_len) {}
+ ~UndoTokenSetClipboard()
+ {
+ g_free(str);
+ g_free(name);
+ }
+
+ void run(void);
+
+ gsize
+ get_size(void) const
+ {
+ return sizeof(*this) + strlen(name) + str_len;
+ }
+ };
+
+ /**
+ * Gets the clipboard name.
+ * Can be easily derived from the Q-Register name.
+ */
+ inline const gchar *
+ get_clipboard_name(void) const
+ {
+ return name+1;
+ }
+
+public:
+ QRegisterClipboard(const gchar *_name = NULL)
+ {
+ name = g_strconcat("~", _name, NIL);
+ }
+
+ void set_string(const gchar *str, gsize len);
+ void undo_set_string(void);
+
+ /*
+ * FIXME: We could support that.
+ */
+ void
+ append_string(const gchar *str, gsize len)
+ {
+ throw QRegOpUnsupportedError(name);
+ }
+ void undo_append_string(void) {}
+
+ gchar *get_string(gsize *out_len);
+ gchar *get_string(void);
+ gsize get_string_size(void);
+ gint get_character(gint pos);
+
+ void edit(void);
+
+ void exchange_string(QRegisterData &reg);
+ void undo_exchange_string(QRegisterData &reg);
+};
+
class QRegisterTable : private RBTree {
class UndoTokenRemove : public UndoTokenWithSize<UndoTokenRemove> {
QRegisterTable *table;
diff --git a/src/sciteco.h b/src/sciteco.h
index 1163020..b7c62f2 100644
--- a/src/sciteco.h
+++ b/src/sciteco.h
@@ -39,10 +39,11 @@ typedef tecoInt tecoBool;
namespace Flags {
enum {
- ED_AUTOEOL = (1 << 4),
- ED_HOOKS = (1 << 5),
- ED_FNKEYS = (1 << 6),
- ED_SHELLEMU = (1 << 7)
+ ED_AUTOEOL = (1 << 4),
+ ED_HOOKS = (1 << 5),
+ ED_FNKEYS = (1 << 6),
+ ED_SHELLEMU = (1 << 7),
+ ED_XTERM_CLIPBOARD = (1 << 8)
};
extern tecoInt ed;
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 0722180..ee35a03 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -26,7 +26,7 @@
#include "undo.h"
#include "expressions.h"
#include "qregisters.h"
-#include "ioview.h"
+#include "eol.h"
#include "ring.h"
#include "parser.h"
#include "error.h"
@@ -262,6 +262,8 @@ StateExecuteCommand::~StateExecuteCommand()
*/
g_main_context_unref(ctx.mainctx);
#endif
+
+ delete ctx.error;
}
void
@@ -341,6 +343,7 @@ StateExecuteCommand::done(const gchar *str)
*/
return &States::start;
+ GError *error = NULL;
gchar **argv, **envp;
static const gint flags = G_SPAWN_DO_NOT_REAP_CHILD |
G_SPAWN_SEARCH_PATH |
@@ -350,16 +353,29 @@ StateExecuteCommand::done(const gchar *str)
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;
- /* opaque state for IOView::save() */
- ctx.stdin_state = 0;
- /* opaque state for IOView::channel_read_with_eol() */
- ctx.stdout_state = 0;
- /* eol style guessed from the stdout stream */
- ctx.eol_style = -1;
+
+ 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, &ctx.error);
+ argv = parse_shell_command_line(str, &error);
if (!argv)
goto gerror;
@@ -368,12 +384,12 @@ StateExecuteCommand::done(const gchar *str)
g_spawn_async_with_pipes(NULL, argv, envp, (GSpawnFlags)flags,
NULL, NULL, &pid,
&stdin_fd, &stdout_fd, NULL,
- &ctx.error);
+ &error);
g_strfreev(envp);
g_strfreev(argv);
- if (ctx.error)
+ if (error)
goto gerror;
ctx.child_src = g_child_watch_source_new(pid);
@@ -390,14 +406,17 @@ StateExecuteCommand::done(const gchar *str)
#endif
g_io_channel_set_flags(stdin_chan, G_IO_FLAG_NONBLOCK, NULL);
g_io_channel_set_encoding(stdin_chan, NULL, NULL);
- g_io_channel_set_buffered(stdin_chan, FALSE);
- g_io_channel_set_flags(stdout_chan, G_IO_FLAG_NONBLOCK, NULL);
- g_io_channel_set_encoding(stdout_chan, NULL, NULL);
/*
- * IOView::save() expects the channel to be buffered
+ * EOLWriterGIO expects the channel to be buffered
* for performance reasons
*/
- g_io_channel_set_buffered(stdout_chan, TRUE);
+ 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));
@@ -425,9 +444,9 @@ StateExecuteCommand::done(const gchar *str)
interface.ssm(SCI_ENDUNDOACTION);
if (register_argument) {
- if (ctx.eol_style >= 0) {
+ if (stdout_reader.eol_style >= 0) {
register_argument->undo_set_eol_mode();
- register_argument->set_eol_mode(ctx.eol_style);
+ 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 */
@@ -448,8 +467,17 @@ StateExecuteCommand::done(const gchar *str)
g_source_unref(ctx.child_src);
g_spawn_close_pid(pid);
- if (ctx.error)
- goto gerror;
+ 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");
@@ -457,25 +485,17 @@ StateExecuteCommand::done(const gchar *str)
if (eval_colon())
expressions.push(SUCCESS);
- undo.push_var(register_argument) = NULL;
- return &States::start;
+ goto cleanup;
gerror:
if (!eval_colon())
- throw GlibError(ctx.error);
+ throw GlibError(error);
+ g_error_free(error);
- /*
- * If possible, encode process exit code
- * in return boolean. It's guaranteed to be
- * a failure since it's non-negative.
- */
- if (ctx.error->domain == G_SPAWN_EXIT_ERROR)
- expressions.push(ABS(ctx.error->code));
- else
- expressions.push(FAILURE);
- undo.push_var(register_argument) = NULL;
+ expressions.push(ctx.rc);
- g_error_free(ctx.error);
+cleanup:
+ undo.push_var(register_argument) = NULL;
return &States::start;
}
@@ -523,13 +543,17 @@ 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, &ctx.error);
+ 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);
@@ -541,29 +565,28 @@ stdin_watch_cb(GIOChannel *chan, GIOCondition condition, gpointer data)
StateExecuteCommand::Context &ctx =
*(StateExecuteCommand::Context *)data;
- /* we always read from the current view */
- IOView *view = (IOView *)interface.get_current_view();
-
+ const gchar *buffer;
gsize bytes_written;
- /*
- * IOView::save() cares about automatic EOL conversion
- */
- switch (view->save(chan, ctx.from, ctx.to - ctx.start,
- &bytes_written, ctx.stdin_state,
- ctx.error ? NULL : &ctx.error)) {
- case G_IO_STATUS_ERROR:
+ /* we always read from the current view */
+ buffer = (const gchar *)interface.ssm(SCI_GETRANGEPOINTER,
+ ctx.from, (sptr_t)(ctx.to - ctx.start));
+
+ try {
+ /*
+ * This cares about automatic EOL conversion
+ */
+ bytes_written = ctx.stdin_writer->convert(buffer, ctx.to - ctx.start);
+ } catch (Error &e) {
+ ctx.error = new Error(e);
/* do not yet quit -- we still have to reap the child */
goto remove;
- case G_IO_STATUS_NORMAL:
- break;
- case G_IO_STATUS_EOF:
- /* process closed stdin preliminarily? */
- goto remove;
- case G_IO_STATUS_AGAIN:
- return G_SOURCE_CONTINUE;
}
+ if (bytes_written == 0)
+ /* EOF: process closed stdin preliminarily? */
+ goto remove;
+
ctx.start += bytes_written;
if (ctx.start == ctx.to)
@@ -590,55 +613,44 @@ stdout_watch_cb(GIOChannel *chan, GIOCondition condition, gpointer data)
StateExecuteCommand::Context &ctx =
*(StateExecuteCommand::Context *)data;
- GIOStatus status;
-
- gchar buffer[1024];
- gsize read_len = 0;
- guint offset = 0;
- gsize block_len = 0;
- /* we're not really interested in that: */
- gboolean eol_style_inconsistent = FALSE;
-
for (;;) {
- status = IOView::channel_read_with_eol(
- chan, buffer, sizeof(buffer),
- read_len, offset, block_len,
- ctx.stdout_state, ctx.eol_style,
- eol_style_inconsistent,
- ctx.error ? NULL : &ctx.error
- );
-
- switch (status) {
- case G_IO_STATUS_NORMAL:
- break;
- case G_IO_STATUS_ERROR:
- case G_IO_STATUS_EOF:
- if (g_source_is_destroyed(ctx.child_src))
- g_main_loop_quit(ctx.mainloop);
- return G_SOURCE_REMOVE;
- case G_IO_STATUS_AGAIN:
- return G_SOURCE_CONTINUE;
+ 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 (!block_len)
- continue;
+ if (!data_len)
+ return G_SOURCE_CONTINUE;
if (register_argument) {
if (ctx.text_added) {
register_argument->undo_append_string();
- register_argument->append_string(buffer+offset, block_len);
+ register_argument->append_string(buffer, data_len);
} else {
register_argument->undo_set_string();
- register_argument->set_string(buffer+offset, block_len);
+ register_argument->set_string(buffer, data_len);
}
} else {
- interface.ssm(SCI_ADDTEXT, block_len, (sptr_t)(buffer+offset));
+ 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 */
diff --git a/src/spawn.h b/src/spawn.h
index 64ee999..3e01b84 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -23,6 +23,8 @@
#include "sciteco.h"
#include "parser.h"
#include "qregisters.h"
+#include "error.h"
+#include "eol.h"
namespace SciTECO {
@@ -42,10 +44,12 @@ public:
tecoInt from, to;
tecoInt start;
bool text_added;
- gint stdin_state;
- gint stdout_state;
- gint eol_style;
- GError *error;
+
+ EOLWriterGIO *stdin_writer;
+ EOLReaderGIO *stdout_reader;
+
+ Error *error;
+ tecoBool rc;
};
private: