patch 8.2.2318: Vim9: string and list index work differently
Problem: Vim9: string and list index work differently.
Solution: Make string index work like list index. (closes #7643)
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 9a76cc9..e7a632a 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -863,6 +863,108 @@
}
}
+/*
+ * Return the character "str[index]" where "index" is the character index. If
+ * "index" is out of range NULL is returned.
+ */
+ char_u *
+char_from_string(char_u *str, varnumber_T index)
+{
+ size_t nbyte = 0;
+ varnumber_T nchar = index;
+ size_t slen;
+
+ if (str == NULL)
+ return NULL;
+ slen = STRLEN(str);
+
+ // do the same as for a list: a negative index counts from the end
+ if (index < 0)
+ {
+ int clen = 0;
+
+ for (nbyte = 0; nbyte < slen; ++clen)
+ nbyte += MB_CPTR2LEN(str + nbyte);
+ nchar = clen + index;
+ if (nchar < 0)
+ // unlike list: index out of range results in empty string
+ return NULL;
+ }
+
+ for (nbyte = 0; nchar > 0 && nbyte < slen; --nchar)
+ nbyte += MB_CPTR2LEN(str + nbyte);
+ if (nbyte >= slen)
+ return NULL;
+ return vim_strnsave(str + nbyte, MB_CPTR2LEN(str + nbyte));
+}
+
+/*
+ * Get the byte index for character index "idx" in string "str" with length
+ * "str_len".
+ * If going over the end return "str_len".
+ * If "idx" is negative count from the end, -1 is the last character.
+ * When going over the start return -1.
+ */
+ static long
+char_idx2byte(char_u *str, size_t str_len, varnumber_T idx)
+{
+ varnumber_T nchar = idx;
+ size_t nbyte = 0;
+
+ if (nchar >= 0)
+ {
+ while (nchar > 0 && nbyte < str_len)
+ {
+ nbyte += MB_CPTR2LEN(str + nbyte);
+ --nchar;
+ }
+ }
+ else
+ {
+ nbyte = str_len;
+ while (nchar < 0 && nbyte > 0)
+ {
+ --nbyte;
+ nbyte -= mb_head_off(str, str + nbyte);
+ ++nchar;
+ }
+ if (nchar < 0)
+ return -1;
+ }
+ return (long)nbyte;
+}
+
+/*
+ * Return the slice "str[first:last]" using character indexes.
+ * Return NULL when the result is empty.
+ */
+ char_u *
+string_slice(char_u *str, varnumber_T first, varnumber_T last)
+{
+ long start_byte, end_byte;
+ size_t slen;
+
+ if (str == NULL)
+ return NULL;
+ slen = STRLEN(str);
+ start_byte = char_idx2byte(str, slen, first);
+ if (start_byte < 0)
+ start_byte = 0; // first index very negative: use zero
+ if (last == -1)
+ end_byte = (long)slen;
+ else
+ {
+ end_byte = char_idx2byte(str, slen, last);
+ if (end_byte >= 0 && end_byte < (long)slen)
+ // end index is inclusive
+ end_byte += MB_CPTR2LEN(str + end_byte);
+ }
+
+ if (start_byte >= (long)slen || end_byte <= start_byte)
+ return NULL;
+ return vim_strnsave(str + start_byte, end_byte - start_byte);
+}
+
static svar_T *
get_script_svar(scriptref_T *sref, ectx_T *ectx)
{