xaizek / vifm (License: GPLv2+) (since 2018-12-07)
Vifm is a file manager with curses interface, which provides Vi[m]-like environment for managing objects within file systems, extended with some useful ideas from mutt.
Commit 20e8f60d18995009f39d491ba3cf5faa993b8c3c

Add \<key> sequences to "strings" in expressions
Thanks to agguser.

See https://q2a.vifm.info/2157/how-to-specify-ctrl-key-in-normal-command
Author: xaizek
Author date (UTC): 2025-07-05 17:13
Committer name: xaizek
Committer date (UTC): 2025-07-06 09:38
Parent(s): 0ceb6f1c11bf097f766cc238801a9dd9b7328e35
Signing key: 99DC5E4DB05F6BE2
Tree: 01e4ee2ba195f0f3b2792745364cade929852a9e
File Lines added Lines deleted
ChangeLog 4 0
data/man/vifm.1 5 2
data/vim/doc/app/vifm-app.txt 4 2
src/bracket_notation.c 22 0
src/bracket_notation.h 4 0
src/cmd_core.c 2 1
src/engine/parsing.c 89 2
src/engine/parsing.h 11 0
src/instance.c 10 1
src/instance.h 3 0
tests/misc/bracket_notation.c 9 0
tests/parsing/double_quotes.c 77 1
File ChangeLog changed (mode: 100644) (index 5cbfb8f69..a25421929)
10 10 Added <help> :*map argument that enables providing description for the Added <help> :*map argument that enables providing description for the
11 11 mapping with the same curly braces syntax as used by :file[x]type. mapping with the same curly braces syntax as used by :file[x]type.
12 12
13 Added escape sequences for expanding bracket notation in strings in double
14 quotes (e.g., `:echo "This is \<space>."`) that appear in expressions (so
15 won't work, say, in `:set grepprg="\<space>"`). Thanks to agguser.
16
13 17 Updated utf8proc to v2.10.0. Updated utf8proc to v2.10.0.
14 18
15 19 Made documentation on which :commands can have comments a bit more Made documentation on which :commands can have comments a bit more
File data/man/vifm.1 changed (mode: 100644) (index e9b76845e..5f52768c8)
1 .TH VIFM 1 "15 June 2025" "vifm 0.15"
1 .TH VIFM 1 "5 July 2025" "vifm 0.15"
2 2 .\" --------------------------------------------------------------------------- .\" ---------------------------------------------------------------------------
3 3 .SH NAME .SH NAME
4 4 .\" --------------------------------------------------------------------------- .\" ---------------------------------------------------------------------------
 
... ... string
6037 6037
6038 6038 Note that double quotes are used. Note that double quotes are used.
6039 6039
6040 A string constant accepts these special characters:
6040 A string constant expands the following special sequences:
6041 6041 .br .br
6042 6042 \\b backspace <bs> \\b backspace <bs>
6043 6043 .br .br
 
... ... A string constant accepts these special characters:
6053 6053 .br .br
6054 6054 \\" double quote \\" double quote
6055 6055 .br .br
6056 \\<key> value of that key
6057 .br
6056 6058
6057 6059 Examples: Examples:
6058 6060
6059 6061 .EX .EX
6060 6062 "\\"Hello,\\tWorld!\\"" "\\"Hello,\\tWorld!\\""
6061 6063 "Hi,\\nthere!" "Hi,\\nthere!"
6064 "Whitespace:\\<space>\\<tab>."
6062 6065 .EE .EE
6063 6066
6064 6067 literal-string literal-string
File data/vim/doc/app/vifm-app.txt changed (mode: 100644) (index bfc29f5ee..05a8abecc)
1 *vifm-app.txt* For Vifm version 0.15 Last change: 2025 June 15
1 *vifm-app.txt* For Vifm version 0.15 Last change: 2025 July 5
2 2
3 3 Email for bugs and suggestions: <xaizek@posteo.net> Email for bugs and suggestions: <xaizek@posteo.net>
4 4
 
... ... string *vifm-expr-string*
5062 5062
5063 5063 Note that double quotes are used. Note that double quotes are used.
5064 5064
5065 A string constant accepts these special characters:
5065 A string constant expands the following special sequences:
5066 5066 \b backspace <bs> \b backspace <bs>
5067 5067 \e escape <esc> \e escape <esc>
5068 5068 \n newline \n newline
 
... ... A string constant accepts these special characters:
5070 5070 \t tab <tab> \t tab <tab>
5071 5071 \\ backslash \\ backslash
5072 5072 \" double quote \" double quote
5073 \<key> value of that key
5073 5074
5074 5075 Examples: > Examples: >
5075 5076 "\"Hello,\tWorld!\"" "\"Hello,\tWorld!\""
5076 5077 "Hi,\nthere!" "Hi,\nthere!"
5078 "Whitespace:\<space>\<tab>."
5077 5079 < <
5078 5080
5079 5081 literal-string *vifm-literal-string* literal-string *vifm-literal-string*
File src/bracket_notation.c changed (mode: 100644) (index 00d294c9d..60fb34432)
... ... substitute_specsw(const wchar_t cmd[])
704 704 return buf; return buf;
705 705 } }
706 706
707 const wchar_t *
708 spec_to_wstr(const char str[])
709 {
710 wchar_t wide[strlen(str) + 1];
711
712 int i;
713 for(i = 0; str[i] != '\0'; ++i)
714 {
715 /* The notation uses Latin characters only. */
716 wide[i] = (unsigned char)str[i];
717 }
718 wide[i] = L'\0';
719
720 const key_pair_t *pair = find_notation(wide);
721 if(pair == NULL)
722 {
723 return NULL;
724 }
725
726 return pair->key;
727 }
728
707 729 /* Performs binary search in the list of bracket notations. Returns NULL if /* Performs binary search in the list of bracket notations. Returns NULL if
708 730 * str wasn't found in the list. */ * str wasn't found in the list. */
709 731 static const key_pair_t * static const key_pair_t *
File src/bracket_notation.h changed (mode: 100644) (index cf4dfbdbe..88cd5cd70)
... ... wchar_t * substitute_specs(const char cmd[]);
32 32 * Returns newly allocated wide string, which should be freed by the caller. */ * Returns newly allocated wide string, which should be freed by the caller. */
33 33 wchar_t * substitute_specsw(const wchar_t cmd[]); wchar_t * substitute_specsw(const wchar_t cmd[]);
34 34
35 /* Looks up the notation. Returns pointer to a literal which corresponds to the
36 * notation or NULL on lookup failure. */
37 const wchar_t * spec_to_wstr(const char str[]);
38
35 39 /* Converts sequence of keys into user-friendly printable string. Spaces are /* Converts sequence of keys into user-friendly printable string. Spaces are
36 40 * left as is, without converting them into <space>. Returns newly allocated * left as is, without converting them into <space>. Returns newly allocated
37 41 * string. */ * string. */
File src/cmd_core.c changed (mode: 100644) (index 5fa34c859..fd6f11f06)
71 71 #include "filelist.h" #include "filelist.h"
72 72 #include "filtering.h" #include "filtering.h"
73 73 #include "flist_sel.h" #include "flist_sel.h"
74 #include "instance.h"
74 75 #include "macros.h" #include "macros.h"
75 76 #include "marks.h" #include "marks.h"
76 77 #include "opt_handlers.h" #include "opt_handlers.h"
 
