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 e0997dd26461545534c3b4eab45b14a20dbd6866

Fix ' escaping for completion of option value
Author: xaizek
Author date (UTC): 2025-01-29 12:57
Committer name: xaizek
Committer date (UTC): 2025-02-01 11:36
Parent(s): 5f03a549dafee3db335795b641dd3cb3f7ad0c57
Signing key: 99DC5E4DB05F6BE2
Tree: 83c8acd8853569d504f44d8308b57ace37b37c86
File Lines added Lines deleted
ChangeLog 2 0
src/engine/options.c 68 6
tests/options/opt_completion.c 25 0
File ChangeLog changed (mode: 100644) (index c40e5fb77..3661eff28)
8 8 Fixed absolute paths comparing equal to similar relative paths (regression Fixed absolute paths comparing equal to similar relative paths (regression
9 9 in 0.14-beta). in 0.14-beta).
10 10
11 Fixed single quotes escaping for completion of option value.
12
11 13 0.13 to 0.14-beta (2025-01-19) 0.13 to 0.14-beta (2025-01-19)
12 14
13 15 Changed --with-gtk flag to --with-glib (old name is still available). GTK Changed --with-gtk flag to --with-glib (old name is still available). GTK
File src/engine/options.c changed (mode: 100644) (index 12fdff9c5..d83615761)
... ... static int find_val(const opt_t *opt, const char value[]);
94 94 static int set_print(const opt_t *opt); static int set_print(const opt_t *opt);
95 95 static char * extract_option(const char **argsp, int completion); static char * extract_option(const char **argsp, int completion);
96 96 static char * skip_alphas(const char str[]); static char * skip_alphas(const char str[]);
97 static char * escape_as_squoted(const char str[]);
97 98 static void complete_option_name(const char buf[], int bool_only, int pseudo, static void complete_option_name(const char buf[], int bool_only, int pseudo,
98 99 OPT_SCOPE scope); OPT_SCOPE scope);
99 100 static int option_matches(const opt_t *opt, OPT_SCOPE scope); static int option_matches(const opt_t *opt, OPT_SCOPE scope);
 
... ... vle_opts_complete(const char args[], const char **start, OPT_SCOPE scope)
1409 1410 } }
1410 1411 else if(strcmp(p, "'") == 0) else if(strcmp(p, "'") == 0)
1411 1412 { {
1412 char *escaped = escape_for_squotes(str_val, /*offset=*/0);
1413 if(escaped != NULL)
1414 {
1415 vle_compl_put_match(format_str("'%s'", escaped), "");
1416 free(escaped);
1417 }
1413 vle_compl_put_match(escape_as_squoted(str_val), "");
1418 1414 } }
1419 1415 else if(strcmp(p, "\"") == 0) else if(strcmp(p, "\"") == 0)
1420 1416 { {
 
... ... skip_alphas(const char str[])
1572 1568 return (char *)str; return (char *)str;
1573 1569 } }
1574 1570
1571 /* Produces a value quoted in single quotes as much as possible (embedded single
1572 * quotes have to be escaped with a slash outside of single quotes). Returns
1573 * newly allocated memory or NULL on error. */
1574 static char *
1575 escape_as_squoted(const char str[])
1576 {
1577 enum
1578 {
1579 START,
1580 NONQUOTE,
1581 QUOTE,
1582 }
1583 state = START;
1584
1585 /* The worst case is when quotes are interleaved with non-quotes: x'y'z'...
1586 * That gets expanded to 3 characters per non-quote and 2 per quote, so each
1587 * pair of input characters becomes 5 characters. */
1588 const size_t max_escaped = strlen(str)*5/2;
1589
1590 char *escaped = malloc(MAX(2U, max_escaped) + 1);
1591 if(escaped == NULL)
1592 {
1593 return NULL;
1594 }
1595
1596 char *out = escaped;
1597
1598 while(*str != '\0')
1599 {
1600 if(*str == '\'')
1601 {
1602 if(state == NONQUOTE)
1603 {
1604 *out++ = '\'';
1605 }
1606
1607 *out++ = '\\';
1608 *out++ = *str++;
1609 state = QUOTE;
1610 }
1611 else
1612 {
1613 if(state != NONQUOTE)
1614 {
1615 *out++ = '\'';
1616 }
1617
1618 *out++ = *str++;
1619 state = NONQUOTE;
1620 }
1621 }
1622
1623 if(state == START)
1624 {
1625 *out++ = '\'';
1626 }
1627 if(state != QUOTE)
1628 {
1629 *out++ = '\'';
1630 }
1631
1632 *out = '\0';
1633
1634 return escaped;
1635 }
1636
1575 1637 void void
1576 1638 vle_opts_complete_real(const char beginning[], OPT_SCOPE scope) vle_opts_complete_real(const char beginning[], OPT_SCOPE scope)
1577 1639 { {
File tests/options/opt_completion.c changed (mode: 100644) (index 0a478b21b..a54540b01)
... ... TEST(after_equal_sign_completion_spaces_ok)
214 214 ASSERT_NEXT_MATCH("\"8\""); ASSERT_NEXT_MATCH("\"8\"");
215 215 } }
216 216
217 TEST(after_equal_sign_completion_corner_cases)
218 {
219 const char *start;
220
221 optval_t val;
222
223 val.str_val = "/home' 'directory/tmp";
224 vle_opts_assign("fusehome", val, OPT_GLOBAL);
225 vle_compl_reset();
226 vle_opts_complete("fusehome='", &start, OPT_GLOBAL);
227 ASSERT_NEXT_MATCH("'/home'\\'' '\\''directory/tmp'");
228
229 val.str_val = "'''";
230 vle_opts_assign("fusehome", val, OPT_GLOBAL);
231 vle_compl_reset();
232 vle_opts_complete("fusehome='", &start, OPT_GLOBAL);
233 ASSERT_NEXT_MATCH("\\'\\'\\'");
234
235 val.str_val = "";
236 vle_opts_assign("fusehome", val, OPT_GLOBAL);
237 vle_compl_reset();
238 vle_opts_complete("fusehome='", &start, OPT_GLOBAL);
239 ASSERT_NEXT_MATCH("''");
240 }
241
217 242 TEST(after_fake_equal_sign_completion_fail) TEST(after_fake_equal_sign_completion_fail)
218 243 { {
219 244 const char *start; const char *start;
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