From 61ff6e97c57f62ee3ad4ffc2166e433bc060e7cb Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Tue, 16 Aug 2016 05:04:54 +0200 Subject: 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. --- src/ioview.cpp | 353 +++++++-------------------------------------------------- 1 file changed, 39 insertions(+), 314 deletions(-) (limited to 'src/ioview.cpp') 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 @@ -85,173 +86,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. @@ -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; } -- cgit v1.2.3