Imported Upstream version 2.0.1
[platform/upstream/git.git] / utf8.c
diff --git a/utf8.c b/utf8.c
index a4ee665..77c28d4 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -5,10 +5,24 @@
 /* This code is originally from http://www.cl.cam.ac.uk/~mgk25/ucs/ */
 
 struct interval {
-  int first;
-  int last;
+       ucs_char_t first;
+       ucs_char_t 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)
 {
@@ -70,11 +84,10 @@ static int git_wcwidth(ucs_char_t ch)
         *   "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c".
         */
        static const struct interval combining[] = {
-               { 0x0300, 0x0357 }, { 0x035D, 0x036F }, { 0x0483, 0x0486 },
-               { 0x0488, 0x0489 }, { 0x0591, 0x05A1 }, { 0x05A3, 0x05B9 },
-               { 0x05BB, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
-               { 0x05C4, 0x05C4 }, { 0x0600, 0x0603 }, { 0x0610, 0x0615 },
-               { 0x064B, 0x0658 }, { 0x0670, 0x0670 }, { 0x06D6, 0x06E4 },
+               { 0x0300, 0x036F }, { 0x0483, 0x0489 }, { 0x0591, 0x05BD },
+               { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 },
+               { 0x05C7, 0x05C7 }, { 0x0600, 0x0604 }, { 0x0610, 0x061A },
+               { 0x064B, 0x065F }, { 0x0670, 0x0670 }, { 0x06D6, 0x06E4 },
                { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F },
                { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 },
                { 0x0901, 0x0902 }, { 0x093C, 0x093C }, { 0x0941, 0x0948 },
@@ -252,18 +265,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 +324,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 +420,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)
@@ -430,6 +483,27 @@ int same_encoding(const char *src, const char *dst)
 }
 
 /*
+ * Wrapper for fprintf and returns the total number of columns required
+ * for the printed string, assuming that the string is utf8.
+ */
+int utf8_fprintf(FILE *stream, const char *format, ...)
+{
+       struct strbuf buf = STRBUF_INIT;
+       va_list arg;
+       int columns;
+
+       va_start(arg, format);
+       strbuf_vaddf(&buf, format, arg);
+       va_end(arg);
+
+       columns = fputs(buf.buf, stream);
+       if (0 <= columns) /* keep the error from the I/O */
+               columns = utf8_strwidth(buf.buf);
+       strbuf_release(&buf);
+       return columns;
+}
+
+/*
  * Given a buffer and its encoding, return it re-encoded
  * with iconv.  If the conversion fails, returns NULL.
  */
@@ -439,7 +513,7 @@ int same_encoding(const char *src, const char *dst)
 #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;
@@ -454,7 +528,7 @@ char *reencode_string_iconv(const char *in, size_t insz, iconv_t conv)
        while (1) {
                size_t cnt = iconv(conv, &cp, &insz, &outpos, &outsz);
 
-               if (cnt == -1) {
+               if (cnt == (size_t) -1) {
                        size_t sofar;
                        if (errno != E2BIG) {
                                free(out);
@@ -473,24 +547,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;
+}