... ... cmds_init(void)
438 439
439 440 /* Initialize modules used by this one. */ /* Initialize modules used by this one. */
440 441 init_bracket_notation(); init_bracket_notation();
441 init_variables();
442 instance_init_variables();
442 443
443 444 create_builtin_vars(); create_builtin_vars();
444 445 sessions_set_callback(&session_changed_callback); sessions_set_callback(&session_changed_callback);
File src/engine/parsing.c changed (mode: 100644) (index 746475e39..f19e2e394)
48 48 #include <ctype.h> /* isalnum() isalpha() tolower() */ #include <ctype.h> /* isalnum() isalpha() tolower() */
49 49 #include <stddef.h> /* NULL size_t */ #include <stddef.h> /* NULL size_t */
50 50 #include <stdio.h> /* snprintf() */ #include <stdio.h> /* snprintf() */
51 #include <stdlib.h>
51 #include <stdlib.h> /* free() */
52 52 #include <string.h> /* strcat() strcmp() strncpy() */ #include <string.h> /* strcat() strcmp() strncpy() */
53 #include <wchar.h> /* wchar_t */
53 54
54 55 #include "../compat/reallocarray.h" #include "../compat/reallocarray.h"
55 56 #include "../utils/str.h" #include "../utils/str.h"
 
