changeset 25294:c626fd34b66f v8.2.3184

patch 8.2.3184: cannot add a digraph with a leading space Commit: https://github.com/vim/vim/commit/6106504e9edc8500131f7a36e59bc146f90180fa Author: mityu <mityu.mail@gmail.com> Date: Mon Jul 19 20:07:21 2021 +0200 patch 8.2.3184: cannot add a digraph with a leading space Problem: Cannot add a digraph with a leading space. It is not easy to list existing digraphs. Solution: Add setdigraph(), setdigraphlist(), getdigraph() and getdigraphlist(). (closes #8580)
author Bram Moolenaar <Bram@vim.org>
date Mon, 19 Jul 2021 20:15:04 +0200
parents d3f443bd2edd
children ff61999903e4
files runtime/doc/digraph.txt runtime/doc/eval.txt runtime/doc/usr_41.txt src/digraph.c src/errors.h src/evalfunc.c src/ex_docmd.c src/globals.h src/proto/digraph.pro src/testdir/test_digraph.vim src/version.c
diffstat 11 files changed, 583 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/digraph.txt
+++ b/runtime/doc/digraph.txt
@@ -38,6 +38,9 @@ 1. Defining digraphs					*digraphs-defin
 <			Avoid defining a digraph with '_' (underscore) as the
 			first character, it has a special meaning in the
 			future.
