patch 8.2.2344: using inclusive index for slice is not always desired

Problem:    Using inclusive index for slice is not always desired.
Solution:   Add the slice() method, which has an exclusive index. (closes
            #7408)
diff --git a/src/eval.c b/src/eval.c
index 8115c7c..84f6c2b 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -3877,8 +3877,9 @@
     if (evaluate)
     {
 	int res = eval_index_inner(rettv, range,
-		empty1 ? NULL : &var1, empty2 ? NULL : &var2,
+		empty1 ? NULL : &var1, empty2 ? NULL : &var2, FALSE,
 		key, keylen, verbose);
+
 	if (!empty1)
 	    clear_tv(&var1);
 	if (range)
@@ -3938,9 +3939,26 @@
 }
 
 /*
+ * slice() function
+ */
+    void
+f_slice(typval_T *argvars, typval_T *rettv)
+{
+    if (check_can_index(argvars, TRUE, FALSE) == OK)
+    {
+	copy_tv(argvars, rettv);
+	eval_index_inner(rettv, TRUE, argvars + 1,
+		argvars[2].v_type == VAR_UNKNOWN ? NULL : argvars + 2,
+		TRUE, NULL, 0, FALSE);
+    }
+}
+
+/*
  * Apply index or range to "rettv".
  * "var1" is the first index, NULL for [:expr].
  * "var2" is the second index, NULL for [expr] and [expr: ]
+ * "exclusive" is TRUE for slice(): second index is exclusive, use character
+ * index for string.
  * Alternatively, "key" is not NULL, then key[keylen] is the dict index.
  */
     int
@@ -3949,12 +3967,13 @@
 	int	    is_range,
 	typval_T    *var1,
 	typval_T    *var2,
+	int	    exclusive,
 	char_u	    *key,
 	int	    keylen,
 	int	    verbose)
 {
-    long	n1, n2 = 0;
-    long	len;
+    varnumber_T	    n1, n2 = 0;
+    long	    len;
 
     n1 = 0;
     if (var1 != NULL && rettv->v_type != VAR_DICT)
@@ -3968,10 +3987,10 @@
 		emsg(_(e_cannot_slice_dictionary));
 	    return FAIL;
 	}
-	if (var2 == NULL)
-	    n2 = -1;
-	else
+	if (var2 != NULL)
 	    n2 = tv_get_number(var2);
+	else
+	    n2 = VARNUM_MAX;
     }
 
     switch (rettv->v_type)
@@ -3994,10 +4013,10 @@
 		char_u	*s = tv_get_string(rettv);
 
 		len = (long)STRLEN(s);
-		if (in_vim9script())
+		if (in_vim9script() || exclusive)
 		{
 		    if (is_range)
-			s = string_slice(s, n1, n2);
+			s = string_slice(s, n1, n2, exclusive);
 		    else
 			s = char_from_string(s, n1);
 		}
@@ -4015,6 +4034,8 @@
 			n2 = len + n2;
 		    else if (n2 >= len)
 			n2 = len;
+		    if (exclusive)
+			--n2;
 		    if (n1 >= len || n2 < 0 || n1 > n2)
 			s = NULL;
 		    else
@@ -4051,7 +4072,9 @@
 		if (n2 < 0)
 		    n2 = len + n2;
 		else if (n2 >= len)
-		    n2 = len - 1;
+		    n2 = len - (exclusive ? 0 : 1);
+		if (exclusive)
+		    --n2;
 		if (n1 >= len || n2 < 0 || n1 > n2)
 		{
 		    clear_tv(rettv);
@@ -4103,9 +4126,9 @@
 	    if (var1 == NULL)
 		n1 = 0;
 	    if (var2 == NULL)
-		n2 = -1;
+		n2 = VARNUM_MAX;
 	    if (list_slice_or_index(rettv->vval.v_list,
-				    is_range, n1, n2, rettv, verbose) == FAIL)
+			  is_range, n1, n2, exclusive, rettv, verbose) == FAIL)
 		return FAIL;
 	    break;
 
diff --git a/src/evalfunc.c b/src/evalfunc.c
index a891378..0e800c2 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -1500,6 +1500,8 @@
 			ret_float,	    FLOAT_FUNC(f_sin)},
     {"sinh",		1, 1, FEARG_1,	    NULL,
 			ret_float,	    FLOAT_FUNC(f_sinh)},