59 60 #include "var.h" #include "var.h"
60 61 #include "variables.h" #include "variables.h"
61 62
63 #define NOTATION_LENGTH_MAX 16
62 64 #define VAR_NAME_LENGTH_MAX 1024 #define VAR_NAME_LENGTH_MAX 1024
63 65 #define CMD_LINE_LENGTH_MAX 4096 #define CMD_LINE_LENGTH_MAX 4096
64 66
 
... ... static int parse_singly_quoted_char(parse_context_t *ctx, const char **in,
177 179 static var_t parse_doubly_quoted_string(parse_context_t *ctx, const char **in); static var_t parse_doubly_quoted_string(parse_context_t *ctx, const char **in);
178 180 static int parse_doubly_quoted_char(parse_context_t *ctx, const char **in, static int parse_doubly_quoted_char(parse_context_t *ctx, const char **in,
179 181 sbuffer *sbuf); sbuffer *sbuf);
182 static int parse_doubly_quoted_notation(parse_context_t *ctx, const char **in,
183 sbuffer *sbuf);
180 184 static var_t eval_envvar(parse_context_t *ctx, const char **in); static var_t eval_envvar(parse_context_t *ctx, const char **in);
181 185 static var_t eval_var(parse_context_t *ctx, const char **in); static var_t eval_var(parse_context_t *ctx, const char **in);
182 186 static var_t eval_opt(parse_context_t *ctx, const char **in); static var_t eval_opt(parse_context_t *ctx, const char **in);
 
... ... static void get_next(parse_context_t *ctx, const char **in);
191 195
192 196 static int initialized; static int initialized;
193 197 static getenv_func getenv_fu; static getenv_func getenv_fu;
198 static notation_func notation_fu; /* Can be NULL. */
194 199
195 200 /* Empty expression to be returned on errors. */ /* Empty expression to be returned on errors. */
196 201 static expr_t null_expr; static expr_t null_expr;
 
... ... void
201 206 vle_parser_init(getenv_func getenv_f) vle_parser_init(getenv_func getenv_f)
202 207 { {
203 208 getenv_fu = getenv_f; getenv_fu = getenv_f;
209 notation_fu = NULL;
204 210 initialized = 1; initialized = 1;
205 211 } }
206 212
213 void
214 vle_parser_set_notation(notation_func notation_f)
215 {
216 assert(initialized && "Parser must be initialized before configuration.");
217 notation_fu = notation_f;
218 }
219
207 220 parsing_result_t parsing_result_t
208 221 vle_parser_eval(const char input[], int interactive) vle_parser_eval(const char input[], int interactive)
209 222 { {
 
... ... parse_doubly_quoted_string(parse_context_t *ctx, const char **in)
1060 1073 return var_false(); return var_false();
1061 1074 } }
1062 1075
1063 /* dqchar
1076 /* dqchar ::= [^"\] | '\' dqesc
1077 * dqesc ::= '\\' | '\0' | '\b' | '\e' | '\n' | '\r' | '\t' | dqbn
1064 1078 * Returns non-zero if there are more characters in the string. */ * Returns non-zero if there are more characters in the string. */
1065 1079 int int
1066 1080 parse_doubly_quoted_char(parse_context_t *ctx, const char **in, sbuffer *sbuf) parse_doubly_quoted_char(parse_context_t *ctx, const char **in, sbuffer *sbuf)
 
... ... parse_doubly_quoted_char(parse_context_t *ctx, const char **in, sbuffer *sbuf)
1107 1121 ctx->last_error = PE_INVALID_EXPRESSION; ctx->last_error = PE_INVALID_EXPRESSION;
1108 1122 return 0; return 0;
1109 1123 } }
1124
1125 if(ctx->last_token.c == '<')
1126 {
1127 parse_context_t tmp_ctx = *ctx;
1128 const char *tmp_in = *in;
1129 if(parse_doubly_quoted_notation(&tmp_ctx, &tmp_in, sbuf) == 0)
1130 {
1131 *ctx = tmp_ctx;
1132 *in = tmp_in;
1133 return 1;
1134 }
1135
1136 if(tmp_ctx.last_error != PE_NO_ERROR)
1137 {
1138 *ctx = tmp_ctx;
1139 return 0;
1140 }
1141 }
1142
1110 1143 ok = sstrappendch(sbuf->data, &sbuf->len, sbuf->size, ok = sstrappendch(sbuf->data, &sbuf->len, sbuf->size,
1111 1144 table[(unsigned char)ctx->last_token.c]); table[(unsigned char)ctx->last_token.c]);
1112 1145 } }
 
