aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2014-12-12 16:39:46 +0100
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2015-03-16 07:20:05 +0100
commitc310c7d875c8aa871180de130e820ed19a2489f5 (patch)
treea42eacf47ba714c99af5ec04ff2ce1b4e9f87765
parenteee4f1ae84bcbb18a9ac0f5f450510533014dd40 (diff)
downloadsciteco-c310c7d875c8aa871180de130e820ed19a2489f5.tar.gz
implemented automatic EOL translation support
* activated via bit 4 of the ED flag (enabled by default) * automatic EOL guessing on file loading and translation to LFs. * works with files that have inconsistent EOL sequences. * automatic translation to original EOL sequences on file saving * works with inconsistent EOL sequences in the buffer. This should usually not happen if the file was read in with automatic EOL translation enabled. * also works with the EC and EG commands * performance is OK, depending on the file being translated. When reading files with UNIX EOLs, the overhead is minimal typically-sized files. For DOS EOLs the overhead is larger but still acceptable. * Return (line feed) is now an immediate editing command. This centralizes EOL sequence insertion. Later, other features like auto-indent could be added to the editing command. * get_eol() has been moved to main.cpp (now called get_eol_seq() * Warn if file ownership could not be preserved when saving files. * IOView has been almost completely rewritten based on GIOChannels. The EOL translation code is also in IOView.
-rw-r--r--TODO10
-rw-r--r--configure.ac14
-rw-r--r--src/cmdline.cpp23
-rw-r--r--src/cmdline.h2
-rw-r--r--src/interface-curses.cpp2
-rw-r--r--src/interface-gtk.cpp2
-rw-r--r--src/ioview.cpp498
-rw-r--r--src/ioview.h13
-rw-r--r--src/main.cpp16
-rw-r--r--src/qregisters.cpp39
-rw-r--r--src/qregisters.h3
-rw-r--r--src/sciteco.h4
-rw-r--r--src/spawn.cpp106
-rw-r--r--src/spawn.h3
14 files changed, 603 insertions, 132 deletions
diff --git a/TODO b/TODO
index f884e0a..d7e4bea 100644
--- a/TODO
+++ b/TODO
@@ -10,6 +10,13 @@ Known Bugs:
throwing exceptions is unsafe from C-linkage callbacks.
Features:
+ * Support loading from stdin (--stdin) and writing to
+ the current buffer to stdout on exit (--stdout).
+ This will make it easy to write command line filters,
+ This also means we need something like --ed to set the
+ ED flags before everything else and --quiet.
+ Command line arguments should then also be handled
+ differently.
* The C/C++ lexer supports preprocessor evaluation.
This is currently always enabled but there are no defines.
Could be added as a global reg to set up defines easily.
@@ -52,8 +59,7 @@ Features:
* Add special Q-Register for dot:
Would simplify inserting dot with string building and saving/restoring
dot on the QReg stack
- * automatic EOL detection of buffers; easy EOL setting
- :EL command could also convert all EOLs in the current
+ * :EL command could also be used to convert all EOLs in the current
buffer.
* command to change the current working directory. This will
influence tab-completion and relative file names.
diff --git a/configure.ac b/configure.ac
index 4e85cc5..c0402a9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -102,11 +102,19 @@ AC_C_INLINE
AC_TYPE_SIZE_T
# Checks for library functions.
-AC_CHECK_FUNCS([memset setlocale strchr strrchr])
+# They must exist on every target system.
+AC_FUNC_MALLOC
+AC_FUNC_REALLOC
+AC_CHECK_FUNCS([memset setlocale strchr strrchr fstat], , [
+ AC_MSG_ERROR([Missing libc function])
+])
+# Library functions that should exist on UNIX/Linux
case $host in
-*-*-linux*)
- AC_CHECK_FUNC([realpath])
+*-*-darwin* | *-*-linux* | *-*-cygwin*)
+ AC_CHECK_FUNCS([realpath fchown], , [
+ AC_MSG_ERROR([Missing libc function])
+ ])
;;
esac
diff --git a/src/cmdline.cpp b/src/cmdline.cpp
index 84d09c3..e7ce066 100644
--- a/src/cmdline.cpp
+++ b/src/cmdline.cpp
@@ -258,6 +258,15 @@ void
Cmdline::process_edit_cmd(gchar key)
{
switch (key) {
+ case '\n': /* insert EOL sequence */
+ interface.popup_clear();
+
+ if (Flags::ed & Flags::ED_AUTOEOL)
+ insert("\n");
+ else
+ insert(get_eol_seq(interface.ssm(SCI_GETEOLMODE)));
+ break;
+
case CTL_KEY('G'): /* toggle immediate editing modifier */
interface.popup_clear();
@@ -507,20 +516,6 @@ Cmdline::fnmacro(const gchar *name)
}
}
-const gchar *
-get_eol(void)
-{
- switch (interface.ssm(SCI_GETEOLMODE)) {
- case SC_EOL_CR:
- return "\r";
- case SC_EOL_CRLF:
- return "\r\n";
- case SC_EOL_LF:
- default:
- return "\n";
- }
-}
-
static gchar *
filename_complete(const gchar *filename, gchar completed)
{
diff --git a/src/cmdline.h b/src/cmdline.h
index d73d79f..11384c1 100644
--- a/src/cmdline.h
+++ b/src/cmdline.h
@@ -90,8 +90,6 @@ private:
extern bool quit_requested;
-const gchar *get_eol(void);
-
/*
* Command states
*/
diff --git a/src/interface-curses.cpp b/src/interface-curses.cpp
index 28f6e70..eb44acb 100644
--- a/src/interface-curses.cpp
+++ b/src/interface-curses.cpp
@@ -644,7 +644,7 @@ event_loop_iter()
case KEY_ENTER:
case '\r':
case '\n':
- cmdline.keypress(get_eol());
+ cmdline.keypress('\n');
break;
/*
diff --git a/src/interface-gtk.cpp b/src/interface-gtk.cpp
index 3e1703e..965bdca 100644
--- a/src/interface-gtk.cpp
+++ b/src/interface-gtk.cpp
@@ -319,7 +319,7 @@ handle_key_press(bool is_shift, bool is_ctl, guint keyval)
cmdline.keypress('\t');
break;
case GDK_Return:
- cmdline.keypress(get_eol());
+ cmdline.keypress('\n');
break;
/*
diff --git a/src/ioview.cpp b/src/ioview.cpp
index b663fcf..72eb94b 100644
--- a/src/ioview.cpp
+++ b/src/ioview.cpp
@@ -23,8 +23,8 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#include <stdio.h>
#include <errno.h>
+#include <sys/stat.h>
#include <glib.h>
#include <glib/gprintf.h>
@@ -42,7 +42,10 @@
/* here it shouldn't cause conflicts with other headers */
#include <windows.h>
-/* still need to clean up */
+/*
+ * MinGW headers define an `interface` macro to work around
+ * Objective C issues
+ */
#undef interface
#endif
@@ -86,48 +89,284 @@ set_file_attributes(const gchar *filename, FileAttributes attrs)
#endif /* !G_OS_WIN32 */
-/*
- * The following simple implementation of file reading is actually the
- * most efficient and useful in the common case of editing small files,
- * since
- * a) it works with minimal number of syscalls and
- * b) small files cause little temporary memory overhead.
- * Reading large files however could be very inefficient since the file
- * must first be read into memory and then copied in-memory. Also it could
- * result in thrashing.
- * Alternatively we could iteratively read into a smaller buffer trading
- * in speed against (temporary) memory consumption.
- * The best way to do it could be memory mapping the file as we could
- * let Scintilla copy from the file's virtual memory directly.
- * Unfortunately since every page of the mapped file is
- * only touched once by Scintilla TLB caching is useless and the TLB is
- * effectively thrashed with entries of the mapped file.
- * This results in the doubling of page faults and weighs out the other
- * advantages of memory mapping (has been benchmarked).
+/**
+ * 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.
*
- * So in the future, the following approach could be implemented:
- * 1.) On Unix/Posix, mmap() one page at a time, hopefully preventing
- * TLB thrashing.
- * 2.) On other platforms read into and copy from a statically sized buffer
- * (perhaps page-sized)
+ * @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_consistent 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()
*/
-void
-IOView::load(const gchar *filename)
+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)
{
- gchar *contents;
- gsize size;
+ 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;
+ }
+ }
- GError *gerror = NULL;
+ /*
+ * 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
+ * (if AUTOEOL is enabled).
+ * This assumes that the channel is blocking.
+ * Also it tries to guess the size of the file behind
+ * channel in order to preallocate memory in Scintilla.
+ *
+ * @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)
+{
+ GIOStatus status;
+ GStatBuf stat_buf;
- if (!g_file_get_contents(filename, &contents, &size, &gerror))
- throw GlibError(gerror);
+ 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;
ssm(SCI_BEGINUNDOACTION);
ssm(SCI_CLEARALL);
- ssm(SCI_APPENDTEXT, size, (sptr_t)contents);
+
+ /*
+ * Preallocate memory based on the file size.
+ * May waste a few bytes if file contains DOS EOLs
+ * and EOL translation is enabled, but is faster.
+ * NOTE: g_io_channel_unix_get_fd() should report the correct fd
+ * on Windows, too.
+ */
+ stat_buf.st_size = 0;
+ if (!fstat(g_io_channel_unix_get_fd(channel), &stat_buf) &&
+ 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));
+ }
+
+ /*
+ * EOL-style guessed.
+ * Save it as the buffer's EOL mode, so save()
+ * can restore the original EOL-style.
+ * If auto-EOL-translation is disabled, this cannot
+ * have been guessed and the buffer's EOL mode should
+ * have a platform default.
+ * 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 (eol_style_inconsistent)
+ interface.msg(InterfaceCurrent::MSG_WARNING,
+ "Inconsistent EOL styles normalized");
+
ssm(SCI_ENDUNDOACTION);
+ return status;
+}
- g_free(contents);
+/**
+ * Load view's document from file.
+ */
+void
+IOView::load(const gchar *filename)
+{
+ GError *error = NULL;
+ GIOChannel *channel;
+ GIOStatus status;
+
+ channel = g_io_channel_new_file(filename, "r", &error);
+ if (!channel) {
+ Error err("Error opening file \"%s\" for reading: %s",
+ filename, error->message);
+ g_error_free(error);
+ throw err;
+ }
+
+ /*
+ * The file loading algorithm does not need buffered
+ * streams, so disabling buffering should increase
+ * performance (slightly).
+ */
+ 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) {
+ Error err("Error reading file \"%s\": %s",
+ filename, error->message);
+ g_error_free(error);
+ throw err;
+ }
}
#if 0
@@ -234,13 +473,139 @@ make_savepoint(const gchar *filename)
#endif /* !G_OS_UNIX */
+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)
+{
+ sptr_t gap;
+ gsize size;
+ 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;
+ 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;
+ g_assert(bytes_written == size);
+ }
+
+ return TRUE;
+}
+
void
IOView::save(const gchar *filename)
{
- const void *buffer;
- sptr_t gap;
- size_t size;
- FILE *file;
+ GError *error = NULL;
+ GIOChannel *channel;
#ifdef G_OS_UNIX
GStatBuf file_stat;
@@ -262,34 +627,22 @@ IOView::save(const gchar *filename)
}
/* leaves access mode intact if file still exists */
- file = g_fopen(filename, "w");
- if (!file)
- /* hopefully, errno is also always set on Windows */
- throw Error("Error opening file \"%s\" for writing: %s",
- filename, strerror(errno));
+ channel = g_io_channel_new_file(filename, "w", &error);
+ if (!channel)
+ throw GlibError(error);
- /* write part of buffer before gap */
- gap = ssm(SCI_GETGAPPOSITION);
- if (gap > 0) {
- buffer = (const void *)ssm(SCI_GETRANGEPOINTER,
- 0, gap);
- if (!fwrite(buffer, (size_t)gap, 1, file)) {
- fclose(file);
- throw Error("Error writing file \"%s\"",
- filename);
- }
- }
-
- /* write part of buffer after gap */
- size = ssm(SCI_GETLENGTH) - gap;
- if (size > 0) {
- buffer = (const void *)ssm(SCI_GETRANGEPOINTER,
- gap, size);
- if (!fwrite(buffer, size, 1, file)) {
- fclose(file);
- throw Error("Error writing file \"%s\"",
- filename);
- }
+ /*
+ * save(GIOChannel *, const gchar *) expects a buffered
+ * and blocking channel
+ */
+ 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);
+ g_io_channel_unref(channel);
+ throw err;
}
/* if file existed but has been renamed, restore attributes */
@@ -299,15 +652,18 @@ IOView::save(const gchar *filename)
/*
* only a good try to inherit owner since process user must have
* CHOWN capability traditionally reserved to root only.
- * That's why we don't handle the return value and are spammed
- * with unused-result warnings by GCC. There is NO sane way to avoid
- * this warning except, adding -Wno-unused-result which disabled all
- * such warnings.
+ * FIXME: We should probably fall back to another save point
+ * strategy.
*/
- fchown(fileno(file), file_stat.st_uid, file_stat.st_gid);
+ if (fchown(g_io_channel_unix_get_fd(channel),
+ file_stat.st_uid, file_stat.st_gid))
+ interface.msg(InterfaceCurrent::MSG_WARNING,
+ "Unable to preserve owner of \"%s\": %s",
+ filename, g_strerror(errno));
#endif
- fclose(file);
+ /* also closes file */
+ g_io_channel_unref(channel);
}
/*
diff --git a/src/ioview.h b/src/ioview.h
index 22e9e14..d314138 100644
--- a/src/ioview.h
+++ b/src/ioview.h
@@ -94,7 +94,20 @@ 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(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(const gchar *filename);
};
diff --git a/src/main.cpp b/src/main.cpp
index 79cb78b..7ad4d80 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -71,7 +71,7 @@ QRegisterTable QRegisters::globals;
Ring ring;
namespace Flags {
- tecoInt ed = 0;
+ tecoInt ed = ED_AUTOEOL;
}
static gchar *eval_macro = NULL;
@@ -88,6 +88,20 @@ static gpointer g_realloc_exception(gpointer mem, gsize n_bytes);
static void sigint_handler(int signal);
}
+const gchar *
+get_eol_seq(gint eol_mode)
+{
+ switch (eol_mode) {
+ case SC_EOL_CRLF:
+ return "\r\n";
+ case SC_EOL_CR:
+ return "\r";
+ case SC_EOL_LF:
+ default:
+ return "\n";
+ }
+}
+
#ifdef G_OS_WIN32
/*
diff --git a/src/qregisters.cpp b/src/qregisters.cpp
index acb12cf..e792fc0 100644
--- a/src/qregisters.cpp
+++ b/src/qregisters.cpp
@@ -252,6 +252,40 @@ QRegister::execute(bool locals)
}
void
+QRegister::undo_set_eol_mode(void)
+{
+ if (!must_undo)
+ return;
+
+ /*
+ * Necessary, so that upon rubout the
+ * string's parameters are restored.
+ */
+ string.update(QRegisters::view);
+
+ if (QRegisters::current && QRegisters::current->must_undo)
+ QRegisters::current->string.undo_edit(QRegisters::view);
+
+ QRegisters::view.undo_ssm(SCI_SETEOLMODE,
+ QRegisters::view.ssm(SCI_GETEOLMODE));
+
+ string.undo_edit(QRegisters::view);
+}
+
+void
+QRegister::set_eol_mode(gint mode)
+{
+ if (QRegisters::current)
+ QRegisters::current->string.update(QRegisters::view);
+
+ string.edit(QRegisters::view);
+ QRegisters::view.ssm(SCI_SETEOLMODE, mode);
+
+ if (QRegisters::current)
+ QRegisters::current->string.edit(QRegisters::view);
+}
+
+void
QRegister::load(const gchar *filename)
{
undo_set_string();
@@ -263,6 +297,11 @@ QRegister::load(const gchar *filename)
string.reset();
/*
+ * IOView::load() might change the EOL style.
+ */
+ undo_set_eol_mode();
+
+ /*
* undo_set_string() pushes undo tokens that restore
* the previous document in the view.
* So if loading fails, QRegisters::current will be
diff --git a/src/qregisters.h b/src/qregisters.h
index 0f2404a..b2d856d 100644
--- a/src/qregisters.h
+++ b/src/qregisters.h
@@ -150,6 +150,9 @@ public:
void execute(bool locals = true);
+ void undo_set_eol_mode(void);
+ void set_eol_mode(gint mode);
+
/*
* Load and save already care about undo token
* creation.
diff --git a/src/sciteco.h b/src/sciteco.h
index c4395c1..117791b 100644
--- a/src/sciteco.h
+++ b/src/sciteco.h
@@ -39,6 +39,7 @@ typedef tecoInt tecoBool;
namespace Flags {
enum {
+ ED_AUTOEOL = (1 << 4),
ED_HOOKS = (1 << 5),
ED_FNKEYS = (1 << 6),
ED_SHELLEMU = (1 << 7)
@@ -79,6 +80,9 @@ extern sig_atomic_t sigint_occurred;
#define IS_SUCCESS(X) ((X) < 0)
#define IS_FAILURE(X) (!IS_SUCCESS(X))
+/* in main.cpp */
+const gchar *get_eol_seq(gint eol_mode);
+
namespace Validate {
static inline bool
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 259f708..af42a95 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -26,6 +26,7 @@
#include "undo.h"
#include "expressions.h"
#include "qregisters.h"
+#include "ioview.h"
#include "ring.h"
#include "parser.h"
#include "error.h"
@@ -308,6 +309,12 @@ StateExecuteCommand::done(const gchar *str)
GIOChannel *stdin_chan, *stdout_chan;
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.error = NULL;
argv = parse_shell_command_line(str, &ctx.error);
@@ -341,7 +348,11 @@ StateExecuteCommand::done(const gchar *str)
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);
- g_io_channel_set_buffered(stdout_chan, FALSE);
+ /*
+ * IOView::save() expects the channel to be buffered
+ * for performance reasons
+ */
+ g_io_channel_set_buffered(stdout_chan, TRUE);
ctx.stdin_src = g_io_create_watch(stdin_chan,
(GIOCondition)(G_IO_OUT | G_IO_ERR | G_IO_HUP));
@@ -368,8 +379,12 @@ StateExecuteCommand::done(const gchar *str)
interface.ssm(SCI_DELETERANGE, ctx.from, ctx.to - ctx.from);
interface.ssm(SCI_ENDUNDOACTION);
- if (!register_argument &&
- (ctx.from != ctx.to || ctx.text_added)) {
+ if (register_argument) {
+ if (ctx.eol_style >= 0) {
+ register_argument->undo_set_eol_mode();
+ register_argument->set_eol_mode(ctx.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);
@@ -478,16 +493,17 @@ stdin_watch_cb(GIOChannel *chan, GIOCondition condition, gpointer data)
StateExecuteCommand::Context &ctx =
*(StateExecuteCommand::Context *)data;
- const gchar *buffer;
- gsize bytes_written;
+ /* we always read from the current view */
+ IOView *view = (IOView *)interface.get_current_view();
- buffer = (const gchar *)interface.ssm(SCI_GETRANGEPOINTER,
- ctx.from,
- ctx.to - ctx.start);
+ gsize bytes_written;
- switch (g_io_channel_write_chars(chan, buffer, ctx.to - ctx.start,
- &bytes_written,
- ctx.error ? NULL : &ctx.error)) {
+ /*
+ * 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:
/* do not yet quit -- we still have to reap the child */
goto remove;
@@ -526,38 +542,54 @@ stdout_watch_cb(GIOChannel *chan, GIOCondition condition, gpointer data)
StateExecuteCommand::Context &ctx =
*(StateExecuteCommand::Context *)data;
- gchar buffer[1024];
- gsize bytes_read;
+ GIOStatus status;
- switch (g_io_channel_read_chars(chan, buffer, sizeof(buffer)-1,
- &bytes_read,
- ctx.error ? NULL : &ctx.error)) {
- 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;
- }
-
- if (register_argument) {
- buffer[bytes_read] = '\0';
+ 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;
+ }
- if (ctx.text_added) {
- register_argument->undo_append_string();
- register_argument->append_string(buffer);
+ if (!block_len)
+ continue;
+
+ if (register_argument) {
+ if (ctx.text_added) {
+ register_argument->undo_append_string();
+ register_argument->append_string(buffer+offset, block_len);
+ } else {
+ register_argument->undo_set_string();
+ register_argument->set_string(buffer+offset, block_len);
+ }
} else {
- register_argument->undo_set_string();
- register_argument->set_string(buffer);
+ interface.ssm(SCI_ADDTEXT, block_len, (sptr_t)(buffer+offset));
}
- } else {
- interface.ssm(SCI_ADDTEXT, bytes_read, (sptr_t)buffer);
+ ctx.text_added = true;
}
- ctx.text_added = true;
+ /* not reached */
return G_SOURCE_CONTINUE;
}
diff --git a/src/spawn.h b/src/spawn.h
index 32cea62..6ae84fd 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -42,6 +42,9 @@ public:
tecoInt from, to;
tecoInt start;
bool text_added;
+ gint stdin_state;
+ gint stdout_state;
+ gint eol_style;
GError *error;
};