+			NOTE: This command cannot add a digraph that starts
+			with a white space.  If you want to add such digraph,
+			you can use |setdigraph()| instead.
 
 Vim is normally compiled with the |+digraphs| feature.  If the feature is
 disabled, the ":digraph" command will display an error message.
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2620,6 +2620,8 @@ getcompletion({pat}, {type} [, {filtered
 getcurpos([{winnr}])		List	position of the cursor
 getcursorcharpos([{winnr}])	List	character position of the cursor
 getcwd([{winnr} [, {tabnr}]])	String	get the current working directory
+getdigraph({chars})		String	get the digraph of {chars}
+getdigraphlist([{listall}])	List	get all |digraph|s
 getenv({name})			String	return environment variable
 getfontname([{name}])		String	name of font being used
 getfperm({fname})		String	file permissions of file {fname}
@@ -2886,6 +2888,8 @@ setcharpos({expr}, {list})	Number	set th
 setcharsearch({dict})		Dict	set character search from {dict}
 setcmdpos({pos})		Number	set cursor position in command-line
 setcursorcharpos({list})	Number	move cursor to position in {list}
+setdigraph({chars}, {digraph})	Boolean  register |digraph|
+setdigraphlist({digraphlist})	Boolean  register multiple |digraph|s
 setenv({name}, {val})		none	set environment variable
 setfperm({fname}, {mode})	Number	set {fname} file permissions to {mode}
 setline({lnum}, {line})		Number	set line {lnum} to {line}
@@ -5572,6 +5576,61 @@ getcwd([{winnr} [, {tabnr}]])
 <		Can also be used as a |method|: >
 			GetWinnr()->getcwd()
 <
+					      *getdigraph()* *E1214*
+getdigraph({chars})
+		Return the digraph of {chars}.  This should be a string with
+		exactly two characters.  If {chars} are not just two
+		characters, or the digraph of {chars} does not exist, an error
+		is given and an empty string is returned.
+
+		The character will be converted from Unicode to 'encoding'
+		when needed.  This does require the conversion to be
+		available, it might fail.
+
+		Also see |getdigraphlist()|.
+
+		Examples: >
+		" Get a built-in digraph
+		:echo getdigraph('00')		" Returns '∞'
+
+		" Get a user-defined digraph
+		:call setdigraph('aa', 'あ')
+		:echo getdigraph('aa')		" Returns 'あ'
+<
+		Can also be used as a |method|: >
+			GetChars()->getdigraph()
+<
+		This function works only when compiled with the |+digraphs|
+		feature.  If this feature is disabled, this function will
+		display an error message.
+
+
+getdigraphlist([{listall}])			*getdigraphlist()*
+		Return a list of digraphs.  If the {listall} argument is given
+		and it is TRUE, return all digraphs, including the default
+		digraphs.  Otherwise, return only user-defined digraphs.
+
+		The characters will be converted from Unicode to 'encoding'
+		when needed.  This does require the conservation to be
+		available, it might fail.
+
+		Also see |getdigraph()|.
+
+		Examples: >
+		" Get user-defined digraphs
+		:echo getdigraphlist()
+
+		" Get all the digraphs, including default digraphs
+		:echo digraphlist(1)
+<
+		Can also be used as a |method|: >
+			GetNumber()->getdigraphlist()
+<
+		This function works only when compiled with the |+digraphs|
+		feature.  If this feature is disabled, this function will
+		display an error message.
+
+
 getenv({name})						*getenv()*
 		Return the value of environment variable {name}.
 		When the variable does not exist |v:null| is returned.  That
@@ -9490,6 +9549,54 @@ setcursorcharpos({list})
 		Can also be used as a |method|: >
 			GetCursorPos()->setcursorcharpos()
 
+
+setdigraph({chars}, {digraph})		*setdigraph()* *E1205*
+		Add digraph {chars} to the list.  {chars} must be a string
+		with two characters.  {digraph} is a string with one utf-8
+		encoded character. Be careful, composing characters are NOT
+		ignored.  This function is similar to |:digraphs| command, but
+		useful to add digraphs start with a white space.
+
+		The function result is v:true if |digraph| is registered.  If
+		this fails an error message is given and v:false is returned.
+
+		If you want to define multiple digraphs at once, you can use
+		|setdigraphlist()|.
+
+		Example: >
+			call setdigraph('  ', 'あ')
+<
+		Can be used as a |method|: >
+			GetString()->setdigraph('あ')
+<
+		This function works only when compiled with the |+digraphs|
+		feature.  If this feature is disabled, this function will
+		display an error message.
+
+
+setdigraphlist({digraphlist})			*setdigraphlist()*
+		Similar to |setdigraph()| but this function can add multiple
+		digraphs at once.  {digraphlist} is a list composed of lists,
+		where each list contains two strings with {chars} and
+		{digraph} as in |setdigraph()|.
+		Example: >
+		    call setdigraphlist([['aa', 'あ'], ['ii', 'い']])
+<
+		It is similar to the following: >
+		    for [chars, digraph] in [['aa', 'あ'], ['ii', 'い']]
+			  call setdigraph(chars, digraph)
+		    endfor
+<		Except that the function returns after the first error,
+		following digraphs will not be added.
+
+		Can be used as a |method|: >
+		    GetList()->setdigraphlist()
+<
+		This function works only when compiled with the |+digraphs|
+		feature.  If this feature is disabled, this function will
+		display an error message.
+
+
 setenv({name}, {val})						*setenv()*
 		Set environment variable {name} to {val}.
 		When {val} is |v:null| the environment variable is deleted.
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -1000,6 +1000,10 @@ Mappings and Menus:			    *mapping-funct
 	mapset()		restore a mapping
 	menu_info()		get information about a menu item
 	wildmenumode()		check if the wildmode is active
+	getdigraph()		get |digraph|
+	getdigraphlist()	get all |digraph|s
+	setdigraph()		register |digraph|
+	setdigraphlist()	register multiple |digraph|s
 
 Testing:				    *test-functions*
 	assert_equal()		assert that two expressions values are equal
--- a/src/digraph.c
+++ b/src/digraph.c
@@ -1993,6 +1993,65 @@ getdigraph(int char1, int char2, int met
 }
 
 /*
+ * Add a digraph to the digraph table.
+ */
+    static void
+registerdigraph(int char1, int char2, int n)
+{
+    int		i;
+    digr_T	*dp;
+
+    // If the digraph already exists, replace "result".
+    dp = (digr_T *)user_digraphs.ga_data;
+    for (i = 0; i < user_digraphs.ga_len; ++i)
+    {
+	if ((int)dp->char1 == char1 && (int)dp->char2 == char2)
+	{
+	    dp->result = n;
+	    return;
+	}
+	++dp;
+    }
+
+    // Add a new digraph to the table.
+    if (ga_grow(&user_digraphs, 1) == OK)
+    {
+	dp = (digr_T *)user_digraphs.ga_data + user_digraphs.ga_len;
+	dp->char1 = char1;
+	dp->char2 = char2;
+	dp->result = n;
+	++user_digraphs.ga_len;
+    }
+}
+
+/*
+ * Check the characters are valid for a digraph.
+ * If they are valid, returns TRUE; otherwise, give an error message and
+ * returns FALSE.
+ */
+    int
+check_digraph_chars_valid(int char1, int char2)
+{
+    if (char2 == 0)
+    {
+	char_u msg[MB_MAXBYTES + 1];
+
+	msg[mb_char2bytes(char1, msg)] = NUL;
+
+	semsg(_(e_digraph_must_be_just_two_characters_str), msg);
+	return FALSE;
+    }
+    if (char1 == ESC || char2 == ESC)
+    {
+	emsg(_("E104: Escape not allowed in digraph"));
+	return FALSE;
+    }
+    return TRUE;
+}
+
+
+
+/*
  * Add the digraphs in the argument to the digraph table.
  * format: {c1}{c2} char {c1}{c2} char ...
  */
@@ -2000,8 +2059,6 @@ getdigraph(int char1, int char2, int met
 putdigraph(char_u *str)
 {
     int		char1, char2, n;
-    int		i;
-    digr_T	*dp;
 
     while (*str != NUL)
     {
@@ -2010,16 +2067,10 @@ putdigraph(char_u *str)
 	    return;
 	char1 = *str++;
 	char2 = *str++;
-	if (char2 == 0)
-	{
-	    emsg(_(e_invarg));
+
+	if (!check_digraph_chars_valid(char1, char2))
 	    return;
-	}
-	if (char1 == ESC || char2 == ESC)
-	{
-	    emsg(_("E104: Escape not allowed in digraph"));
-	    return;
-	}
+
 	str = skipwhite(str);
 	if (!VIM_ISDIGIT(*str))
 	{
@@ -2028,30 +2079,7 @@ putdigraph(char_u *str)
 	}
 	n = getdigits(&str);
 
-	// If the digraph already exists, replace the result.
-	dp = (digr_T *)user_digraphs.ga_data;
-	for (i = 0; i < user_digraphs.ga_len; ++i)
-	{
-	    if ((int)dp->char1 == char1 && (int)dp->char2 == char2)
-	    {
-		dp->result = n;
-		break;
-	    }
-	    ++dp;
-	}
-
-	// Add a new digraph to the table.
-	if (i == user_digraphs.ga_len)
-	{
-	    if (ga_grow(&user_digraphs, 1) == OK)
-	    {
-		dp = (digr_T *)user_digraphs.ga_data + user_digraphs.ga_len;
-		dp->char1 = char1;
-		dp->char2 = char2;
-		dp->result = n;
-		++user_digraphs.ga_len;
-	    }
-	}
+	registerdigraph(char1, char2, n);
     }
 }
 
@@ -2114,6 +2142,97 @@ listdigraphs(int use_headers)
 			    // wrong, in which case we messed up ScreenLines
 }
 
+    static void
+getdigraphlist_appendpair(digr_T *dp, list_T *l)
+{
+    char_u	buf[30];
+    char_u	*p;
+    list_T	*l2;
+    listitem_T	*li, *li2;
+
+
+    li = listitem_alloc();
+    if (li == NULL)
+	return;
+    list_append(l, li);
+    li->li_tv.v_type = VAR_LIST;
+    li->li_tv.v_lock = 0;
+
+    l2 = list_alloc();
+    li->li_tv.vval.v_list = l2;
+    if (l2 == NULL)
+	return;
+    ++l2->lv_refcount;
+
+    li2 = listitem_alloc();
+    if (li2 == NULL)
+	return;
+    list_append(l2, li2);
+    li2->li_tv.v_type = VAR_STRING;
+    li2->li_tv.v_lock = 0;
+
+    buf[0] = dp->char1;
+    buf[1] = dp->char2;
+    buf[2] = NUL;
+    li2->li_tv.vval.v_string = vim_strsave(&buf[0]);
+
+    li2 = listitem_alloc();
+    if (li2 == NULL)
+	return;
+    list_append(l2, li2);
+    li2->li_tv.v_type = VAR_STRING;
+    li2->li_tv.v_lock = 0;
+
+    p = buf;
+    if (has_mbyte)
+	p += (*mb_char2bytes)(dp->result, p);
+    else
+	*p++ = (char_u)dp->result;
+    *p = NUL;
+
+    li2->li_tv.vval.v_string = vim_strsave(buf);
+}
+
+    void
+getdigraphlist_common(int list_all, typval_T *rettv)
+{
+    int		i;
+    digr_T	*dp;
+
+    if (rettv_list_alloc(rettv) == FAIL)
+	return;
+
+    if (list_all)
+    {
+	dp = digraphdefault;
+	for (i = 0; dp->char1 != NUL && !got_int; ++i)
+	{
+#ifdef USE_UNICODE_DIGRAPHS
+	    digr_T tmp;
+
+	    tmp.char1 = dp->char1;
+	    tmp.char2 = dp->char2;
+	    tmp.result = getexactdigraph(tmp.char1, tmp.char2, FALSE);
+	    if (tmp.result != 0 && tmp.result != tmp.char2
+					  && (has_mbyte || tmp.result <= 255))
+		getdigraphlist_appendpair(&tmp, rettv->vval.v_list);
+#else
+	    if (getexactdigraph(dp->char1, dp->char2, FALSE) == dp->result
+		    && (has_mbyte || dp->result <= 255))
+		getdigraphlist_appendpair(dp, rettv->vval.v_list);
+#endif
+	    ++dp;
+	}
+    }
+
+    dp = (digr_T *)user_digraphs.ga_data;
+    for (i = 0; i < user_digraphs.ga_len && !got_int; ++i)
+    {
+	getdigraphlist_appendpair(dp, rettv->vval.v_list);
+	++dp;
+    }
+}
+
 static struct dg_header_entry {
     int	    dg_start;
     char    *dg_header;
@@ -2210,8 +2329,207 @@ printdigraph(digr_T *dp, result_T *previ
     }
 }
 
+# ifdef FEAT_EVAL
+/*
+ * Get the two digraph characters from a typval.
+ * Return OK or FAIL.
+ */
+    static int
+get_digraph_chars(typval_T *arg, int *char1, int *char2)
+{
+    char_u	buf_chars[NUMBUFLEN];
+    char_u	*chars = tv_get_string_buf_chk(arg, buf_chars);
+    char_u	*p = chars;
+
+    if (p != NULL)
+    {
+	if (*p != NUL)
+	{
+	    *char1 = mb_cptr2char_adv(&p);
+	    if (*p != NUL)
+	    {
+		*char2 = mb_cptr2char_adv(&p);
+		if (*p == NUL)
+		{
+		    if (check_digraph_chars_valid(*char1, *char2))
+			return OK;
+		    return FAIL;
+		}
+	    }
+	}
+    }
+    semsg(_(e_digraph_must_be_just_two_characters_str), chars);
+    return FAIL;
+}
+
+    static int
+setdigraph_common(typval_T *argchars, typval_T *argdigraph)
+{
+    int		char1, char2;
+    char_u	*digraph;
+    char_u	*p;
+    char_u	buf_digraph[NUMBUFLEN];
+    varnumber_T n;
+
+    if (get_digraph_chars(argchars, &char1, &char2) == FAIL)
+	return FALSE;
+
+    digraph = tv_get_string_buf_chk(argdigraph, buf_digraph);
+    if (digraph == NULL)
+	return FALSE;
+    p = digraph;
+    n = mb_cptr2char_adv(&p);
+    if (*p != NUL)
+    {
+	semsg(_(e_digraph_argument_must_be_one_character_str), digraph);
+	return FALSE;
+    }
+
+    registerdigraph(char1, char2, (int)n);
+    return TRUE;
+}
+# endif
+
 #endif // FEAT_DIGRAPHS
 
+#if defined(FEAT_EVAL) || defined(PROTO)
+/*
+ * "getdigraph()" function
+ */
+    void
+f_getdigraph(typval_T *argvars, typval_T *rettv)
+{
+# ifdef FEAT_DIGRAPHS
+    int		code;
+    char_u	buf[NUMBUFLEN];
+    char_u	*digraphs;
+
+    rettv->v_type = VAR_STRING;
+    rettv->vval.v_string = NULL;  // Return empty string for failure
+    digraphs = tv_get_string_chk(&argvars[0]);
+
+    if (digraphs == NULL)
+	return;
+    else if (STRLEN(digraphs) != 2)
+    {
+	semsg(_(e_digraph_must_be_just_two_characters_str), digraphs);
+	return;
+    }
+    code = getdigraph(digraphs[0], digraphs[1], FALSE);
+
+    if (has_mbyte)
+	buf[(*mb_char2bytes)(code, buf)] = NUL;
+    else {
+	buf[0] = code;
+	buf[1] = NUL;
+    }
+
+    rettv->vval.v_string = vim_strsave(buf);
+# else
+    emsg(_(e_no_digraphs_version));
+# endif
+}
+
+/*
+ * "getdigraphlist()" function
+ */
+    void
+f_getdigraphlist(typval_T *argvars, typval_T *rettv)
+{
+# ifdef FEAT_DIGRAPHS
+    int     flag_list_all;
+
+    if (argvars[0].v_type == VAR_UNKNOWN)
+	flag_list_all = FALSE;
+    else
+    {
+	int         error = FALSE;
+	varnumber_T flag = tv_get_number_chk(&argvars[0], &error);
+	if (error)
+	    return;
+	flag_list_all = flag ? TRUE : FALSE;
+    }
+
+    getdigraphlist_common(flag_list_all, rettv);
+# else
+    emsg(_(e_no_digraphs_version));
+# endif
+}
+
+/*
+ * "setdigraph()" function
+ */
+    void
+f_setdigraph(typval_T *argvars, typval_T *rettv)
+{
+# ifdef FEAT_DIGRAPHS
+    rettv->v_type = VAR_BOOL;
+    rettv->vval.v_number = VVAL_FALSE;
+
+    if (!setdigraph_common(&argvars[0], &argvars[1]))
+	return;
+
+    rettv->vval.v_number = VVAL_TRUE;
+# else
+    emsg(_(e_no_digraphs_version));
+# endif
+}
+
+/*
+ * "setdigraphlist()" function
+ */
+    void
+f_setdigraphlist(typval_T * argvars, typval_T *rettv)
+{
+# ifdef FEAT_DIGRAPHS
+    list_T	*pl, *l;
+    listitem_T	*pli;
+
+    rettv->v_type = VAR_BOOL;
+    rettv->vval.v_number = VVAL_FALSE;
+
+    if (argvars[0].v_type != VAR_LIST)
+    {
+	emsg(_(e_setdigraphlist_argument_must_be_list_of_lists_with_two_items));
+	return;
+    }
+
+    pl = argvars[0].vval.v_list;
+    if (pl == NULL)
+    {
+	// Empty list always results in success.
+	rettv->vval.v_number = VVAL_TRUE;
+	return;
+    }
+
+    FOR_ALL_LIST_ITEMS(pl, pli)
+    {
+	if (pli->li_tv.v_type != VAR_LIST)
+	{
+	    emsg(_(e_setdigraphlist_argument_must_be_list_of_lists_with_two_items));
+	    return;
+	}
+
+	l = pli->li_tv.vval.v_list;
+	if (l == NULL || l->lv_len != 2)
+	{
+	    emsg(_(e_setdigraphlist_argument_must_be_list_of_lists_with_two_items));
+	    return;
+	}
+
+	if (!setdigraph_common(&l->lv_first->li_tv,
+						 &l->lv_first->li_next->li_tv))
+	    return;
+    }
+    rettv->vval.v_number = VVAL_TRUE;
+# else
+    emsg(_(e_no_digraphs_version));
+# endif
+}
+
+#endif // FEAT_EVAL
+
+
 #if defined(FEAT_KEYMAP) || defined(PROTO)
 
 // structure used for b_kmap_ga.ga_data
--- a/src/errors.h
+++ b/src/errors.h
@@ -54,6 +54,10 @@ EXTERN char e_undefined_variable_str[]
 EXTERN char e_undefined_variable_char_str[]
 	INIT(= N_("E121: Undefined variable: %c:%s"));
 #endif
+#ifndef FEAT_DIGRAPHS
+EXTERN char e_no_digraphs_version[]
+	INIT(= N_("E196: No digraphs in this version"));
+#endif
 EXTERN char e_ambiguous_use_of_user_defined_command[]
 	INIT(= N_("E464: Ambiguous use of user-defined command"));
 EXTERN char e_invalid_command[]
@@ -508,3 +512,11 @@ EXTERN char e_bool_required_for_argument
 	INIT(= N_("E1212: Bool required for argument %d"));
 EXTERN char e_redefining_imported_item_str[]
 	INIT(= N_("E1213: Redefining imported item %s"));
+#if defined(FEAT_DIGRAPHS) && defined(FEAT_EVAL)
+EXTERN char e_digraph_must_be_just_two_characters_str[]
+	INIT(= N_("E1214: Digraph must be just two characters: %s"));
+EXTERN char e_digraph_argument_must_be_one_character_str[]
+	INIT(= N_("E1215: Digraph must be one character: %s"));
+EXTERN char e_setdigraphlist_argument_must_be_list_of_lists_with_two_items[]
+	INIT(= N_("E1216: setdigraphlist() argument must be a list of lists with two items"));
+#endif
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -493,6 +493,7 @@ static argcheck_T arg1_chan_or_job[] = {
 static argcheck_T arg2_float_or_nr[] = {arg_float_or_nr, arg_float_or_nr};
 static argcheck_T arg2_number[] = {arg_number, arg_number};
 static argcheck_T arg2_string[] = {arg_string, arg_string};
+static argcheck_T arg2_string_number[] = {arg_string, arg_number};
 static argcheck_T arg2_list_nr[] = {arg_list_number, arg_list_number};
 static argcheck_T arg2_nr_string[] = {arg_number, arg_string};
 static argcheck_T arg2_dict_string[] = {arg_dict_any, arg_string};
@@ -585,6 +586,12 @@ ret_list_items(int argcount UNUSED, type
 {
     return &t_list_list_any;
 }
+
+    static type_T *
+ret_list_string_items(int argcount UNUSED, type_T **argtypes UNUSED)
+{
+    return &t_list_list_string;
+}
     static type_T *
 ret_dict_any(int argcount UNUSED, type_T **argtypes UNUSED)
 {
@@ -1107,6 +1114,10 @@ static funcentry_T global_functions[] =
 			ret_list_number,    f_getcursorcharpos},
     {"getcwd",		0, 2, FEARG_1,	    arg2_number,
 			ret_string,	    f_getcwd},
+    {"getdigraph",     1, 1, FEARG_1,      arg1_string,
+                        ret_string,         f_getdigraph},
+    {"getdigraphlist",  0, 1, FEARG_1,      arg1_number,
+                        ret_list_string_items, f_getdigraphlist},
     {"getenv",		1, 1, FEARG_1,	    arg1_string,
 			ret_any,	    f_getenv},
     {"getfontname",	0, 1, 0,	    arg1_string,
@@ -1567,6 +1578,10 @@ static funcentry_T global_functions[] =
 			ret_number_bool,    f_setcmdpos},
     {"setcursorcharpos", 1, 3, FEARG_1,	    NULL,
 			ret_number_bool,    f_setcursorcharpos},
+    {"setdigraph",	2, 2, FEARG_1,	    arg2_string_number,
+			ret_bool,           f_setdigraph},
+    {"setdigraphlist",	1, 1, FEARG_1,	    arg1_list_string,
+			ret_bool,	    f_setdigraphlist},
     {"setenv",		2, 2, FEARG_2,	    NULL,
 			ret_void,	    f_setenv},
     {"setfperm",	2, 2, FEARG_1,	    arg2_string,
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -9315,7 +9315,7 @@ ex_digraphs(exarg_T *eap UNUSED)
     else
 	listdigraphs(eap->forceit);
 #else
-    emsg(_("E196: No digraphs in this version"));
+    emsg(_(e_no_digraphs_version));
 #endif
 }
 
--- a/src/globals.h
+++ b/src/globals.h
@@ -442,6 +442,7 @@ EXTERN type_T t_list_string INIT6(VAR_LI
 EXTERN type_T t_list_job INIT6(VAR_LIST, 0, 0, TTFLAG_STATIC, &t_job, NULL);
 EXTERN type_T t_list_dict_any INIT6(VAR_LIST, 0, 0, TTFLAG_STATIC, &t_dict_any, NULL);
 EXTERN type_T t_list_list_any INIT6(VAR_LIST, 0, 0, TTFLAG_STATIC, &t_list_any, NULL);
+EXTERN type_T t_list_list_string INIT6(VAR_LIST, 0, 0, TTFLAG_STATIC, &t_list_string, NULL);
 
 EXTERN type_T t_dict_bool INIT6(VAR_DICT, 0, 0, TTFLAG_STATIC, &t_bool, NULL);
 EXTERN type_T t_dict_number INIT6(VAR_DICT, 0, 0, TTFLAG_STATIC, &t_number, NULL);
@@ -1805,6 +1806,7 @@ EXTERN char e_nowhitespace[]	INIT(= N_("
 
 EXTERN char e_lock_unlock[]	INIT(= N_("E940: Cannot lock or unlock variable %s"));
 #endif
+
 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
 EXTERN char e_alloc_color[]	INIT(= N_("E254: Cannot allocate color %s"));
 #endif
--- a/src/proto/digraph.pro
+++ b/src/proto/digraph.pro
@@ -3,8 +3,14 @@ int do_digraph(int c);
 char_u *get_digraph_for_char(int val_arg);
 int get_digraph(int cmdline);
 int getdigraph(int char1, int char2, int meta_char);
+int check_digraph_chars_valid(int char1, int char2);
 void putdigraph(char_u *str);
 void listdigraphs(int use_headers);
+void getdigraphlist_common(int list_all, typval_T *rettv);
+void f_getdigraph(typval_T *argvars, typval_T *rettv);
+void f_getdigraphlist(typval_T *argvars, typval_T *rettv);
+void f_setdigraph(typval_T *argvars, typval_T *rettv);
+void f_setdigraphlist(typval_T *argvars, typval_T *rettv);
 char *keymap_init(void);
 void ex_loadkeymap(exarg_T *eap);
 void keymap_clear(garray_T *kmap);
--- a/src/testdir/test_digraph.vim
+++ b/src/testdir/test_digraph.vim
@@ -214,7 +214,7 @@ func Test_digraphs()
   call assert_fails('exe "digraph a\<Esc> 100"', 'E104:')
   call assert_fails('exe "digraph \<Esc>a 100"', 'E104:')
   call assert_fails('digraph xy z', 'E39:')
-  call assert_fails('digraph x', 'E474:')
+  call assert_fails('digraph x', 'E1214:')
   bw!
 endfunc
 
@@ -515,4 +515,81 @@ func Test_entering_digraph()
   call StopVimInTerminal(buf)
 endfunc
 
+func Test_setdigraph_function()
+  new
+  call setdigraph('aa', 'あ')
+  call Put_Dig('aa')
+  call assert_equal('あ', getline('$'))
+  call setdigraph(' i', 'い')
+  call Put_Dig(' i')
+  call assert_equal('い', getline('$'))
+  call setdigraph('  ', 'う')
+  call Put_Dig('  ')
+  call assert_equal('う', getline('$'))
+
+  eval 'aa'->setdigraph('え')
+  call Put_Dig('aa')
+  call assert_equal('え', getline('$'))
+
+  call assert_fails('call setdigraph("aaa", "あ")', 'E1214: Digraph must be just two characters: aaa')
+  call assert_fails('call setdigraph("b", "あ")', 'E1214: Digraph must be just two characters: b')
+  call assert_fails('call setdigraph("あ", "あ")', 'E1214: Digraph must be just two characters: あ')
+  call assert_fails('call setdigraph("aa", "ああ")', 'E1215: Digraph must be one character: ああ')
+  call assert_fails('call setdigraph("aa", "か" .. nr2char(0x3099))',  'E1215: Digraph must be one character: か' .. nr2char(0x3099))
+  bwipe!
+endfunc
+
+func Test_getdigraph_function()
+  " Built-in digraphs
+  call assert_equal('∞', getdigraph('00'))
+
+  " User-defined digraphs
+  call setdigraph('aa', 'あ')
+  call setdigraph(' i', 'い')
+  call setdigraph('  ', 'う')
+  call assert_equal('あ', getdigraph('aa'))
+  call assert_equal('あ', 'aa'->getdigraph())
+  call assert_equal('い', getdigraph(' i'))
+  call assert_equal('う', getdigraph('  '))
+  call assert_fails('call getdigraph("aaa")', 'E1214: Digraph must be just two characters: aaa')
+  call assert_fails('call getdigraph("b")', 'E1214: Digraph must be just two characters: b')
+endfunc
+
+func Test_getdigraph_function_encode()
+  CheckFeature iconv
+  let testcases = {
+        \'00': '∞',
+        \'aa': 'あ',
+        \}
+  for [key, ch] in items(testcases)
+    call setdigraph(key, ch)
+    set encoding=japan
+    call assert_equal(iconv(ch, 'utf-8', 'japan'), getdigraph(key))
+    set encoding&
+  endfor
+endfunc
+
+func Test_setdigraphlist_function()
+  call setdigraphlist([['aa', 'き'], ['bb', 'く']])
+  call assert_equal('き', getdigraph('aa'))
+  call assert_equal('く', getdigraph('bb'))
+
+  call assert_fails('call setdigraphlist([[]])', 'E1216:')
+  call assert_fails('call setdigraphlist([["aa", "b", "cc"]])', '1216:')
+  call assert_fails('call setdigraphlist([["あ", "あ"]])', 'E1214: Digraph must be just two characters: あ')
+endfunc
+
+func Test_getdigraphlist_function()
+  " Make sure user-defined digraphs are defined
+  call setdigraphlist([['aa', 'き'], ['bb', 'く']])
+
+  for pair in getdigraphlist(1)
+    call assert_equal(getdigraph(pair[0]), pair[1])
+  endfor
+
+  " We don't know how many digraphs are registered before, so check the number
+  " of digraphs returned.
+  call assert_equal(getdigraphlist()->len(), getdigraphlist(0)->len())
+  call assert_notequal((getdigraphlist()->len()), getdigraphlist(1)->len())
+endfunc
 " vim: shiftwidth=2 sts=2 expandtab
--- a/src/version.c
+++ b/src/version.c
@@ -756,6 +756,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3184,
+/**/
     3183,
 /**/
     3182,