... ... parse_doubly_quoted_char(parse_context_t *ctx, const char **in, sbuffer *sbuf)
1125 1158 return 1; return 1;
1126 1159 } }
1127 1160
1161 /* dqbn ::= '<' [^>]* '>'
1162 * Parses bracket notation (like <cr>). Returns zero on success. */
1163 static int
1164 parse_doubly_quoted_notation(parse_context_t *ctx, const char **in,
1165 sbuffer *sbuf)
1166 {
1167 if(notation_fu == NULL)
1168 {
1169 return 1;
1170 }
1171
1172 char notation[NOTATION_LENGTH_MAX + 1];
1173 notation[0] = '\0';
1174 size_t len = 0;
1175 while(ctx->last_token.type != END && ctx->last_token.c != '>' &&
1176 len < sizeof(notation) - 1)
1177 {
1178 (void)sstrappendch(notation, &len, sizeof(notation), ctx->last_token.c);
1179 get_next(ctx, in);
1180 }
1181
1182 if(ctx->last_token.c != '>')
1183 {
1184 return 1;
1185 }
1186
1187 (void)sstrappendch(notation, &len, sizeof(notation), '>');
1188 get_next(ctx, in);
1189
1190 const wchar_t *expanded = notation_fu(notation);
1191 if(expanded == NULL)
1192 {
1193 return 1;
1194 }
1195
1196 char *expanded_mb = to_multibyte(expanded);
1197 if(expanded_mb == NULL)
1198 {
1199 ctx->last_error = PE_INTERNAL;
1200 return 1;
1201 }
1202
1203 int err = sstrappend(sbuf->data, &sbuf->len, sbuf->size, expanded_mb);
1204 free(expanded_mb);
1205
1206 if(err != 0)
1207 {
1208 ctx->last_error = PE_INTERNAL;
1209 return 1;
1210 }
1211
1212 return 0;
1213 }
1214
1128 1215 /* envvar ::= '$' envvarname */ /* envvar ::= '$' envvarname */
1129 1216 static var_t static var_t
1130 1217 eval_envvar(parse_context_t *ctx, const char **in) eval_envvar(parse_context_t *ctx, const char **in)
File src/engine/parsing.h changed (mode: 100644) (index 360c19db5..badcbc3a9)
19 19 #ifndef VIFM__ENGINE__PARSING_H__ #ifndef VIFM__ENGINE__PARSING_H__
20 20 #define VIFM__ENGINE__PARSING_H__ #define VIFM__ENGINE__PARSING_H__
21 21
22 #include <wchar.h> /* wchar_t */
23
22 24 #include "var.h" #include "var.h"
23 25
24 26 /* An enumeration of possible parsing errors. */ /* An enumeration of possible parsing errors. */
 
