diff options
| author | Robin Haberkorn <rhaberkorn@fmsbw.de> | 2026-07-25 01:55:28 +0200 |
|---|---|---|
| committer | Robin Haberkorn <rhaberkorn@fmsbw.de> | 2026-07-25 01:55:28 +0200 |
| commit | 685507922b0b75da5935076395a5b1ec1ef58356 (patch) | |
| tree | bebc7c421856a6da363d2f9f7010b1ed211fee6c /src | |
| parent | 378b9da1476cdcac14332b89f6286a450c4a7757 (diff) | |
revised and improved the Unicode glyph-to-byte conversion heuristics
Previously almost all glyph-to-byte offset conversions consulted
Scintilla's line index and counted characters on the resulting line.
For instance a simple expression like `.+1J` would scan the same line
twice completely, which would be very slow on pathologically long lines.
Even insertions did that due to having to update the ^Y ranges.
If you repeat such an operation over all characters as in `<.+1:J;>`
you would have complexity O(n^2) for n = line length.
Only commands with an explicit relative nature like `C` and `A` would
use teco_view_glyph2bytes_relative() which scans beginning at dot
as long as the relative movement is less than 1024 glyphs.
Wit the new heuristics almost all glyph-to-byte and byte-to-glyph
conversions can make use of that optimization.
This requires that dot must at all times be known in glyphs as well -
the byte position is managed by Scintilla (SCI_GETCURRENTPOS).
We therefore introduced teco_current_doc_set_dot() and
teco_current_doc_get_dot() to update dot in the current buffer or
Q-Register -- it cannot be stored along with the view since
Q-Registers share a single view.
A number of auxiliary functions have been introduced for
converting relative to a known (glyphs,bytes) offset pair
and for converting absolute and relative positions with regard
to the current doc and SCI_GETCURRENTPOS position.
Of course this is error-prone since the glyph and dot positions
are interdependant - they must always be kept in sync.
With these new optimizations even pathologically long lines can
(usually) be managed even in UTF-8 documents.
It does not address slow-downs in Scintilla's line layout, yet.
grosciteco.tes for instance runs twice as fast now.
Diffstat (limited to 'src')
| -rw-r--r-- | src/cmdline.c | 10 | ||||
| -rw-r--r-- | src/core-commands.c | 89 | ||||
| -rw-r--r-- | src/core-commands.h | 3 | ||||
| -rw-r--r-- | src/doc.c | 13 | ||||
| -rw-r--r-- | src/doc.h | 24 | ||||
| -rw-r--r-- | src/glob.c | 21 | ||||
| -rw-r--r-- | src/help.c | 13 | ||||
| -rw-r--r-- | src/interface.h | 64 | ||||
| -rw-r--r-- | src/move-commands.c | 72 | ||||
| -rw-r--r-- | src/parser.c | 2 | ||||
| -rw-r--r-- | src/qreg-commands.c | 19 | ||||
| -rw-r--r-- | src/qreg.c | 18 | ||||
| -rw-r--r-- | src/qreg.h | 1 | ||||
| -rw-r--r-- | src/ring.c | 13 | ||||
| -rw-r--r-- | src/ring.h | 26 | ||||
| -rw-r--r-- | src/search.c | 49 | ||||
| -rw-r--r-- | src/spawn.c | 13 | ||||
| -rw-r--r-- | src/stdio-commands.c | 2 | ||||
| -rw-r--r-- | src/view.c | 113 | ||||
| -rw-r--r-- | src/view.h | 7 |
20 files changed, 427 insertions, 145 deletions
diff --git a/src/cmdline.c b/src/cmdline.c index b944b5e..8b2aebd 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -476,12 +476,10 @@ teco_cmdline_keymacro(const gchar *name, gssize name_len, GError **error) static void teco_cmdline_rubout(void) { - gsize effective_len = teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0); - gssize p = teco_view_glyphs2bytes_relative(teco_cmdline.view, effective_len, -1); - if (p >= 0) { - teco_cmdline_ssm(SCI_GOTOPOS, p, 0); - teco_undo_pop(p); - } + sptr_t effective_len = teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0); + sptr_t p = teco_cmdline_ssm(SCI_POSITIONRELATIVE, effective_len, -1); + teco_cmdline_ssm(SCI_GOTOPOS, p, 0); + teco_undo_pop(p); } /** diff --git a/src/core-commands.c b/src/core-commands.c index 81d5869..94501b6 100644 --- a/src/core-commands.c +++ b/src/core-commands.c @@ -72,7 +72,8 @@ static teco_state_t *teco_state_ctlc_control_input(teco_machine_main_t *ctx, gun * But it needs to discern between invalid ranges and other errors. */ gboolean -teco_get_range_args(const gchar *cmd, gsize *from_ret, gsize *len_ret, GError **error) +teco_get_range_args(const gchar *cmd, teco_int_t *from_glyphs_ret, gsize *from_ret, + teco_int_t *len_glyphs_ret, gsize *len_ret, GError **error) { gssize from, len; /* in bytes */ @@ -99,15 +100,27 @@ teco_get_range_args(const gchar *cmd, gsize *from_ret, gsize *len_ret, GError ** from += len; len *= -1; } + + if (from_glyphs_ret) + *from_glyphs_ret = teco_interface_bytes2glyphs_absdot(from); + if (len_glyphs_ret) + *len_glyphs_ret = teco_interface_bytes2glyphs_absdot(from+len); } else { - gssize to = teco_interface_glyphs2bytes(teco_expressions_pop_num(0)); - from = teco_interface_glyphs2bytes(teco_expressions_pop_num(0)); + teco_int_t to_glyphs = teco_expressions_pop_num(0); + gssize to = teco_interface_glyphs2bytes_absdot(to_glyphs); + teco_int_t from_glyphs = teco_expressions_pop_num(0); + from = teco_interface_glyphs2bytes_absdot(from_glyphs); len = to - from; if (len < 0 || from < 0 || to < 0) { teco_error_range_set(error, cmd); return FALSE; } + + if (from_glyphs_ret) + *from_glyphs_ret = from_glyphs; + if (len_glyphs_ret) + *len_glyphs_ret = to_glyphs - from_glyphs; } *from_ret = from; @@ -199,8 +212,7 @@ teco_state_start_dot(teco_machine_main_t *ctx, GError **error) { if (!teco_expressions_eval(FALSE, error)) return; - sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - teco_expressions_push(teco_interface_bytes2glyphs(pos)); + teco_expressions_push(teco_current_doc_get_dot()); } /*$ Z size @@ -217,7 +229,7 @@ teco_state_start_zed(teco_machine_main_t *ctx, GError **error) if (!teco_expressions_eval(FALSE, error)) return; sptr_t pos = teco_interface_ssm(SCI_GETLENGTH, 0, 0); - teco_expressions_push(teco_interface_bytes2glyphs(pos)); + teco_expressions_push(teco_interface_bytes2glyphs_absdot(pos)); } /*$ H @@ -235,7 +247,7 @@ teco_state_start_range(teco_machine_main_t *ctx, GError **error) return; teco_expressions_push(0); sptr_t pos = teco_interface_ssm(SCI_GETLENGTH, 0, 0); - teco_expressions_push(teco_interface_bytes2glyphs(pos)); + teco_expressions_push(teco_interface_bytes2glyphs_absdot(pos)); } /*$ \[rs] @@ -267,8 +279,7 @@ teco_state_start_backslash(teco_machine_main_t *ctx, GError **error) g_assert(*str != '\0'); gsize len = strlen(str); - sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos); + teco_undo_int(teco_ranges[0].from) = teco_current_doc_get_dot(); /* * We can assume that `len` is already in glyphs, * i.e. formatted numbers will never use multi-byte/Unicode characters. @@ -276,6 +287,8 @@ teco_state_start_backslash(teco_machine_main_t *ctx, GError **error) teco_undo_int(teco_ranges[0].to) = teco_ranges[0].from + len; teco_undo_guint(teco_ranges_count) = 1; + teco_current_doc_set_dot(teco_ranges[0].to); + teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0); teco_interface_ssm(SCI_ADDTEXT, len, (sptr_t)str); teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0); @@ -563,6 +576,12 @@ teco_state_start_cmdline_push(teco_machine_main_t *ctx, GError **error) * A undo action should always have been generated. */ undo__teco_interface_ssm(SCI_UNDO, 0, 0); + + /* + * FIXME: If we would maintain dot in the command line, + * it could simply be copied. + */ + teco_current_doc_set_dot(teco_interface_bytes2glyphs_rel(0, 0, teco_cmdline.pc)); } static void @@ -617,8 +636,7 @@ teco_state_start_get(teco_machine_main_t *ctx, GError **error) if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error)) return; - sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - gssize get_pos = teco_interface_glyphs2bytes_relative(pos, v); + gssize get_pos = teco_interface_glyphs2bytes_reldot(v); sptr_t len = teco_interface_ssm(SCI_GETLENGTH, 0, 0); teco_expressions_push(get_pos < 0 || get_pos == len @@ -1444,14 +1462,9 @@ teco_state_control_glyphs2bytes(teco_machine_main_t *ctx, GError **error) res = teco_interface_ssm(colon_modified ? SCI_GETLENGTH : SCI_GETCURRENTPOS, 0, 0); } else { teco_int_t pos = teco_expressions_pop_num(0); - if (colon_modified) { - /* teco_interface_bytes2glyphs() does not check addresses */ - res = 0 <= pos && pos <= teco_interface_ssm(SCI_GETLENGTH, 0, 0) - ? teco_interface_bytes2glyphs(pos) : -1; - } else { - /* negative values for invalid indexes are passed down. */ - res = teco_interface_glyphs2bytes(pos); - } + /* negative values for invalid indexes are passed down. */ + res = colon_modified ? teco_interface_bytes2glyphs_absdot(pos) + : teco_interface_glyphs2bytes_absdot(pos); } teco_expressions_push(res); @@ -2671,7 +2684,7 @@ teco_state_ecommand_encoding(teco_machine_main_t *ctx, GError **error) teco_int_t dot_glyphs = 0; if (colon_modified) { sptr_t dot_bytes = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - dot_glyphs = teco_interface_bytes2glyphs(dot_bytes); + dot_glyphs = teco_current_doc_get_dot(); /* * Convert buffer to new codepage. @@ -2719,7 +2732,7 @@ teco_state_ecommand_encoding(teco_machine_main_t *ctx, GError **error) teco_interface_ssm(SCI_SETCODEPAGE, SC_CP_UTF8, 0); /* * UTF-8 documents strictly require the line character index. - * See teco_view_glyphs2bytes() and teco_view_bytes2glyphs(). + * See teco_view_glyphs2bytes_unicode() and teco_view_bytes2glyphs_unicode(). */ g_assert(!(teco_interface_ssm(SCI_GETLINECHARACTERINDEX, 0, 0) & SC_LINECHARACTERINDEX_UTF32)); @@ -2763,13 +2776,18 @@ teco_state_ecommand_encoding(teco_machine_main_t *ctx, GError **error) teco_interface_ssm(SCI_SETCODEPAGE, 0, 0); } - if (colon_modified) + if (colon_modified) { /* * Only now, it will be safe to recalculate dot in the new encoding. * If the new codepage is UTF-8, the line character index will be * ready only now. */ - teco_interface_ssm(SCI_GOTOPOS, teco_interface_glyphs2bytes(dot_glyphs), 0); + teco_interface_ssm(SCI_GOTOPOS, teco_interface_glyphs2bytes_rel(0, 0, dot_glyphs), 0); + } else { + /* recalculate dot */ + sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_current_doc_set_dot(teco_interface_bytes2glyphs_rel(0, 0, pos)); + } } /*$ EO version @@ -2928,8 +2946,7 @@ teco_state_insert_initial(teco_machine_main_t *ctx, GError **error) if (ctx->flags.mode > TECO_MODE_NORMAL) return TRUE; - sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos); + teco_undo_int(teco_ranges[0].from) = teco_current_doc_get_dot(); teco_undo_guint(teco_ranges_count) = 1; /* @@ -2942,8 +2959,10 @@ teco_state_insert_initial(teco_machine_main_t *ctx, GError **error) if (!teco_expressions_eval(FALSE, error)) return FALSE; guint args = teco_expressions_args(); - if (!args) + if (!args) { + teco_undo_int(teco_ranges[0].to) = teco_ranges[0].from; return TRUE; + } if (teco_interface_ssm(SCI_GETCODEPAGE, 0, 0) == SC_CP_UTF8) { /* detect possible errors before introducing side effects */ @@ -2986,6 +3005,9 @@ teco_state_insert_initial(teco_machine_main_t *ctx, GError **error) for (gint i = 0; i < args; i++) teco_expressions_pop_num(0); + /* this way we remember how many numeric arguments were given */ + teco_undo_int(teco_ranges[0].to) = teco_ranges[0].from + args; + return TRUE; } @@ -3013,8 +3035,15 @@ teco_state_insert_done(teco_machine_main_t *ctx, teco_string_t str, GError **err if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; + /* + * NOTE: teco_interface_bytes2glyphs_reldot() would count relative to + * dot, which is still invalid. + */ sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos); + teco_int_t to = teco_interface_bytes2glyphs_rel(teco_ranges[0].to, pos-str.len, str.len); + teco_undo_int(teco_ranges[0].to) = to; + teco_current_doc_set_dot(to); + return &teco_state_start; } @@ -3053,12 +3082,18 @@ teco_state_insert_indent_initial(teco_machine_main_t *ctx, GError **error) teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0); if (teco_interface_ssm(SCI_GETUSETABS, 0, 0)) { teco_interface_ssm(SCI_ADDTEXT, 1, (sptr_t)"\t"); + + /* teco_state_insert_done() expects this */ + teco_undo_int(teco_ranges[0].to)++; } else { gint len = teco_interface_ssm(SCI_GETTABWIDTH, 0, 0); len -= teco_interface_ssm(SCI_GETCOLUMN, teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0), 0) % len; + /* teco_state_insert_done() expects this */ + teco_undo_int(teco_ranges[0].to) += len; + gchar space = ' '; while (len-- > 0) teco_interface_ssm(SCI_ADDTEXT, 1, (sptr_t)&space); diff --git a/src/core-commands.h b/src/core-commands.h index 254c4a7..6a31b92 100644 --- a/src/core-commands.h +++ b/src/core-commands.h @@ -29,7 +29,8 @@ teco_is_noop(gunichar c) return c == ' ' || c == '\f' || c == '\r' || c == '\n' || c == '\v'; } -gboolean teco_get_range_args(const gchar *cmd, gsize *from_ret, gsize *len_ret, GError **error); +gboolean teco_get_range_args(const gchar *cmd, teco_int_t *from_glyphs_ret, gsize *from_ret, + teco_int_t *len_glyphs_ret, gsize *len_ret, GError **error); /* in cmdline.c */ gboolean teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, @@ -77,7 +77,7 @@ teco_doc_edit(teco_doc_t *ctx, guint default_cp) (sptr_t)teco_doc_get_scintilla(ctx)); teco_view_ssm(teco_qreg_view, SCI_SETFIRSTVISIBLELINE, ctx->first_line, 0); teco_view_ssm(teco_qreg_view, SCI_SETXOFFSET, ctx->xoffset, 0); - teco_view_ssm(teco_qreg_view, SCI_SETSEL, ctx->anchor, (sptr_t)ctx->dot); + teco_view_ssm(teco_qreg_view, SCI_SETSEL, ctx->anchor, (sptr_t)ctx->pos); /* * NOTE: Thanks to a custom Scintilla patch, representations @@ -124,7 +124,7 @@ teco_doc_undo_edit(teco_doc_t *ctx) */ //undo__teco_view_set_representations(teco_qreg_view); - undo__teco_view_ssm(teco_qreg_view, SCI_SETSEL, ctx->anchor, (sptr_t)ctx->dot); + undo__teco_view_ssm(teco_qreg_view, SCI_SETSEL, ctx->anchor, (sptr_t)ctx->pos); undo__teco_view_ssm(teco_qreg_view, SCI_SETXOFFSET, ctx->xoffset, 0); undo__teco_view_ssm(teco_qreg_view, SCI_SETFIRSTVISIBLELINE, ctx->first_line, 0); undo__teco_view_ssm(teco_qreg_view, SCI_SETDOCPOINTER, 0, @@ -222,9 +222,11 @@ void teco_doc_update_from_view(teco_doc_t *ctx, teco_view_t *from) { ctx->anchor = teco_view_ssm(from, SCI_GETANCHOR, 0, 0); - ctx->dot = teco_view_ssm(from, SCI_GETCURRENTPOS, 0, 0); + ctx->pos = teco_view_ssm(from, SCI_GETCURRENTPOS, 0, 0); ctx->first_line = teco_view_ssm(from, SCI_GETFIRSTVISIBLELINE, 0, 0); ctx->xoffset = teco_view_ssm(from, SCI_GETXOFFSET, 0, 0); + + ctx->dot = teco_view_bytes2glyphs_rel(from, 0, 0, ctx->pos); } /** @memberof teco_doc_t */ @@ -232,9 +234,11 @@ void teco_doc_update_from_doc(teco_doc_t *ctx, const teco_doc_t *from) { ctx->anchor = from->anchor; - ctx->dot = from->dot; + ctx->pos = from->pos; ctx->first_line = from->first_line; ctx->xoffset = from->xoffset; + + ctx->dot = from->dot; } /** @@ -252,7 +256,6 @@ teco_doc_exchange(teco_doc_t *ctx, teco_doc_t *other) memcpy(other, &temp, sizeof(*other)); } -/** @memberof teco_doc_t */ void teco_doc_clear(teco_doc_t *ctx) { @@ -51,10 +51,17 @@ typedef struct { /* * The so called "parameters". + * They are all in bytes. * Updated/restored only when required */ - gint anchor, dot; - gint first_line, xoffset; + gsize anchor, pos; + guint first_line, xoffset; + + /** + * Dot in glyphs. + * This field is always kept up to date. + */ + teco_int_t dot; } teco_doc_t; /** @memberof teco_doc_t */ @@ -85,8 +92,9 @@ void teco_doc_update_from_doc(teco_doc_t *ctx, const teco_doc_t *from); static inline void teco_doc_reset(teco_doc_t *ctx) { - ctx->anchor = ctx->dot = 0; + ctx->anchor = ctx->pos = 0; ctx->first_line = ctx->xoffset = 0; + ctx->dot = 0; } /** @memberof teco_doc_t */ @@ -98,10 +106,12 @@ teco_doc_undo_reset(teco_doc_t *ctx) * and called with teco_undo_call() if we really * wanted to save more memory. */ - teco_undo_gint(ctx->anchor); - teco_undo_gint(ctx->dot); - teco_undo_gint(ctx->first_line); - teco_undo_gint(ctx->xoffset); + teco_undo_gsize(ctx->anchor); + teco_undo_gsize(ctx->pos); + teco_undo_guint(ctx->first_line); + teco_undo_guint(ctx->xoffset); + + teco_undo_int(ctx->dot); } void teco_doc_exchange(teco_doc_t *ctx, teco_doc_t *other); @@ -38,6 +38,7 @@ #include "expressions.h" #include "qreg.h" #include "ring.h" +#include "view.h" #include "error.h" #include "undo.h" #include "glob.h" @@ -527,21 +528,24 @@ teco_state_glob_filename_done(teco_machine_main_t *ctx, teco_string_t str, GErro gint rc = tere_exec(&pattern, (chr *)filename, strlen(filename), NULL, 0, NULL, 0); if (rc == REG_OKAY && (teco_test_mode == 0 || g_file_test(filename, file_flags))) { if (!colon_modified) { - sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - /* * FIXME: Filenames may contain linefeeds. * But if we add them null-terminated, they will be relatively hard to parse. */ gsize len = strlen(filename); filename[len] = '\n'; + + sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_undo_int(teco_ranges[0].from) = teco_current_doc_get_dot(); + teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0); teco_interface_ssm(SCI_ADDTEXT, len+1, (sptr_t)filename); teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0); - teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos); - teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos + len + 1); + teco_int_t to = teco_interface_bytes2glyphs_rel(teco_ranges[0].from, pos, len + 1); + teco_undo_int(teco_ranges[0].to) = to; teco_undo_guint(teco_ranges_count) = 1; + teco_current_doc_set_dot(to); } matching = TRUE; @@ -565,14 +569,15 @@ teco_state_glob_filename_done(teco_machine_main_t *ctx, teco_string_t str, GErro globber = teco_globber_new(pattern_str.data, file_flags); sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos); + gsize total_len = 0; + teco_undo_int(teco_ranges[0].from) = teco_current_doc_get_dot(); teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0); gchar *globbed_filename; while ((globbed_filename = teco_globber_next(globber))) { gsize len = strlen(globbed_filename); - pos += len+1; + total_len += len+1; /* * FIXME: Filenames may contain linefeeds. @@ -587,8 +592,10 @@ teco_state_glob_filename_done(teco_machine_main_t *ctx, teco_string_t str, GErro teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0); - teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos); + teco_int_t to = teco_interface_bytes2glyphs_rel(teco_ranges[0].from, pos, total_len); + teco_undo_int(teco_ranges[0].to) = to; teco_undo_guint(teco_ranges_count) = 1; + teco_current_doc_set_dot(to); } if (colon_modified) { @@ -38,7 +38,7 @@ #include "rb3str.h" #include "help.h" -static void teco_help_set(const gchar *topic_name, const gchar *filename, teco_int_t pos); +static void teco_help_set(const gchar *topic_name, const gchar *filename, gsize pos); static GStringChunk *teco_help_chunk = NULL; @@ -46,13 +46,14 @@ static GStringChunk *teco_help_chunk = NULL; typedef struct { teco_rb3str_head_t head; - teco_int_t pos; + /** position of topic in filename (in bytes) */ + gsize pos; gchar filename[]; } teco_help_topic_t; /** @static @memberof teco_help_topic_t */ static teco_help_topic_t * -teco_help_topic_new(const gchar *topic_name, const gchar *filename, teco_int_t pos) +teco_help_topic_new(const gchar *topic_name, const gchar *filename, gsize pos) { /* * Topics are inserted only once into the RB tree, so we can store @@ -157,7 +158,7 @@ teco_help_init(GError **error) do { gchar *endptr; - teco_int_t pos = strtoul(topic, &endptr, 10); + gsize pos = strtoul(topic, &endptr, 10); /* * This also breaks at the last line of the @@ -196,7 +197,7 @@ teco_help_find(const gchar *topic_name) } static void -teco_help_set(const gchar *topic_name, const gchar *filename, teco_int_t pos) +teco_help_set(const gchar *topic_name, const gchar *filename, gsize pos) { teco_help_topic_t *topic; teco_help_topic_t *existing = teco_help_find(topic_name); @@ -298,6 +299,8 @@ teco_state_help_done(teco_machine_main_t *ctx, teco_string_t str, GError **error !teco_ring_edit(topic->filename, error)) return NULL; + teco_current_doc_set_dot(teco_interface_bytes2glyphs_absdot(topic->pos)); + /* * Make sure the topic is visible. * We do need undo tokens for this (even though diff --git a/src/interface.h b/src/interface.h index a7968d1..9c3d31e 100644 --- a/src/interface.h +++ b/src/interface.h @@ -211,21 +211,73 @@ teco_interface_get_codepage(void) } static inline gssize -teco_interface_glyphs2bytes(teco_int_t pos) +teco_interface_glyphs2bytes_rel(teco_int_t pos_glyphs, gsize pos, teco_int_t n) { - return teco_view_glyphs2bytes(teco_interface_current_view, pos); + return teco_view_glyphs2bytes_rel(teco_interface_current_view, + pos_glyphs, pos, n); } static inline teco_int_t -teco_interface_bytes2glyphs(gsize pos) +teco_interface_bytes2glyphs_rel(teco_int_t pos_glyphs, gsize pos, gssize n) { - return teco_view_bytes2glyphs(teco_interface_current_view, pos); + return teco_view_bytes2glyphs_rel(teco_interface_current_view, + pos_glyphs, pos, n); } +/** + * Convert absolute glyph position to bytes. + * + * This allows for scanning around dot and should be used if + * pos can be expected to be close to dot. + * Dot in glyphs as set by teco_current_doc_set_dot() + * \b must be up to date for this to work. + */ +static inline gssize +teco_interface_glyphs2bytes_absdot(teco_int_t pos) +{ + teco_int_t dot = teco_current_doc_get_dot(); + gsize curpos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + return teco_interface_glyphs2bytes_rel(dot, curpos, pos-dot); +} + +/** + * Convert absolute byte position to glyphs. + * + * @see teco_interface_glyphs2bytes_absdot + */ +static inline teco_int_t +teco_interface_bytes2glyphs_absdot(gsize pos) +{ + teco_int_t dot = teco_current_doc_get_dot(); + gsize curpos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + return teco_interface_bytes2glyphs_rel(dot, curpos, pos-curpos); +} + +/** + * Convert relative glyph position to bytes. + * + * Dot in glyphs as set by teco_current_doc_set_dot() + * \b must be up to date for this to work. + */ static inline gssize -teco_interface_glyphs2bytes_relative(gsize pos, teco_int_t n) +teco_interface_glyphs2bytes_reldot(teco_int_t n) +{ + teco_int_t dot = teco_current_doc_get_dot(); + gsize curpos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + return teco_interface_glyphs2bytes_rel(dot, curpos, n); +} + +/** + * Convert relative byte position to glyphs. + * + * @see teco_interface_glyphs2bytes_reldot + */ +static inline teco_int_t +teco_interface_bytes2glyphs_reldot(gssize n) { - return teco_view_glyphs2bytes_relative(teco_interface_current_view, pos, n); + teco_int_t dot = teco_current_doc_get_dot(); + gsize curpos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + return teco_interface_bytes2glyphs_rel(dot, curpos, n); } static inline teco_int_t diff --git a/src/move-commands.c b/src/move-commands.c index 45afc4e..9ef893e 100644 --- a/src/move-commands.c +++ b/src/move-commands.c @@ -58,12 +58,13 @@ teco_state_start_jump(teco_machine_main_t *ctx, GError **error) if (!teco_expressions_pop_num_calc(&v, 0, error)) return; - gssize pos = teco_interface_glyphs2bytes(v); - if (pos >= 0) { + gssize next_pos = teco_interface_glyphs2bytes_absdot(v); + if (next_pos >= 0) { if (teco_current_doc_must_undo()) undo__teco_interface_ssm(SCI_GOTOPOS, teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0), 0); - teco_interface_ssm(SCI_GOTOPOS, pos, 0); + teco_interface_ssm(SCI_GOTOPOS, next_pos, 0); + teco_current_doc_set_dot(v); if (teco_machine_main_eval_colon(ctx) > 0) teco_expressions_push(TECO_SUCCESS); @@ -78,14 +79,15 @@ teco_state_start_jump(teco_machine_main_t *ctx, GError **error) static teco_bool_t teco_move_chars(teco_int_t n) { - sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - gssize next_pos = teco_interface_glyphs2bytes_relative(pos, n); + gssize next_pos = teco_interface_glyphs2bytes_reldot(n); if (next_pos < 0) return TECO_FAILURE; - teco_interface_ssm(SCI_GOTOPOS, next_pos, 0); if (teco_current_doc_must_undo()) - undo__teco_interface_ssm(SCI_GOTOPOS, pos, 0); + undo__teco_interface_ssm(SCI_GOTOPOS, teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0), 0); + + teco_interface_ssm(SCI_GOTOPOS, next_pos, 0); + teco_current_doc_set_dot(teco_current_doc_get_dot() + n); return TECO_SUCCESS; } @@ -151,7 +153,15 @@ teco_move_lines(teco_int_t n) if (!teco_validate_line(line)) return TECO_FAILURE; - teco_interface_ssm(SCI_GOTOLINE, line, 0); + sptr_t next_pos = teco_interface_ssm(SCI_POSITIONFROMLINE, line, 0); + /* + * FIXME: Perhaps it would be more efficient to fall back to the + * line index cache via teco_view_bytes2glyphs_unicode() since next_pos + * is guaranteed to fall on line starts. + */ + teco_current_doc_set_dot(teco_interface_bytes2glyphs_absdot(next_pos)); + teco_interface_ssm(SCI_GOTOPOS, next_pos, 0); + if (teco_current_doc_must_undo()) undo__teco_interface_ssm(SCI_GOTOPOS, pos, 0); @@ -388,6 +398,8 @@ teco_state_start_words(teco_machine_main_t *ctx, const gchar *cmd, gint factor, if (rc) { if (teco_current_doc_must_undo()) undo__teco_interface_ssm(SCI_GOTOPOS, pos, 0); + + teco_current_doc_set_dot(teco_interface_bytes2glyphs_absdot(word_pos)); teco_interface_ssm(SCI_GOTOPOS, word_pos, 0); } @@ -457,6 +469,9 @@ teco_state_start_delete_words(teco_machine_main_t *ctx, const gchar *cmd, gint f if (rc && start_pos != end_pos) { g_assert(start_pos < end_pos); + if (start_pos < pos) + teco_current_doc_set_dot(teco_interface_bytes2glyphs_absdot(start_pos)); + teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0); teco_interface_ssm(SCI_DELETERANGE, start_pos, end_pos-start_pos); teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0); @@ -487,8 +502,10 @@ teco_state_start_kill(teco_machine_main_t *ctx, const gchar *cmd, gboolean by_li if (!teco_expressions_eval(FALSE, error)) return FALSE; + sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + if (teco_expressions_args() <= 1) { - from = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + from = pos; if (by_lines) { teco_int_t line; if (!teco_expressions_pop_num_calc(&line, teco_num_sign, error)) @@ -496,13 +513,24 @@ teco_state_start_kill(teco_machine_main_t *ctx, const gchar *cmd, gboolean by_li line += teco_interface_ssm(SCI_LINEFROMPOSITION, from, 0); len = teco_interface_ssm(SCI_POSITIONFROMLINE, line, 0) - from; rc = teco_bool(teco_validate_line(line)); + + /* + * FIXME: Perhaps it would be more efficient to fall back to the + * line index cache via teco_view_bytes2glyphs_unicode() since from+len + * is guaranteed to fall on line starts. + */ + if (teco_is_success(rc) && len < 0) + teco_current_doc_set_dot(teco_interface_bytes2glyphs_reldot(len)); } else { teco_int_t len_glyphs; if (!teco_expressions_pop_num_calc(&len_glyphs, teco_num_sign, error)) return FALSE; - gssize to = teco_interface_glyphs2bytes_relative(from, len_glyphs); + gssize to = teco_interface_glyphs2bytes_reldot(len_glyphs); rc = teco_bool(to >= 0); len = to-from; + + if (teco_is_success(rc) && len_glyphs < 0) + teco_current_doc_set_dot(teco_current_doc_get_dot() + len_glyphs); } if (len < 0) { len *= -1; @@ -510,11 +538,18 @@ teco_state_start_kill(teco_machine_main_t *ctx, const gchar *cmd, gboolean by_li } } else { teco_int_t to_glyphs = teco_expressions_pop_num(0); - gssize to = teco_interface_glyphs2bytes(to_glyphs); + gssize to = teco_interface_glyphs2bytes_absdot(to_glyphs); teco_int_t from_glyphs = teco_expressions_pop_num(0); - from = teco_interface_glyphs2bytes(from_glyphs); + from = teco_interface_glyphs2bytes_absdot(from_glyphs); len = to - from; rc = teco_bool(len >= 0 && from >= 0 && to >= 0); + + if (teco_is_success(rc)) { + if (to < pos) + teco_current_doc_set_dot(teco_current_doc_get_dot() - (to_glyphs-from_glyphs)); + else if (from < pos) + teco_current_doc_set_dot(from_glyphs); + } } if (teco_machine_main_eval_colon(ctx) > 0) { @@ -528,7 +563,6 @@ teco_state_start_kill(teco_machine_main_t *ctx, const gchar *cmd, gboolean by_li return TRUE; if (teco_current_doc_must_undo()) { - sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); undo__teco_interface_ssm(SCI_GOTOPOS, pos, 0); undo__teco_interface_ssm(SCI_UNDO, 0, 0); } @@ -629,7 +663,11 @@ teco_state_control_lines2glyphs(teco_machine_main_t *ctx, GError **error) if (!teco_expressions_args()) { pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); } else { - pos = teco_interface_glyphs2bytes(teco_expressions_pop_num(0)); + /* + * FIXME: Could be optimized by directly calling + * SCI_LINEFROMINDEXPOSITION. + */ + pos = teco_interface_glyphs2bytes_absdot(teco_expressions_pop_num(0)); if (pos < 0) { teco_error_range_set(error, "^Q"); return; @@ -652,6 +690,10 @@ teco_state_control_lines2glyphs(teco_machine_main_t *ctx, GError **error) } sptr_t line_pos = teco_interface_ssm(SCI_POSITIONFROMLINE, line, 0); - teco_expressions_push(teco_interface_bytes2glyphs(line_pos) - teco_interface_bytes2glyphs(pos)); + /* + * FIXME: Could be optimized with SCI_INDEXPOSITIONFROMLINE. + */ + teco_expressions_push(teco_interface_bytes2glyphs_absdot(line_pos) - + teco_current_doc_get_dot()); } } diff --git a/src/parser.c b/src/parser.c index 747249d..c84881b 100644 --- a/src/parser.c +++ b/src/parser.c @@ -123,7 +123,7 @@ teco_machine_main_step(teco_machine_main_t *ctx, const gchar *macro, gsize stop_ gunichar chr = g_utf8_get_char(macro+ctx->macro_pc); #ifdef DEBUG - g_printf("EXEC(%d): input='%C' (U+%04" G_GINT32_MODIFIER "X), state=%p, mode=%d\n", + g_printf("EXEC(%zu): input='%C' (U+%04" G_GINT32_MODIFIER "X), state=%p, mode=%d\n", ctx->macro_pc, chr, chr, ctx->parent.current, ctx->flags.mode); #endif diff --git a/src/qreg-commands.c b/src/qreg-commands.c index 4ede403..a31fe4c 100644 --- a/src/qreg-commands.c +++ b/src/qreg-commands.c @@ -572,9 +572,11 @@ teco_state_getqregstring_got_register(teco_machine_main_t *ctx, teco_qreg_t *qre undo__teco_interface_ssm(SCI_UNDO, 0, 0); } - teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos); - teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos + str.len); + teco_undo_int(teco_ranges[0].from) = teco_current_doc_get_dot(); + teco_int_t to = teco_interface_bytes2glyphs_rel(teco_ranges[0].from, pos, str.len); + teco_undo_int(teco_ranges[0].to) = to; teco_undo_guint(teco_ranges_count) = 1; + teco_current_doc_set_dot(to); return &teco_state_start; } @@ -798,9 +800,10 @@ teco_state_copytoqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; + teco_int_t from_glyphs, len_glyphs; gsize from, len; - if (!teco_get_range_args("X", &from, &len, error)) + if (!teco_get_range_args("X", &from_glyphs, &from, &len_glyphs, &len, error)) return NULL; /* @@ -829,11 +832,12 @@ teco_state_copytoqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, if (!modifier_at || len == 0) return &teco_state_start; + sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + /* * If @-modified, cut into the register */ if (teco_current_doc_must_undo()) { - sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); undo__teco_interface_ssm(SCI_GOTOPOS, pos, 0); undo__teco_interface_ssm(SCI_UNDO, 0, 0); } @@ -846,6 +850,13 @@ teco_state_copytoqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0); teco_ring_dirtify(); + if (from+len < pos) + /* dot after deleted range */ + teco_current_doc_set_dot(teco_current_doc_get_dot() - len_glyphs); + else if (from <= pos) + /* dot falls on deleted range */ + teco_current_doc_set_dot(from_glyphs); + return &teco_state_start; } @@ -237,8 +237,10 @@ teco_qreg_plain_get_character(teco_qreg_t *qreg, teco_int_t position, teco_doc_edit(&qreg->string, teco_default_codepage()); + sptr_t curpos = teco_view_ssm(teco_qreg_view, SCI_GETCURRENTPOS, 0, 0); sptr_t len = teco_view_ssm(teco_qreg_view, SCI_GETLENGTH, 0, 0); - gssize off = teco_view_glyphs2bytes(teco_qreg_view, position); + gssize off = teco_view_glyphs2bytes_rel(teco_qreg_view, qreg->string.dot, curpos, + position - qreg->string.dot); *chr = off >= 0 && off != len ? teco_view_get_character(teco_qreg_view, off, len) : -1; @@ -256,8 +258,10 @@ teco_qreg_plain_get_length(teco_qreg_t *qreg, GError **error) teco_doc_edit(&qreg->string, teco_default_codepage()); + sptr_t curpos = teco_view_ssm(teco_qreg_view, SCI_GETCURRENTPOS, 0, 0); sptr_t len = teco_view_ssm(teco_qreg_view, SCI_GETLENGTH, 0, 0); - teco_int_t ret = teco_view_bytes2glyphs(teco_qreg_view, len); + teco_int_t ret = teco_view_bytes2glyphs_rel(teco_qreg_view, qreg->string.dot, + curpos, len - curpos); if (teco_qreg_current) teco_doc_edit(&teco_qreg_current->string, 0); @@ -396,7 +400,7 @@ teco_qreg_plain_new(const gchar *name, gsize len) static gboolean teco_qreg_dot_set_integer(teco_qreg_t *qreg, teco_int_t value, GError **error) { - gssize pos = teco_interface_glyphs2bytes(value); + gssize pos = teco_interface_glyphs2bytes_absdot(value); if (pos < 0) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_MOVE, "Attempt to move pointer off page when setting Q-Register \":\""); @@ -404,6 +408,11 @@ teco_qreg_dot_set_integer(teco_qreg_t *qreg, teco_int_t value, GError **error) } teco_interface_ssm(SCI_GOTOPOS, pos, 0); + /* + * FIXME: Currently emits undo tokens. + * But we could just get rid of the undo_set_integer() callback. + */ + teco_current_doc_set_dot(value); return TRUE; } @@ -420,8 +429,7 @@ teco_qreg_dot_undo_set_integer(teco_qreg_t *qreg, GError **error) static gboolean teco_qreg_dot_get_integer(teco_qreg_t *qreg, teco_int_t *ret, GError **error) { - sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - *ret = teco_interface_bytes2glyphs(pos); + *ret = teco_current_doc_get_dot(); return TRUE; } @@ -50,6 +50,7 @@ extern teco_view_t *teco_qreg_view; */ typedef const struct { gboolean (*set_integer)(teco_qreg_t *qreg, teco_int_t value, GError **error); + /** @fixme We don't appear to need this as a separate callback */ gboolean (*undo_set_integer)(teco_qreg_t *qreg, GError **error); gboolean (*get_integer)(teco_qreg_t *qreg, teco_int_t *ret, GError **error); @@ -736,19 +736,24 @@ teco_state_read_file_done(teco_machine_main_t *ctx, teco_string_t str, GError ** if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; - sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos); + sptr_t from = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_undo_int(teco_ranges[0].from) = teco_current_doc_get_dot();; g_autofree gchar *filename = teco_file_expand_path(str.data); /* FIXME: Add wrapper to interface.h? */ if (!teco_view_load(teco_interface_current_view, filename, FALSE, error)) return NULL; - pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos); + /* + * FIXME: Perhaps teco_view_load() should return the number of glyphs added. + */ + sptr_t to = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs_rel(teco_ranges[0].from, + from, to-from); teco_undo_guint(teco_ranges_count) = 1; if (teco_ranges[0].from != teco_ranges[0].to) { + teco_current_doc_set_dot(teco_ranges[0].to); teco_ring_dirtify(); if (teco_current_doc_must_undo()) @@ -48,6 +48,12 @@ typedef struct teco_buffer_t { * This is still a guint, so you can call teco_undo_guint(). */ guint state; + + /** + * Dot in glyphs. + * This field is always kept up to date. + */ + teco_int_t dot; } teco_buffer_t; /** @memberof teco_buffer_t */ @@ -149,3 +155,23 @@ teco_current_doc_must_undo(void) */ return !teco_qreg_current || teco_qreg_current->must_undo; } + +/** update dot in the current document (Q-Register or buffer) */ +static inline void +teco_current_doc_set_dot(teco_int_t dot) +{ + if (teco_qreg_current) { + if (teco_qreg_current->must_undo) + teco_undo_int(teco_qreg_current->string.dot); + teco_qreg_current->string.dot = dot; + } else { + teco_undo_int(teco_ring_current->dot) = dot; + } +} + +static inline teco_int_t +teco_current_doc_get_dot(void) +{ + return teco_qreg_current ? teco_qreg_current->string.dot + : teco_ring_current->dot; +} diff --git a/src/search.c b/src/search.c index 1ab925c..accee69 100644 --- a/src/search.c +++ b/src/search.c @@ -131,12 +131,12 @@ teco_state_search_initial(teco_machine_main_t *ctx, GError **error) v1 = teco_expressions_pop_num(0); if (v1 <= v2) { teco_search_parameters.count = 1; - teco_search_parameters.from = teco_interface_glyphs2bytes(v1); - teco_search_parameters.to = teco_interface_glyphs2bytes(v2); + teco_search_parameters.from = teco_interface_glyphs2bytes_absdot(v1); + teco_search_parameters.to = teco_interface_glyphs2bytes_absdot(v2); } else { teco_search_parameters.count = -1; - teco_search_parameters.from = teco_interface_glyphs2bytes(v2); - teco_search_parameters.to = teco_interface_glyphs2bytes(v1); + teco_search_parameters.from = teco_interface_glyphs2bytes_absdot(v2); + teco_search_parameters.to = teco_interface_glyphs2bytes_absdot(v1); } if (teco_search_parameters.from < 0 || @@ -890,18 +890,21 @@ teco_do_search(regex_t *re, gsize from, gsize to, gint *count, GError **error) /* match success */ g_assert(teco_ranges_count > 0); - teco_interface_ssm(SCI_SETSEL, teco_ranges[0].from, teco_ranges[0].to); + gsize from = teco_ranges[0].from, to = teco_ranges[0].to; /* * teco_get_ranges() returned byte positions, * while everything else expects glyph offsets. * They are fixed up only now to avoid unnecessary - * possibly costly teco_interface_bytes2glyphs() calls. + * possibly costly teco_interface_bytes2glyphs_absdot() calls. */ for (guint i = 0; i < teco_ranges_count; i++) { - teco_ranges[i].from = teco_interface_bytes2glyphs(teco_ranges[i].from); - teco_ranges[i].to = teco_interface_bytes2glyphs(teco_ranges[i].to); + teco_ranges[i].from = teco_interface_bytes2glyphs_absdot(teco_ranges[i].from); + teco_ranges[i].to = teco_interface_bytes2glyphs_absdot(teco_ranges[i].to); } + + teco_interface_ssm(SCI_SETSEL, from, to); + teco_current_doc_set_dot(teco_ranges[0].to); } return TRUE; @@ -940,6 +943,7 @@ teco_state_search_process(teco_machine_main_t *ctx, teco_string_t str, gsize new /* * While SciTECO code is always guaranteed to be in valid UTF-8, * the result of string building may not (eg. if ^EQq inserts garbage). + * terex does not currently reliably detect invalid UTF-8 sequences. */ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CODEPOINT, "Invalid UTF-8 byte sequence in search pattern"); @@ -1333,8 +1337,8 @@ teco_state_search_kill_done(teco_machine_main_t *ctx, teco_string_t str, GError if (teco_search_parameters.dot < dot) { /* kill forwards */ sptr_t anchor = teco_interface_ssm(SCI_GETANCHOR, 0, 0); - teco_int_t len_glyphs = teco_interface_bytes2glyphs(anchor) - - teco_interface_bytes2glyphs(teco_search_parameters.dot); + teco_int_t len_glyphs = teco_interface_bytes2glyphs_absdot(anchor) - + teco_interface_bytes2glyphs_absdot(teco_search_parameters.dot); if (teco_current_doc_must_undo()) undo__teco_interface_ssm(SCI_GOTOPOS, dot, 0); @@ -1353,8 +1357,13 @@ teco_state_search_kill_done(teco_machine_main_t *ctx, teco_string_t str, GError teco_ranges[i].from -= len_glyphs; teco_ranges[i].to -= len_glyphs; } + + g_assert(teco_ranges_count > 0); + teco_current_doc_set_dot(teco_ranges[0].from); } else { /* kill backwards */ + teco_current_doc_set_dot(teco_interface_bytes2glyphs_absdot(dot)); + teco_interface_ssm(SCI_DELETERANGE, dot, teco_search_parameters.dot - dot); /* NOTE: An undo action is not always created. */ @@ -1409,6 +1418,9 @@ teco_state_search_delete_done(teco_machine_main_t *ctx, teco_string_t str, GErro return NULL; if (teco_is_success(search_state)) { + sptr_t anchor = teco_interface_ssm(SCI_GETANCHOR, 0, 0); + teco_current_doc_set_dot(teco_interface_bytes2glyphs_absdot(anchor)); + teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0); teco_interface_ssm(SCI_REPLACESEL, 0, (sptr_t)""); teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0); @@ -1450,8 +1462,8 @@ teco_state_replace_insert_initial(teco_machine_main_t *ctx, GError **error) * FIXME: Wastes undo tokens in teco_do_search(). * Perhaps make this configurable in the state. */ - sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos); + teco_undo_int(teco_ranges[0].from) = teco_current_doc_get_dot(); + teco_undo_int(teco_ranges[0].to) = teco_ranges[0].from; teco_undo_guint(teco_ranges_count) = 1; /* @@ -1531,7 +1543,8 @@ teco_state_replace_default_insert_done(teco_machine_main_t *ctx, teco_string_t s teco_qreg_t *replace_reg = teco_qreg_table_find(&teco_qreg_table_globals, "-", 1); g_assert(replace_reg != NULL); - if (str.len > 0) { + gsize len = str.len; + if (len > 0) { if (!replace_reg->vtable->undo_set_string(replace_reg, error) || !replace_reg->vtable->set_string(replace_reg, str.data, str.len, teco_default_codepage(), error)) @@ -1542,10 +1555,18 @@ teco_state_replace_default_insert_done(teco_machine_main_t *ctx, teco_string_t s NULL, error) || (replace_str.len > 0 && !teco_state_insert_process(ctx, replace_str, replace_str.len, error))) return NULL; + len = replace_str.len; } + /* + * NOTE: teco_interface_bytes2glyphs_reldot() would count relative to + * dot, which is still invalid. + */ sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos); + teco_int_t to = teco_interface_bytes2glyphs_rel(teco_ranges[0].to, pos-len, len); + teco_undo_int(teco_ranges[0].to) = to; + teco_current_doc_set_dot(to); + return &teco_state_start; } diff --git a/src/spawn.c b/src/spawn.c index 0492bbf..b1cd305 100644 --- a/src/spawn.c +++ b/src/spawn.c @@ -222,8 +222,8 @@ teco_state_execute_initial(teco_machine_main_t *ctx, GError **error) default: /* pipe and replace character range */ - teco_spawn_ctx.to = teco_interface_glyphs2bytes(teco_expressions_pop_num(0)); - teco_spawn_ctx.from = teco_interface_glyphs2bytes(teco_expressions_pop_num(0)); + teco_spawn_ctx.to = teco_interface_glyphs2bytes_absdot(teco_expressions_pop_num(0)); + teco_spawn_ctx.from = teco_interface_glyphs2bytes_absdot(teco_expressions_pop_num(0)); rc = teco_bool(teco_spawn_ctx.from <= teco_spawn_ctx.to && teco_spawn_ctx.from >= 0 && teco_spawn_ctx.to >= 0); } @@ -424,6 +424,8 @@ teco_state_execute_done(teco_machine_main_t *ctx, teco_string_t str, GError **er g_source_attach(teco_spawn_ctx.stdout_src, teco_spawn_ctx.mainctx); if (!teco_spawn_ctx.register_argument) { + teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs_absdot(teco_spawn_ctx.from); + if (teco_current_doc_must_undo()) undo__teco_interface_ssm(SCI_GOTOPOS, teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0), 0); @@ -438,9 +440,12 @@ teco_state_execute_done(teco_machine_main_t *ctx, teco_string_t str, GError **er teco_spawn_ctx.to - teco_spawn_ctx.from); sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(teco_spawn_ctx.from); - teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos); + teco_int_t to = teco_interface_bytes2glyphs_rel(teco_ranges[0].from, teco_spawn_ctx.from, + pos - teco_spawn_ctx.from); + teco_undo_int(teco_ranges[0].to) = to; teco_undo_guint(teco_ranges_count) = 1; + + teco_current_doc_set_dot(to); } teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0); diff --git a/src/stdio-commands.c b/src/stdio-commands.c index c2a1c16..34179c3 100644 --- a/src/stdio-commands.c +++ b/src/stdio-commands.c @@ -334,7 +334,7 @@ teco_state_start_typeout(teco_machine_main_t *ctx, GError **error) { gsize from, len; - if (!teco_get_range_args("T", &from, &len, error)) + if (!teco_get_range_args("T", NULL, &from, NULL, &len, error)) return; /* @@ -644,27 +644,19 @@ teco_view_save_to_stdout(teco_view_t *ctx, GError **error) /** * Convert a glyph index to a byte offset as used by Scintilla. * - * This is optimized with the "line character index", + * This function is only for UTF-8 documents. + * It is optimized with the "line character index", * which must always be enabled in UTF-8 documents. * - * It is also used to validate glyph indexes. - * * @param ctx The view to operate on. * @param pos Position in glyphs/characters. * @return Position in bytes or -1 if pos is out of bounds. */ -gssize -teco_view_glyphs2bytes(teco_view_t *ctx, teco_int_t pos) +static gssize +teco_view_glyphs2bytes_unicode(teco_view_t *ctx, teco_int_t pos) { if (pos < 0) - return -1; /* invalid position */ - if (!pos) - return 0; - - if (!(teco_view_ssm(ctx, SCI_GETLINECHARACTERINDEX, 0, 0) & - SC_LINECHARACTERINDEX_UTF32)) - /* assume single-byte encoding */ - return pos <= teco_view_ssm(ctx, SCI_GETLENGTH, 0, 0) ? pos : -1; + return -1; sptr_t line = teco_view_ssm(ctx, SCI_LINEFROMINDEXPOSITION, pos, SC_LINECHARACTERINDEX_UTF32); @@ -675,19 +667,16 @@ teco_view_glyphs2bytes(teco_view_t *ctx, teco_int_t pos) } /** - * Convert byte offset to glyph/character index without bounds checking. + * Convert a bytes index to a glyph offset. + * + * This function is only for UTF-8 documents. + * It is optimized with the "line character index", + * which must always be enabled in UTF-8 documents. + * This function does \b not check for invalid positions. */ -teco_int_t -teco_view_bytes2glyphs(teco_view_t *ctx, gsize pos) +static teco_int_t +teco_view_bytes2glyphs_unicode(teco_view_t *ctx, gsize pos) { - if (!pos) - return 0; - - if (!(teco_view_ssm(ctx, SCI_GETLINECHARACTERINDEX, 0, 0) & - SC_LINECHARACTERINDEX_UTF32)) - /* assume single-byte encoding */ - return pos; - sptr_t line = teco_view_ssm(ctx, SCI_LINEFROMPOSITION, pos, 0); sptr_t line_bytes = teco_view_ssm(ctx, SCI_POSITIONFROMLINE, line, 0); return teco_view_ssm(ctx, SCI_INDEXPOSITIONFROMLINE, line, @@ -698,7 +687,7 @@ teco_view_bytes2glyphs(teco_view_t *ctx, gsize pos) #define TECO_RELATIVE_LIMIT 1024 /** - * Convert a glyph index relative to a byte position to + * Convert a glyph index relative to a position to * a byte position. * * Can be used to implement commands with relative character @@ -709,23 +698,87 @@ teco_view_bytes2glyphs(teco_view_t *ctx, gsize pos) * (as on exceedingly long lines). * * @param ctx The view to operate on. + * @param pos_glyphs Glyph position to start. + * It must be the same position as specified by pos. * @param pos Byte position to start. * @param n Number of glyphs/characters to the left (negative) or - * right (positive) of pos. + * right (positive) of pos_glyphs and pos. * @return Position in bytes or -1 if the resulting position is out of bounds. */ gssize -teco_view_glyphs2bytes_relative(teco_view_t *ctx, gsize pos, teco_int_t n) +teco_view_glyphs2bytes_rel(teco_view_t *ctx, teco_int_t pos_glyphs, gsize pos, teco_int_t n) { - if (!n) + if (n == 0) return pos; + if (pos_glyphs == -n) + return 0; + + if (!(teco_view_ssm(ctx, SCI_GETLINECHARACTERINDEX, 0, 0) & + SC_LINECHARACTERINDEX_UTF32)) { + /* assume single-byte encoding */ + g_assert(pos_glyphs == pos); + + sptr_t len = teco_view_ssm(ctx, SCI_GETLENGTH, 0, 0); + if (0 > (gssize)pos+n || (gssize)pos+n > len) + return -1; + + return pos+n; + } + /* NOTE: Does not work for n == G_MININT64. */ if (ABS(n) > TECO_RELATIVE_LIMIT) - return teco_view_glyphs2bytes(ctx, teco_view_bytes2glyphs(ctx, pos) + n); + return teco_view_glyphs2bytes_unicode(ctx, pos_glyphs+n); sptr_t res = teco_view_ssm(ctx, SCI_POSITIONRELATIVE, pos, n); /* SCI_POSITIONRELATIVE may return 0 even if the offset is valid */ - return res ? : n > 0 ? -1 : teco_view_bytes2glyphs(ctx, pos)+n >= 0 ? 0 : -1; + return res ? : n > 0 ? -1 : pos_glyphs + n >= 0 ? 0 : -1; +} + +/** + * Convert a byte index relative to a known position to + * a glyph position. + * + * Can be used to implement commands with relative character + * ranges. + * As an optimization, this always counts characters for deltas + * smaller than TECO_RELATIVE_LIMIT, so it will be fast + * even where the character-index based lookup is too slow + * (as on exceedingly long lines). + * + * @param ctx The view to operate on. + * @param pos_glyphs Glyph position to start. + * It must be the same position as specified by pos. + * @param pos Byte position to start. + * @param n Number of bytes to the left (negative) or + * right (positive) of pos_glyphs and pos. + * @return Position in glyphs or -1 if the resulting position is out of bounds. + */ +teco_int_t +teco_view_bytes2glyphs_rel(teco_view_t *ctx, teco_int_t pos_glyphs, gsize pos, gssize n) +{ + if (n == 0) + return pos_glyphs; + if (pos == -n) + return 0; + + sptr_t len = teco_view_ssm(ctx, SCI_GETLENGTH, 0, 0); + + if (0 > (gssize)pos+n || (gssize)pos+n > len) + return -1; + + if (!(teco_view_ssm(ctx, SCI_GETLINECHARACTERINDEX, 0, 0) & + SC_LINECHARACTERINDEX_UTF32)) { + /* assume single-byte encoding */ + g_assert(pos_glyphs == pos); + return pos_glyphs+n; + } + + /* NOTE: Does not work for n == G_MINSSIZE. */ + if (ABS(n) > TECO_RELATIVE_LIMIT) + return teco_view_bytes2glyphs_unicode(ctx, pos+n); + + return n < 0 ? pos_glyphs - teco_view_ssm(ctx, SCI_COUNTCHARACTERS, pos+n, pos) + : pos_glyphs + teco_view_ssm(ctx, SCI_COUNTCHARACTERS, pos, pos+n); } /** @@ -85,9 +85,10 @@ teco_view_get_codepage(teco_view_t *ctx) ? : teco_view_ssm(ctx, SCI_STYLEGETCHARACTERSET, STYLE_DEFAULT, 0); } -gssize teco_view_glyphs2bytes(teco_view_t *ctx, teco_int_t pos); -teco_int_t teco_view_bytes2glyphs(teco_view_t *ctx, gsize pos); -gssize teco_view_glyphs2bytes_relative(teco_view_t *ctx, gsize pos, teco_int_t n); +gssize teco_view_glyphs2bytes_rel(teco_view_t *ctx, teco_int_t pos_glyphs, + gsize pos, teco_int_t n); +teco_int_t teco_view_bytes2glyphs_rel(teco_view_t *ctx, teco_int_t pos_glyphs, + gsize pos, gssize n); teco_int_t teco_view_get_character(teco_view_t *ctx, gsize pos, gsize len); |
