changeset 30226:b6b803ed4a53 v9.0.0449

patch 9.0.0449: there is no easy way to translate a key code into a string Commit: https://github.com/vim/vim/commit/cdc839353f68ca43db6446e1b727fc7ba657b738 Author: zeertzjq <zeertzjq@outlook.com> Date: Mon Sep 12 13:38:41 2022 +0100 patch 9.0.0449: there is no easy way to translate a key code into a string Problem: There is no easy way to translate a string with a key code into a readable string. Solution: Add the keytrans() function. (closes #11114)
author Bram Moolenaar <Bram@vim.org>
date Mon, 12 Sep 2022 14:45:04 +0200
parents 51f58c2d23fa
children 5e8a1c97cdd7
files runtime/doc/builtin.txt src/evalfunc.c src/map.c src/menu.c src/message.c src/option.c src/proto/message.pro src/testdir/test_functions.vim src/version.c
diffstat 9 files changed, 83 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -325,6 +325,8 @@ js_encode({expr})		String	encode JS styl
 json_decode({string})		any	decode JSON
 json_encode({expr})		String	encode JSON
 keys({dict})			List	keys in {dict}
+keytrans({string})		String	translate internal keycodes to a form
+					that can be used by |:map|
 len({expr})			Number	the length of {expr}
 libcall({lib}, {func}, {arg})	String	call {func} in library {lib} with {arg}
 libcallnr({lib}, {func}, {arg})	Number	idem, but return a Number
@@ -5205,6 +5207,16 @@ keys({dict})						*keys()*
 		Can also be used as a |method|: >
 			mydict->keys()
 
+keytrans({string})					*keytrans()*
+		Turn the internal byte representation of keys into a form that
+		can be used for |:map|.  E.g. >
+			:let xx = "\<C-Home>"
+			:echo keytrans(xx)
+<			<C-Home>
+
+		Can also be used as a |method|: >
+			"\<C-Home>"->keytrans()
+
 <							*len()* *E701*
 len({expr})	The result is a Number, which is the length of the argument.
 		When {expr} is a String or a Number the length in bytes is
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -89,6 +89,7 @@ static void f_inputsecret(typval_T *argv
 static void f_interrupt(typval_T *argvars, typval_T *rettv);
 static void f_invert(typval_T *argvars, typval_T *rettv);
 static void f_islocked(typval_T *argvars, typval_T *rettv);
+static void f_keytrans(typval_T *argvars, typval_T *rettv);
 static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv);
 static void f_libcall(typval_T *argvars, typval_T *rettv);
 static void f_libcallnr(typval_T *argvars, typval_T *rettv);
@@ -2058,6 +2059,8 @@ static funcentry_T global_functions[] =
 			ret_string,	    f_json_encode},
     {"keys",		1, 1, FEARG_1,	    arg1_dict_any,
 			ret_list_string,    f_keys},