... ... parsing_result_t;
55 57 * string. The function should not allocate new string. */ * string. The function should not allocate new string. */
56 58 typedef const char * (*getenv_func)(const char *envname); typedef const char * (*getenv_func)(const char *envname);
57 59
60 /* Type of a function that will be used to resolve bracket notation (e.g.,
61 * <cr>) to its value. If the notation doesn't exist, the function must return
62 * NULL. The function must not allocate a new string. */
63 typedef const wchar_t * (*notation_func)(const char str[]);
64
58 65 /* A type of function that will be used to print error messages. */ /* A type of function that will be used to print error messages. */
59 66 typedef void (*print_error_func)(const char msg[]); typedef void (*print_error_func)(const char msg[]);
60 67
61 68 /* Can be called several times. getenv_f can be NULL. */ /* Can be called several times. getenv_f can be NULL. */
62 69 void vle_parser_init(getenv_func getenv_f); void vle_parser_init(getenv_func getenv_f);
63 70
71 /* Sets optional function which resolves bracket notation (like <cr>). The
72 * parameter can be NULL. */
73 void vle_parser_set_notation(notation_func notation_f);
74
64 75 /* Performs parsing and evaluation. Returns structure describing the outcome. /* Performs parsing and evaluation. Returns structure describing the outcome.
65 76 * Field value of the result should be freed by the caller. */ * Field value of the result should be freed by the caller. */
66 77 parsing_result_t vle_parser_eval(const char input[], int interactive); parsing_result_t vle_parser_eval(const char input[], int interactive);
File src/instance.c changed (mode: 100644) (index 1e3ef247d..39d5b2d26)
27 27 #include "engine/cmds.h" #include "engine/cmds.h"
28 28 #include "engine/keys.h" #include "engine/keys.h"
29 29 #include "engine/options.h" #include "engine/options.h"
30 #include "engine/parsing.h"
30 31 #include "engine/variables.h" #include "engine/variables.h"
31 32 #include "int/path_env.h" #include "int/path_env.h"
32 33 #include "lua/vlua.h" #include "lua/vlua.h"
 
34 35 #include "ui/ui.h" #include "ui/ui.h"
35 36 #include "utils/str.h" #include "utils/str.h"
36 37 #include "utils/utils.h" #include "utils/utils.h"
38 #include "bracket_notation.h"
37 39 #include "bmarks.h" #include "bmarks.h"
38 40 #include "dir_stack.h" #include "dir_stack.h"
39 41 #include "filelist.h" #include "filelist.h"
 