+    {"slice",		2, 3, FEARG_1,	    NULL,
+			ret_first_arg,	    f_slice},
     {"sort",		1, 3, FEARG_1,	    NULL,
 			ret_first_arg,	    f_sort},
     {"sound_clear",	0, 0, 0,	    NULL,
diff --git a/src/list.c b/src/list.c
index f7842fa..0bca0b5 100644
--- a/src/list.c
+++ b/src/list.c
@@ -905,14 +905,15 @@
 list_slice_or_index(
 	    list_T	*list,
 	    int		range,
-	    long	n1_arg,
-	    long	n2_arg,
+	    varnumber_T	n1_arg,
+	    varnumber_T	n2_arg,
+	    int		exclusive,
 	    typval_T	*rettv,
 	    int		verbose)
 {
     long	len = list_len(list);
-    long	n1 = n1_arg;
-    long	n2 = n2_arg;
+    varnumber_T	n1 = n1_arg;
+    varnumber_T	n2 = n2_arg;
     typval_T	var1;
 
     if (n1 < 0)
@@ -936,7 +937,9 @@
 	if (n2 < 0)
 	    n2 = len + n2;
 	else if (n2 >= len)
-	    n2 = len - 1;
+	    n2 = len - (exclusive ? 0 : 1);
+	if (exclusive)
+	    --n2;
 	if (n2 < 0 || n2 + 1 < n1)
 	    n2 = -1;
 	l = list_slice(list, n1, n2);
diff --git a/src/proto/eval.pro b/src/proto/eval.pro
index 66aa430..f611a0e 100644
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -41,7 +41,8 @@
 int eval_addlist(typval_T *tv1, typval_T *tv2);
 int eval_leader(char_u **arg, int vim9);
 int check_can_index(typval_T *rettv, int evaluate, int verbose);
-int eval_index_inner(typval_T *rettv, int is_range, typval_T *var1, typval_T *var2, char_u *key, int keylen, int verbose);
+void f_slice(typval_T *argvars, typval_T *rettv);
+int eval_index_inner(typval_T *rettv, int is_range, typval_T *var1, typval_T *var2, int exclusive, char_u *key, int keylen, int verbose);
 char_u *partial_name(partial_T *pt);
 void partial_unref(partial_T *pt);
 int get_copyID(void);
@@ -58,7 +59,7 @@
 int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx);
 int buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx);
 pos_T *var2fpos(typval_T *varp, int dollar_lnum, int *fnum, int charcol);
-int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, int char_col);
+int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, int charcol);
 int get_env_len(char_u **arg);
 int get_id_len(char_u **arg);
 int get_name_len(char_u **arg, char_u **alias, int evaluate, int verbose);
diff --git a/src/proto/list.pro b/src/proto/list.pro
index b77add5..7c9ddae 100644
--- a/src/proto/list.pro
+++ b/src/proto/list.pro
@@ -34,7 +34,7 @@
 int list_extend(list_T *l1, list_T *l2, listitem_T *bef);
 int list_concat(list_T *l1, list_T *l2, typval_T *tv);
 list_T *list_slice(list_T *ol, long n1, long n2);
-int list_slice_or_index(list_T *list, int range, long n1_arg, long n2_arg, typval_T *rettv, int verbose);
+int list_slice_or_index(list_T *list, int range, varnumber_T n1_arg, varnumber_T n2_arg, int exclusive, typval_T *rettv, int verbose);
 list_T *list_copy(list_T *orig, int deep, int copyID);
 void vimlist_remove(list_T *l, listitem_T *item, listitem_T *item2);
 char_u *list2string(typval_T *tv, int copyID, int restore_copyID);
diff --git a/src/proto/vim9execute.pro b/src/proto/vim9execute.pro
index 2337a6c..2124282 100644
--- a/src/proto/vim9execute.pro
+++ b/src/proto/vim9execute.pro
@@ -2,7 +2,7 @@
 void to_string_error(vartype_T vartype);
 void funcstack_check_refcount(funcstack_T *funcstack);
 char_u *char_from_string(char_u *str, varnumber_T index);
-char_u *string_slice(char_u *str, varnumber_T first, varnumber_T last);
+char_u *string_slice(char_u *str, varnumber_T first, varnumber_T last, int exclusive);
 int fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx);
 int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, typval_T *rettv);
 void ex_disassemble(exarg_T *eap);
diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim
index 3d474f3..f1c5de1 100644
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -741,6 +741,29 @@
   getreginfo('a')->assert_equal(reginfo)
 enddef 
 
+def Test_slice()
+  assert_equal('12345', slice('012345', 1))
+  assert_equal('123', slice('012345', 1, 4))
+  assert_equal('1234', slice('012345', 1, -1))
+  assert_equal('1', slice('012345', 1, -4))
+  assert_equal('', slice('012345', 1, -5))
+  assert_equal('', slice('012345', 1, -6))
+
+  assert_equal([1, 2, 3, 4, 5], slice(range(6), 1))
+  assert_equal([1, 2, 3], slice(range(6), 1, 4))
+  assert_equal([1, 2, 3, 4], slice(range(6), 1, -1))
+  assert_equal([1], slice(range(6), 1, -4))
+  assert_equal([], slice(range(6), 1, -5))
+  assert_equal([], slice(range(6), 1, -6))
+
+  assert_equal(0z1122334455, slice(0z001122334455, 1))
+  assert_equal(0z112233, slice(0z001122334455, 1, 4))
+  assert_equal(0z11223344, slice(0z001122334455, 1, -1))
+  assert_equal(0z11, slice(0z001122334455, 1, -4))
+  assert_equal(0z, slice(0z001122334455, 1, -5))
+  assert_equal(0z, slice(0z001122334455, 1, -6))
+enddef
+
 def Test_spellsuggest()
   if !has('spell')
     MissingFeature 'spell'
diff --git a/src/version.c b/src/version.c
index 07df2f3..dc1b752 100644
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2344,
+/**/
     2343,
 /**/
     2342,
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 7c4ef2a..938fc2e 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -965,10 +965,11 @@
 
 /*
  * Return the slice "str[first:last]" using character indexes.
+ * "exclusive" is TRUE for slice().
  * Return NULL when the result is empty.
  */
     char_u *
-string_slice(char_u *str, varnumber_T first, varnumber_T last)
+string_slice(char_u *str, varnumber_T first, varnumber_T last, int exclusive)
 {
     long	start_byte, end_byte;
     size_t	slen;
@@ -979,12 +980,12 @@
     start_byte = char_idx2byte(str, slen, first);
     if (start_byte < 0)
 	start_byte = 0; // first index very negative: use zero
-    if (last == -1)
+    if ((last == -1 && !exclusive) || last == VARNUM_MAX)
 	end_byte = (long)slen;
     else
     {
 	end_byte = char_idx2byte(str, slen, last);
-	if (end_byte >= 0 && end_byte < (long)slen)
+	if (!exclusive && end_byte >= 0 && end_byte < (long)slen)
 	    // end index is inclusive
 	    end_byte += MB_CPTR2LEN(str + end_byte);
     }
@@ -2992,7 +2993,7 @@
 		    tv = STACK_TV_BOT(-1);
 		    if (is_slice)
 			// Slice: Select the characters from the string
-			res = string_slice(tv->vval.v_string, n1, n2);
+			res = string_slice(tv->vval.v_string, n1, n2, FALSE);
 		    else
 			// Index: The resulting variable is a string of a
 			// single character.  If the index is too big or
@@ -3030,8 +3031,8 @@
 		    ectx.ec_stack.ga_len -= is_slice ? 2 : 1;
 		    tv = STACK_TV_BOT(-1);
 		    SOURCING_LNUM = iptr->isn_lnum;
-		    if (list_slice_or_index(list, is_slice, n1, n2, tv, TRUE)
-								       == FAIL)
+		    if (list_slice_or_index(list, is_slice, n1, n2, FALSE,
+							     tv, TRUE) == FAIL)
 			goto on_error;
 		}
 		break;
@@ -3052,8 +3053,8 @@
 			goto on_error;
 		    var1 = is_slice ? STACK_TV_BOT(-2) : STACK_TV_BOT(-1);
 		    var2 = is_slice ? STACK_TV_BOT(-1) : NULL;
-		    res = eval_index_inner(tv, is_slice,
-						   var1, var2, NULL, -1, TRUE);
+		    res = eval_index_inner(tv, is_slice, var1, var2,
+							FALSE, NULL, -1, TRUE);
 		    clear_tv(var1);
 		    if (is_slice)
 			clear_tv(var2);