+    {"keytrans",	1, 1, FEARG_1,	    arg1_string,
+			ret_string,	    f_keytrans},
     {"last_buffer_nr",	0, 0, 0,	    NULL,	// obsolete
 			ret_number,	    f_last_buffer_nr},
     {"len",		1, 1, FEARG_1,	    arg1_len,
@@ -7136,6 +7139,24 @@ f_islocked(typval_T *argvars, typval_T *
 }
 
 /*
+ * "keytrans()" function
+ */
+    static void
+f_keytrans(typval_T *argvars, typval_T *rettv)
+{
+    char_u *escaped;
+
+    rettv->v_type = VAR_STRING;
+    if (check_for_string_arg(argvars, 0) == FAIL
+	    || argvars[0].vval.v_string == NULL)
+	return;
+    // Need to escape K_SPECIAL and CSI for mb_unescape().
+    escaped = vim_strsave_escape_csi(argvars[0].vval.v_string);
+    rettv->vval.v_string = str2special_save(escaped, TRUE, TRUE);
+    vim_free(escaped);
+}
+
+/*
  * "last_buffer_nr()" function.
  */
     static void
--- a/src/map.c
+++ b/src/map.c
@@ -2317,7 +2317,7 @@ mapblock2dict(
 	int	    buffer_local,   // false if not buffer local mapping
 	int	    abbr)	    // true if abbreviation
 {
-    char_u	    *lhs = str2special_save(mp->m_keys, TRUE);
+    char_u	    *lhs = str2special_save(mp->m_keys, TRUE, FALSE);
     char_u	    *mapmode = map_mode_to_chars(mp->m_mode);
 
     dict_add_string(dict, "lhs", lhs);
@@ -2409,7 +2409,7 @@ get_maparg(typval_T *argvars, typval_T *
 	    if (*rhs == NUL)
 		rettv->vval.v_string = vim_strsave((char_u *)"<Nop>");
 	    else
-		rettv->vval.v_string = str2special_save(rhs, FALSE);
+		rettv->vval.v_string = str2special_save(rhs, FALSE, FALSE);
 	}
 
     }
@@ -2478,7 +2478,7 @@ f_maplist(typval_T *argvars UNUSED, typv
 		keys_buf = NULL;
 		did_simplify = FALSE;
 
-		lhs = str2special_save(mp->m_keys, TRUE);
+		lhs = str2special_save(mp->m_keys, TRUE, FALSE);
 		(void)replace_termcodes(lhs, &keys_buf, flags, &did_simplify);
 		vim_free(lhs);
 
--- a/src/menu.c
+++ b/src/menu.c
@@ -2890,7 +2890,7 @@ menuitem_getinfo(char_u *menu_name, vimm
 			*menu->strings[bit] == NUL
 				? (char_u *)"<Nop>"
 				: (tofree = str2special_save(
-						  menu->strings[bit], FALSE)));
+					menu->strings[bit], FALSE, FALSE)));
 		vim_free(tofree);
 	    }
 	    if (status == OK)
--- a/src/message.c
+++ b/src/message.c
@@ -1759,7 +1759,7 @@ msg_outtrans_special(
 	    ++str;
 	}
 	else
-	    text = (char *)str2special(&str, from);
+	    text = (char *)str2special(&str, from, FALSE);
 	if (text[0] != NUL && text[1] == NUL)
 	    // single-byte character or illegal byte
 	    text = (char *)transchar_byte((char_u)text[0]);
@@ -1782,14 +1782,16 @@ msg_outtrans_special(
     char_u *
 str2special_save(
     char_u  *str,
-    int	    is_lhs)  // TRUE for lhs, FALSE for rhs
+    int	    replace_spaces,	// TRUE to replace " " with "<Space>".
+				// used for the lhs of mapping and keytrans().
+    int	    replace_lt)		// TRUE to replace "<" with "<lt>".
 {
     garray_T	ga;
     char_u	*p = str;
 
     ga_init2(&ga, 1, 40);
     while (*p != NUL)
-	ga_concat(&ga, str2special(&p, is_lhs));
+	ga_concat(&ga, str2special(&p, replace_spaces, replace_lt));
     ga_append(&ga, NUL);
     return (char_u *)ga.ga_data;
 }