46 48 #include "undo.h" #include "undo.h"
47 49 #include "vifm.h" #include "vifm.h"
48 50
51 void
52 instance_init_variables(void)
53 {
54 init_variables();
55 vle_parser_set_notation(&spec_to_wstr);
56 }
57
49 58 void void
50 59 instance_stop(void) instance_stop(void)
51 60 { {
 
... ... instance_start_restart(RestartType type)
111 120
112 121 /* Reset variables. */ /* Reset variables. */
113 122 clear_envvars(); clear_envvars();
114 init_variables();
123 instance_init_variables();
115 124 /* This update is needed as clear_variables() will reset $PATH. */ /* This update is needed as clear_variables() will reset $PATH. */
116 125 update_path_env(1); update_path_env(1);
117 126
File src/instance.h changed (mode: 100644) (index e235dd5f2..6c6be7286)
21 21
22 22 #include "status.h" #include "status.h"
23 23
24 /* Makes sure variables are ready to be used. */
25 void instance_init_variables(void);
26
24 27 /* Stops the process by send itself SIGSTOP. */ /* Stops the process by send itself SIGSTOP. */
25 28 void instance_stop(void); void instance_stop(void);
26 29
File tests/misc/bracket_notation.c renamed from tests/misc/wstr_to_spec.c (similarity 95%) (mode: 100644) (index 88a0a222f..7f546c019)
16 16 SETUP_ONCE() SETUP_ONCE()
17 17 { {
18 18 try_enable_utf8_locale(); try_enable_utf8_locale();
19 init_bracket_notation();
19 20 } }
20 21
21 22 TEST(c_h_is_bs_at_start_only, IF(utf8_locale)) TEST(c_h_is_bs_at_start_only, IF(utf8_locale))
 
... ... TEST(known_sequences)
202 203 } }
203 204 } }
204 205
206 TEST(notation_lookup)
207 {
208 /* Known notation. */
209 assert_wstring_equal(L" ", spec_to_wstr("<space>"));
210 /* Unknown notation. */
211 assert_wstring_equal(NULL, spec_to_wstr("<unknown>"));
212 }
213
205 214 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
206 215 /* vim: set cinoptions+=t0 : */ /* vim: set cinoptions+=t0 : */
File tests/parsing/double_quotes.c changed (mode: 100644) (index 84b693d63..beaeca832)
1 1 #include <stic.h> #include <stic.h>
2 2
3 3 #include <stdlib.h> /* free() */ #include <stdlib.h> /* free() */
4 #include <string.h> /* memset() */
4 #include <string.h> /* memset() strcmp() */
5 #include <wchar.h> /* wchar_t */
5 6
6 7 #include "../../src/engine/parsing.h" #include "../../src/engine/parsing.h"
7 8 #include "../../src/engine/var.h" #include "../../src/engine/var.h"
9 #include "../../src/utils/macros.h"
8 10
9 11 #include "asserts.h" #include "asserts.h"
10 12
13 static const wchar_t * expand_notation(const char str[]);
14
11 15 TEST(empty_ok) TEST(empty_ok)
12 16 { {
13 17 ASSERT_OK("\"\"", ""); ASSERT_OK("\"\"", "");
 
... ... TEST(very_long_string)
66 70 ASSERT_FAIL(string, PE_INTERNAL); ASSERT_FAIL(string, PE_INTERNAL);
67 71 } }
68 72
73 TEST(bracket_notation)
74 {
75 /* No notation by default. */
76 vle_parser_set_notation(NULL);
77 ASSERT_OK("\"\\<space>\"", "<space>");
78
79 vle_parser_set_notation(&expand_notation);
80
81 /* Unescaped notation. */
82 ASSERT_OK("\"<space>\"", "<space>");
83
84 /* Escaped notation. */
85 ASSERT_OK("\"\\<space>\"", " ");
86
87 /* Unrecognized notation. */
88 ASSERT_OK("\"\\<notspace>\"", "<notspace>");
89
90 /* Unrecognized notation which is too long for the parser. */
91 ASSERT_OK("\"\\<nosuchexcessivelylongnotation>\"",
92 "<nosuchexcessivelylongnotation>");
93
94 /* Recognized notation which is too long for the parser. */
95 ASSERT_OK("\"\\<thisnotationiswaytoolong>\"", "<thisnotationiswaytoolong>");
96
97 /* Unfinished notation. */
98 ASSERT_OK("\"\\<space\"", "<space");
99
100 /* Nested notation. */
101 ASSERT_OK("\"\\<sp\\ace>\"", "<space>");
102 ASSERT_OK("\"\\<sp\\<a>ce>\"", "<sp<a>ce>");
103 ASSERT_OK("\"\\<\\<tab>>\"", "<\t>");
104
105 /* Unrealistically long expansion. */
106 ASSERT_FAIL("\"\\<very_long>\"", PE_INTERNAL);
107
108 /* More realistic case. */
109 ASSERT_OK("\"ma\\<space>yy\\<tab>\\<tab>p'a\"", "ma yy\t\tp'a");
110 }
111
112 static const wchar_t *
113 expand_notation(const char str[])
114 {
115 static wchar_t very_long[5*1024];
116
117 if(very_long[0] == '\0')
118 {
119 int i;
120 for(i = 0; i < (int)ARRAY_LEN(very_long) - 1; ++i)
121 {
122 very_long[i] = L'1';
123 }
124 }
125
126 if(strcmp(str, "<thisnotationiswaytoolong>") == 0)
127 {
128 return L"!!!";
129 }
130 if(strcmp(str, "<very_long>") == 0)
131 {
132 return very_long;
133 }
134 if(strcmp(str, "<space>") == 0)
135 {
136 return L" ";
137 }
138 if(strcmp(str, "<tab>") == 0)
139 {
140 return L"\t";
141 }
142 return NULL;
143 }
144
69 145 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
70 146 /* vim: set cinoptions+=t0 filetype=c : */ /* vim: set cinoptions+=t0 filetype=c : */
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/vifm

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/vifm

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master