remote-hg: test: simplify previous branch checkout
[gitweb.git] / utf8.c
diff --git a/utf8.c b/utf8.c
index 1087870c51caff3dd86a852ec5e8bf5875f6d797..0d20e0acb2b6fb2dd1d63abed676a036b2b7ec2f 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -9,6 +9,20 @@ struct interval {
   int last;
 };
 
+size_t display_mode_esc_sequence_len(const char *s)
+{
+       const char *p = s;
+       if (*p++ != '\033')
+               return 0;
+       if (*p++ != '[')
+               return 0;
+       while (isdigit(*p) || *p == ';')
+               p++;
+       if (*p++ != 'm')
+               return 0;
+       return p - s;
+}
+
 /* auxiliary function for binary search in interval table */
 static int bisearch(ucs_char_t ucs, const struct interval *table, int max)
 {
@@ -252,18 +266,26 @@ int utf8_width(const char **start, size_t *remainder_p)
  * string, assuming that the string is utf8.  Returns strlen() instead
  * if the string does not look like a valid utf8 string.
  */
-int utf8_strwidth(const char *string)
+int utf8_strnwidth(const char *string, int len, int skip_ansi)
 {
        int width = 0;
        const char *orig = string;
 
-       while (1) {
-               if (!string)
-                       return strlen(orig);
-               if (!*string)
-                       return width;
+       if (len == -1)
+               len = strlen(string);
+       while (string && string < orig + len) {
+               int skip;
+               while (skip_ansi &&
+                      (skip = display_mode_esc_sequence_len(string)) != 0)
+                       string += skip;
                width += utf8_width(&string, NULL);
        }
+       return string ? width : len;
+}
+
+int utf8_strwidth(const char *string)
+{
+       return utf8_strnwidth(string, -1, 0);
 }
 
 int is_utf8(const char *text)
@@ -303,20 +325,6 @@ static void strbuf_add_indented_text(struct strbuf *buf, const char *text,
        }
 }
 
-static size_t display_mode_esc_sequence_len(const char *s)
-{
-       const char *p = s;
-       if (*p++ != '\033')
-               return 0;
-       if (*p++ != '[')
-               return 0;
-       while (isdigit(*p) || *p == ';')
-               p++;
-       if (*p++ != 'm')
-               return 0;
-       return p - s;
-}
-
 /*
  * Wrap the text, if necessary. The variable indent is the indent for the
  * first line, indent2 is the indent for all other lines.
@@ -413,6 +421,52 @@ void strbuf_add_wrapped_bytes(struct strbuf *buf, const char *data, int len,
        free(tmp);
 }
 
+void strbuf_utf8_replace(struct strbuf *sb_src, int pos, int width,
+                        const char *subst)
+{
+       struct strbuf sb_dst = STRBUF_INIT;
+       char *src = sb_src->buf;
+       char *end = src + sb_src->len;
+       char *dst;
+       int w = 0, subst_len = 0;
+
+       if (subst)
+               subst_len = strlen(subst);
+       strbuf_grow(&sb_dst, sb_src->len + subst_len);
+       dst = sb_dst.buf;
+
+       while (src < end) {
+               char *old;
+               size_t n;
+
+               while ((n = display_mode_esc_sequence_len(src))) {
+                       memcpy(dst, src, n);
+                       src += n;
+                       dst += n;
+               }
+
+               old = src;
+               n = utf8_width((const char**)&src, NULL);
+               if (!src)       /* broken utf-8, do nothing */
+                       return;
+               if (n && w >= pos && w < pos + width) {
+                       if (subst) {
+                               memcpy(dst, subst, subst_len);
+                               dst += subst_len;
+                               subst = NULL;
+                       }
+                       w += n;
+                       continue;
+               }
+               memcpy(dst, old, src - old);
+               dst += src - old;
+               w += n;
+       }
+       strbuf_setlen(&sb_dst, dst - sb_dst.buf);
+       strbuf_swap(sb_src, &sb_dst);
+       strbuf_release(&sb_dst);
+}
+
 int is_encoding_utf8(const char *name)
 {
        if (!name)
@@ -460,7 +514,7 @@ int utf8_fprintf(FILE *stream, const char *format, ...)
 #else
        typedef char * iconv_ibp;
 #endif
-char *reencode_string_iconv(const char *in, size_t insz, iconv_t conv)
+char *reencode_string_iconv(const char *in, size_t insz, iconv_t conv, int *outsz_p)
 {
        size_t outsz, outalloc;
        char *out, *outpos;
@@ -494,24 +548,83 @@ char *reencode_string_iconv(const char *in, size_t insz, iconv_t conv)
                }
                else {
                        *outpos = '\0';
+                       if (outsz_p)
+                               *outsz_p = outpos - out;
                        break;
                }
        }
        return out;
 }
 
-char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding)
+char *reencode_string_len(const char *in, int insz,
+                         const char *out_encoding, const char *in_encoding,
+                         int *outsz)
 {
        iconv_t conv;
        char *out;
 
        if (!in_encoding)
                return NULL;
+
        conv = iconv_open(out_encoding, in_encoding);
-       if (conv == (iconv_t) -1)
-               return NULL;
-       out = reencode_string_iconv(in, strlen(in), conv);
+       if (conv == (iconv_t) -1) {
+               /*
+                * Some platforms do not have the variously spelled variants of
+                * UTF-8, so let's fall back to trying the most official
+                * spelling. We do so only as a fallback in case the platform
+                * does understand the user's spelling, but not our official
+                * one.
+                */
+               if (is_encoding_utf8(in_encoding))
+                       in_encoding = "UTF-8";
+               if (is_encoding_utf8(out_encoding))
+                       out_encoding = "UTF-8";
+               conv = iconv_open(out_encoding, in_encoding);
+               if (conv == (iconv_t) -1)
+                       return NULL;
+       }
+
+       out = reencode_string_iconv(in, insz, conv, outsz);
        iconv_close(conv);
        return out;
 }
 #endif
+
+/*
+ * Returns first character length in bytes for multi-byte `text` according to
+ * `encoding`.
+ *
+ * - The `text` pointer is updated to point at the next character.
+ * - When `remainder_p` is not NULL, on entry `*remainder_p` is how much bytes
+ *   we can consume from text, and on exit `*remainder_p` is reduced by returned
+ *   character length. Otherwise `text` is treated as limited by NUL.
+ */
+int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding)
+{
+       int chrlen;
+       const char *p = *text;
+       size_t r = (remainder_p ? *remainder_p : SIZE_MAX);
+
+       if (r < 1)
+               return 0;
+
+       if (is_encoding_utf8(encoding)) {
+               pick_one_utf8_char(&p, &r);
+
+               chrlen = p ? (p - *text)
+                          : 1 /* not valid UTF-8 -> raw byte sequence */;
+       }
+       else {
+               /*
+                * TODO use iconv to decode one char and obtain its chrlen
+                * for now, let's treat encodings != UTF-8 as one-byte
+                */
+               chrlen = 1;
+       }
+
+       *text += chrlen;
+       if (remainder_p)
+               *remainder_p -= chrlen;
+
+       return chrlen;
+}