@@ -1804,7 +1806,9 @@ str2special_save(
     char_u *
 str2special(
     char_u	**sp,
-    int		from)	// TRUE for lhs of mapping
+    int		replace_spaces,	// TRUE to replace " " with "<Space>".
+				// used for the lhs of mapping and keytrans().
+    int		replace_lt)	// TRUE to replace "<" with "<lt>".
 {
     int			c;
     static char_u	buf[7];
@@ -1861,8 +1865,10 @@ str2special(
 	*sp = str + (*str == NUL ? 0 : 1);
 
     // Make special keys and C0 control characters in <> form, also <M-Space>.
-    // Use <Space> only for lhs of a mapping.
-    if (special || c < ' ' || (from && c == ' '))
+    if (special
+	|| c < ' '
+	|| (replace_spaces && c == ' ')
+	|| (replace_lt && c == '<'))
 	return get_special_key_name(c, modifiers);
     buf[0] = c;
     buf[1] = NUL;
@@ -1880,7 +1886,7 @@ str2specialbuf(char_u *sp, char_u *buf, 
     *buf = NUL;
     while (*sp)
     {
-	s = str2special(&sp, FALSE);
+	s = str2special(&sp, FALSE, FALSE);
 	if ((int)(STRLEN(s) + STRLEN(buf)) < len)
 	    STRCAT(buf, s);
     }
--- a/src/option.c
+++ b/src/option.c
@@ -3994,7 +3994,8 @@ get_option_value(
 	if (stringval != NULL)
 	{
 	    if ((char_u **)varp == &p_pt)	// 'pastetoggle'
-		*stringval = str2special_save(*(char_u **)(varp), FALSE);
+		*stringval = str2special_save(*(char_u **)(varp), FALSE,
+									FALSE);
 #ifdef FEAT_CRYPT
 	    // never return the value of the crypt key
 	    else if ((char_u **)varp == &curbuf->b_p_key
@@ -4879,7 +4880,7 @@ put_setstring(
 	{
 	    s = *valuep;
 	    while (*s != NUL)
-		if (put_escstr(fd, str2special(&s, FALSE), 2) == FAIL)
+		if (put_escstr(fd, str2special(&s, FALSE, FALSE), 2) == FAIL)
 		    return FAIL;
 	}
 	// expand the option value, replace $HOME by ~
--- a/src/proto/message.pro
+++ b/src/proto/message.pro
@@ -37,8 +37,8 @@ char_u *msg_outtrans_one(char_u *p, int 
 int msg_outtrans_len_attr(char_u *msgstr, int len, int attr);
 void msg_make(char_u *arg);
 int msg_outtrans_special(char_u *strstart, int from, int maxlen);
-char_u *str2special_save(char_u *str, int is_lhs);
-char_u *str2special(char_u **sp, int from);
+char_u *str2special_save(char_u *str, int replace_spaces, int replace_lt);
+char_u *str2special(char_u **sp, int replace_spaces, int replace_lt);
 void str2specialbuf(char_u *sp, char_u *buf, int len);
 void msg_prt_line(char_u *s, int list);
 void msg_puts(char *s);
--- a/src/testdir/test_functions.vim
+++ b/src/testdir/test_functions.vim
@@ -2764,6 +2764,32 @@ func Test_eval()
   call assert_fails("call eval('5 a')", 'E488:')
 endfunc
 
+" Test for the keytrans() function
+func Test_keytrans()
+  call assert_equal('<Space>', keytrans(' '))
+  call assert_equal('<lt>', keytrans('<'))
+  call assert_equal('<lt>Tab>', keytrans('<Tab>'))
+  call assert_equal('<Tab>', keytrans("\<Tab>"))
+  call assert_equal('<C-V>', keytrans("\<C-V>"))
+  call assert_equal('<BS>', keytrans("\<BS>"))
+  call assert_equal('<Home>', keytrans("\<Home>"))
+  call assert_equal('<C-Home>', keytrans("\<C-Home>"))
+  call assert_equal('<M-Home>', keytrans("\<M-Home>"))
+  call assert_equal('<C-Space>', keytrans("\<C-Space>"))
+  call assert_equal('<M-Space>', keytrans("\<*M-Space>"))
+  call assert_equal('<M-x>', "\<*M-x>"->keytrans())
+  call assert_equal('<C-I>', "\<*C-I>"->keytrans())
+  call assert_equal('<S-3>', "\<*S-3>"->keytrans())
+  call assert_equal('π', 'π'->keytrans())
+  call assert_equal('<M-π>', "\<M-π>"->keytrans())
+  call assert_equal('ě', 'ě'->keytrans())
+  call assert_equal('<M-ě>', "\<M-ě>"->keytrans())
+  call assert_equal('', ''->keytrans())
+  call assert_equal('', test_null_string()->keytrans())
+  call assert_fails('call keytrans(1)', 'E1174:')
+  call assert_fails('call keytrans()', 'E119:')
+endfunc
+
 " Test for the nr2char() function
 func Test_nr2char()
   set encoding=latin1
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    449,
+/**/
     448,
 /**/
     447,