From a5c596f86098764c99f7eaa79e807427683f3f2e Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Mon, 2 Mar 2026 21:18:14 +0000 Subject: [PATCH 1/2] Add :dirhash to create directory shortcuts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit zsh has a very handy "directory hash" feature that allows you to create directory shortcuts like so: hash -d v=~/src/vim hash -d vim=/usr/share/vim/vim92 And then you can use `cd ~v` or `vim ~v/src/map.c` etc. everywhere, similar to `~user`. This adds that feature in Vim: # Create and list hashes :dirhash v ~/src/vim :dirhash vim ~/src/vim :dirhash vim /usr/share/vim/vim92 v ~/src/vim # Works in expand(), expandcmd() :echo expand('~vim') /usr/share/vim/vim92 :echo expand('~vim/foo') /usr/share/vim/vim92/foo # And on CLI :e ~v :e ~v/src/map.c # Remove dirhash :undirhash vim :dirhash v ~/src/vim I am not in love with the name :dirhash, but I can't really think of anything else at the moment. I considered :dirabbr and :dabbr, but it seems to me that this is significantly different enough from :abbr that it would be somewhat confusing. And keeping the same name zsh uses is probably a good idea? None of the map*() functions support dirhashes. I would prefer to add new functions – that all of this uses the map hash table seems mostly an implementation detail rather than something that should be exposed. --- runtime/doc/index.txt | 2 + runtime/doc/map.txt | 35 ++++++++-- src/digraph.c | 4 +- src/errors.h | 2 + src/ex_cmdidxs.h | 50 +++++++------- src/ex_cmds.h | 6 ++ src/map.c | 95 +++++++++++++++++++++----- src/misc1.c | 128 ++++++++++++++++++----------------- src/netbeans.c | 2 +- src/proto/map.pro | 4 +- src/structs.h | 1 + src/testdir/Make_all.mak | 2 + src/testdir/test_dirhash.vim | 91 +++++++++++++++++++++++++ 13 files changed, 310 insertions(+), 112 deletions(-) create mode 100644 src/testdir/test_dirhash.vim diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 310da6fab38861..57ce4278f4824b 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1315,6 +1315,7 @@ tag command action ~ |:diffsplit| :diffs[plit] show differences with another file |:diffthis| :difft[his] make current window a diff window |:digraphs| :dig[raphs] show or enter digraphs +|:dirhash| :dirh[ash] create directory hash |:display| :di[splay] display registers |:disassemble| :disa[ssemble] disassemble Vim9 user function |:djump| :dj[ump] jump to #define @@ -1741,6 +1742,7 @@ tag command action ~ |:undojoin| :undoj[oin] join next change with previous undo block |:undolist| :undol[ist] list leafs of the undo tree |:unabbreviate| :una[bbreviate] remove abbreviation +|:undirhash| :undir[hash] remove directory hash |:unhide| :unh[ide] open a window for each loaded file in the buffer list |:uniq| :uni[q] uniq lines diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index 118e2d44e90a72..e5da5d1fccf26b 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -26,8 +26,9 @@ manual. 1.14 Mapping with Kitty keyboard protocol |kitty-keyboard-protocol| 1.15 Mapping an operator |:map-operator| 2. Abbreviations |abbreviations| -3. Local mappings and functions |script-local| -4. User-defined commands |user-commands| +3. Directory hashes |dirhash| +4. Local mappings and functions |script-local| +5. User-defined commands |user-commands| ============================================================================== 1. Key mapping *key-mapping* *mapping* *macro* @@ -180,7 +181,7 @@ This unmap command does NOT work: > Because it tries to unmap "@@ ", including the white space before the command separator "|". Other examples with trailing white space: > - unmap @@ + unmap @@ unmap @@ # Vim9 script comment unmap @@ " legacy script comment @@ -1423,7 +1424,31 @@ Expands to: ^[ [example given by Steve Kirkendall] ============================================================================== -3. Local mappings and functions *script-local* +3. Directory hashes *dirhash* + +A directory hash allows using any value as ~name at the start of the path. For +example: +> + :dirhash ~vim /usr/share/vim/vim92 + :edit ~vim/syntax/vim.vim +< +These work everywhere |expandcmd()| is called. + + *:dirhash* +:dirh[ash] list all directory hashes. + +:dirh[ash] [] {lhs} {rhs} + add directory hash for {lhs} to {rhs}. If {lhs} already + existed it is replaced with the new {rhs}. {rhs} may + contain spaces. + See :map- for the optional argument. + + *:undirhash* +:undir[hash] [] {lhs} + Remove directory hash for {lhs} from the list. + +============================================================================== +4. Local mappings and functions *script-local* When using several Vim script files, there is the danger that mappings and functions used in one script use the same name as in other scripts. To avoid @@ -1478,7 +1503,7 @@ and what their number is. This is all {not available when compiled without the |+eval| feature}. ============================================================================== -4. User-defined commands *user-commands* +5. User-defined commands *user-commands* It is possible to define your own Ex commands. A user-defined command can act just like a built-in command (it can have a range or arguments, arguments can diff --git a/src/digraph.c b/src/digraph.c index 4320e57a3774af..25e96c36949b1d 100644 --- a/src/digraph.c +++ b/src/digraph.c @@ -2352,7 +2352,7 @@ ex_loadkeymap(exarg_T *eap) vim_snprintf((char *)buf, sizeof(buf), " %s %s", ((kmap_T *)curbuf->b_kmap_ga.ga_data)[i].from, ((kmap_T *)curbuf->b_kmap_ga.ga_data)[i].to); - (void)do_map(MAPTYPE_NOREMAP, buf, MODE_LANGMAP, FALSE); + (void)do_map(MAPTYPE_NOREMAP, buf, MODE_LANGMAP, FALSE, FALSE); } p_cpo = save_cpo; @@ -2383,7 +2383,7 @@ keymap_unload(void) for (i = 0; i < curbuf->b_kmap_ga.ga_len; ++i) { vim_snprintf((char *)buf, sizeof(buf), " %s", kp[i].from); - (void)do_map(MAPTYPE_UNMAP, buf, MODE_LANGMAP, FALSE); + (void)do_map(MAPTYPE_UNMAP, buf, MODE_LANGMAP, FALSE, FALSE); } keymap_clear(&curbuf->b_kmap_ga); diff --git a/src/errors.h b/src/errors.h index b909c0fbf3b83b..4b031020133588 100644 --- a/src/errors.h +++ b/src/errors.h @@ -3807,3 +3807,5 @@ EXTERN char e_no_redraw_listener_callbacks_defined[] #endif EXTERN char e_leadtab_requires_tab[] INIT(= N_("E1572: 'listchars' field \"leadtab\" requires \"tab\" to be specified")); +EXTERN char e_no_such_dirhash[] + INIT(= N_("E1573: No such dirhash")); diff --git a/src/ex_cmdidxs.h b/src/ex_cmdidxs.h index a406f397d04709..b7a3e994b43614 100644 --- a/src/ex_cmdidxs.h +++ b/src/ex_cmdidxs.h @@ -9,28 +9,28 @@ static const unsigned short cmdidxs1[26] = /* b */ 21, /* c */ 45, /* d */ 113, - /* e */ 139, - /* f */ 168, - /* g */ 185, - /* h */ 191, - /* i */ 201, - /* j */ 222, - /* k */ 224, - /* l */ 229, - /* m */ 292, - /* n */ 310, - /* o */ 330, - /* p */ 342, - /* q */ 383, - /* r */ 386, - /* s */ 407, - /* t */ 477, - /* u */ 524, - /* v */ 536, - /* w */ 557, - /* x */ 572, - /* y */ 582, - /* z */ 583 + /* e */ 140, + /* f */ 169, + /* g */ 186, + /* h */ 192, + /* i */ 202, + /* j */ 223, + /* k */ 225, + /* l */ 230, + /* m */ 293, + /* n */ 311, + /* o */ 331, + /* p */ 343, + /* q */ 384, + /* r */ 387, + /* s */ 408, + /* t */ 478, + /* u */ 525, + /* v */ 538, + /* w */ 559, + /* x */ 574, + /* y */ 584, + /* z */ 585 }; /* @@ -44,7 +44,7 @@ static const unsigned char cmdidxs2[26][26] = /* a */ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 0, 0, 8, 17, 0, 18, 0, 0, 0, 0, 0 }, /* b */ { 2, 0, 0, 5, 6, 8, 0, 0, 0, 0, 0, 9, 10, 11, 12, 13, 0, 14, 0, 0, 0, 0, 23, 0, 0, 0 }, /* c */ { 3, 12, 16, 18, 20, 22, 25, 0, 0, 0, 0, 33, 39, 42, 48, 58, 60, 61, 62, 0, 64, 0, 67, 0, 0, 0 }, - /* d */ { 0, 0, 0, 0, 0, 0, 0, 0, 9, 19, 0, 20, 0, 0, 21, 0, 0, 23, 24, 0, 0, 0, 0, 0, 0, 0 }, + /* d */ { 0, 0, 0, 0, 0, 0, 0, 0, 9, 20, 0, 21, 0, 0, 22, 0, 0, 24, 25, 0, 0, 0, 0, 0, 0, 0 }, /* e */ { 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 9, 11, 12, 0, 0, 0, 0, 0, 0, 0, 23, 0, 24, 0, 0 }, /* f */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0 }, /* g */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 4, 5, 0, 0, 0, 0 }, @@ -61,7 +61,7 @@ static const unsigned char cmdidxs2[26][26] = /* r */ { 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 20, 0, 0, 0, 0 }, /* s */ { 2, 6, 15, 0, 19, 23, 0, 25, 26, 0, 0, 29, 31, 35, 39, 41, 0, 50, 0, 51, 0, 64, 65, 0, 66, 0 }, /* t */ { 2, 0, 19, 0, 24, 26, 0, 27, 0, 29, 0, 30, 34, 37, 39, 40, 0, 41, 43, 0, 44, 0, 0, 0, 46, 0 }, - /* u */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* u */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* v */ { 1, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 12, 15, 0, 0, 0, 0, 18, 0, 19, 0, 0, 0, 0, 0 }, /* w */ { 2, 0, 0, 0, 0, 0, 0, 3, 4, 0, 0, 8, 0, 9, 0, 10, 11, 0, 0, 0, 13, 14, 0, 0, 0, 0 }, /* x */ { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 5, 0, 0, 0, 7, 0, 0, 8, 0, 0, 0, 0, 0 }, @@ -69,4 +69,4 @@ static const unsigned char cmdidxs2[26][26] = /* z */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; -static const int command_count = 600; +static const int command_count = 602; diff --git a/src/ex_cmds.h b/src/ex_cmds.h index a3d705d5f1cea9..1d07744fc1147a 100644 --- a/src/ex_cmds.h +++ b/src/ex_cmds.h @@ -509,6 +509,9 @@ EXCMD(CMD_diffthis, "diffthis", ex_diffthis, EXCMD(CMD_digraphs, "digraphs", ex_digraphs, EX_BANG|EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK, ADDR_NONE), +EXCMD(CMD_dirhash, "dirhash", ex_dirhash, + EX_EXTRA|EX_TRLBAR|EX_NOTRLCOM|EX_CTRLV|EX_CMDWIN|EX_LOCK_OK, + ADDR_NONE), EXCMD(CMD_disassemble, "disassemble", ex_disassemble, EX_BANG|EX_EXTRA|EX_NEEDARG|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK, ADDR_NONE), @@ -1688,6 +1691,9 @@ EXCMD(CMD_tunmap, "tunmap", ex_unmap, EXCMD(CMD_type, "type", ex_type, EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK|EX_WHOLE|EX_EXPORT, ADDR_NONE), +EXCMD(CMD_undirhash, "undirhash", ex_dirhash, + EX_EXTRA|EX_TRLBAR|EX_NOTRLCOM|EX_CTRLV|EX_CMDWIN|EX_LOCK_OK, + ADDR_NONE), EXCMD(CMD_undo, "undo", ex_undo, EX_RANGE|EX_COUNT|EX_ZEROR|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK, ADDR_OTHER), diff --git a/src/map.c b/src/map.c index 0a909fb93b2e45..75ed9699699458 100644 --- a/src/map.c +++ b/src/map.c @@ -17,6 +17,7 @@ * List used for abbreviations. */ static mapblock_T *first_abbr = NULL; // first entry in abbrlist +static mapblock_T *first_dir = NULL; // first entry in dirhash list /* * Each mapping is put in one of the 256 hash lists, to speed up finding it. @@ -219,6 +220,7 @@ showmap( map_add( mapblock_T **map_table, mapblock_T **abbr_table, + mapblock_T **dir_table, char_u *keys, char_u *rhs, char_u *orig_rhs, @@ -227,6 +229,7 @@ map_add( int silent, int mode, int is_abbr, + int is_dir, #ifdef FEAT_EVAL int expr, scid_T sid, // 0 to use current_sctx @@ -287,6 +290,11 @@ map_add( mp->m_next = *abbr_table; *abbr_table = mp; } + else if (is_dir) + { + mp->m_next = *dir_table; + *dir_table = mp; + } else { int n = MAP_HASH(mp->m_mode, mp->m_keys[0]); @@ -305,6 +313,7 @@ map_add( list_mappings( int keyround, int abbrev, + int dir, int haskey, char_u *keys, int keys_len, @@ -367,6 +376,12 @@ list_mappings( break; mp = curbuf->b_first_abbr; } + else if (dir) + { + if (hash != 0) // there is only one dirhash list + break; + mp = curbuf->b_first_dir; + } else mp = curbuf->b_maphash[hash]; for ( ; mp != NULL && !got_int; mp = mp->m_next) @@ -444,7 +459,8 @@ do_map( int maptype, char_u *arg, int mode, - int abbrev) // not a mapping but an abbreviation + int abbrev, // not a mapping but an abbreviation + int dir) // not a mapping but a dirhash { char_u *keys; mapblock_T *mp, **mpp; @@ -463,6 +479,7 @@ do_map( int retval = 0; int do_backslash; mapblock_T **abbr_table; + mapblock_T **dir_table; mapblock_T **map_table; int unique = FALSE; int nowait = FALSE; @@ -479,6 +496,7 @@ do_map( keys = arg; map_table = maphash; abbr_table = &first_abbr; + dir_table = &first_dir; if (maptype == MAPTYPE_UNMAP_LHS) { @@ -502,6 +520,7 @@ do_map( keys = skipwhite(keys + 8); map_table = curbuf->b_maphash; abbr_table = &curbuf->b_first_abbr; + dir_table = &curbuf->b_first_dir; continue; } @@ -743,7 +762,7 @@ do_map( // When listing global mappings, also list buffer-local ones here. if (map_table != curbuf->b_maphash && !hasarg && maptype != MAPTYPE_UNMAP) - list_mappings(keyround, abbrev, haskey, keys, len, + list_mappings(keyround, abbrev, dir, haskey, keys, len, mode, &did_local); // Find an entry in the maphash[] list that matches. @@ -764,6 +783,12 @@ do_map( break; mpp = abbr_table; } + else if (dir) + { + if (hash > 0) // there is only one dir list + break; + mpp = dir_table; + } else mpp = &(map_table[hash]); for (mp = *mpp; mp != NULL && !got_int; mp = *mpp) @@ -927,6 +952,8 @@ do_map( { if (abbrev) msg(_("No abbreviation found")); + else if (dir) + msg(_("No dirhash found")); else msg(_("No mapping found")); } @@ -937,8 +964,8 @@ do_map( continue; // have added the new entry already // Get here when adding a new entry to the maphash[] list or abbrlist. - mp_result[keyround - 1] = map_add(map_table, abbr_table, keys, - rhs, orig_rhs, noremap, nowait, silent, mode, abbrev, + mp_result[keyround - 1] = map_add(map_table, abbr_table, dir_table, keys, + rhs, orig_rhs, noremap, nowait, silent, mode, abbrev, dir, #ifdef FEAT_EVAL expr, /* sid */ 0, /* scriptversion */ 0, /* lnum */ 0, #endif @@ -1787,6 +1814,25 @@ check_abbr( return FALSE; } +/* + * Find directory hash for the given name, returning NULL if it doesn't exist. + */ + char_u* +find_dirhash(char_u *name) +{ + for (mapblock_T *mp = curbuf->b_first_dir; mp != NULL; mp = mp->m_next) + { + if (STRCMP(name, mp->m_keys) == 0) + return mp->m_str; + } + for (mapblock_T *mp = first_dir; mp != NULL; mp = mp->m_next) + { + if (STRCMP(name, mp->m_keys) == 0) + return mp->m_str; + } + return NULL; +} + #ifdef FEAT_EVAL /* * Evaluate the RHS of a mapping or abbreviations and take care of escaping @@ -2741,6 +2787,7 @@ f_mapset(typval_T *argvars, typval_T *rettv UNUSED) linenr_T lnum; mapblock_T **map_table = maphash; mapblock_T **abbr_table = &first_abbr; + mapblock_T **dir_table = &first_abbr; int nowait; char_u *arg; int dict_only; @@ -2821,6 +2868,7 @@ f_mapset(typval_T *argvars, typval_T *rettv UNUSED) { map_table = curbuf->b_maphash; abbr_table = &curbuf->b_first_abbr; + dir_table = &curbuf->b_first_dir; } // Delete any existing mapping for this lhs and mode. @@ -2838,15 +2886,15 @@ f_mapset(typval_T *argvars, typval_T *rettv UNUSED) if (arg == NULL) return; } - do_map(MAPTYPE_UNMAP_LHS, arg, mode, is_abbr); + do_map(MAPTYPE_UNMAP_LHS, arg, mode, is_abbr, FALSE); vim_free(arg); - mp_result[0] = map_add(map_table, abbr_table, lhsraw, rhs, orig_rhs, - noremap, nowait, silent, mode, is_abbr, expr, sid, + mp_result[0] = map_add(map_table, abbr_table, dir_table, lhsraw, rhs, orig_rhs, + noremap, nowait, silent, mode, is_abbr, FALSE, expr, sid, scriptversion, lnum, 0); if (lhsrawalt != NULL) - mp_result[1] = map_add(map_table, abbr_table, lhsrawalt, rhs, orig_rhs, - noremap, nowait, silent, mode, is_abbr, expr, sid, + mp_result[1] = map_add(map_table, abbr_table, dir_table, lhsrawalt, rhs, orig_rhs, + noremap, nowait, silent, mode, is_abbr, FALSE, expr, sid, scriptversion, lnum, 1); if (mp_result[0] != NULL && mp_result[1] != NULL) @@ -2975,7 +3023,7 @@ add_map(char_u *map, int mode, int nore) s = vim_strsave(map); if (s != NULL) { - (void)do_map(nore ? MAPTYPE_NOREMAP : MAPTYPE_MAP, s, mode, FALSE); + (void)do_map(nore ? MAPTYPE_NOREMAP : MAPTYPE_MAP, s, mode, FALSE, FALSE); vim_free(s); } p_cpo = cpo_save; @@ -3178,7 +3226,7 @@ did_set_langmap(optset_T *args UNUSED) #endif static void -do_exmap(exarg_T *eap, int isabbrev) +do_exmap(exarg_T *eap, int isabbrev, int isdir) { int mode; char_u *cmdp; @@ -3188,12 +3236,16 @@ do_exmap(exarg_T *eap, int isabbrev) switch (do_map(*cmdp == 'n' ? MAPTYPE_NOREMAP : *cmdp == 'u' ? MAPTYPE_UNMAP : MAPTYPE_MAP, - eap->arg, mode, isabbrev)) + eap->arg, mode, isabbrev, isdir)) { case 1: emsg(_(e_invalid_argument)); break; - case 2: emsg((isabbrev ? _(e_no_such_abbreviation) - : _(e_no_such_mapping))); + case 2: if (isabbrev) + emsg(_(e_no_such_abbreviation)); + else if (isdir) + emsg(_(e_no_such_dirhash)); + else + emsg(_(e_no_such_mapping)); break; } } @@ -3204,7 +3256,16 @@ do_exmap(exarg_T *eap, int isabbrev) void ex_abbreviate(exarg_T *eap) { - do_exmap(eap, TRUE); // almost the same as mapping + do_exmap(eap, TRUE, FALSE); // almost the same as mapping +} + +/* + * ":dirhash" + */ + void +ex_dirhash(exarg_T *eap) +{ + do_exmap(eap, FALSE, TRUE); // almost the same as mapping } /* @@ -3221,7 +3282,7 @@ ex_map(exarg_T *eap) msg_outtrans(eap->cmd); msg_putchar('\n'); } - do_exmap(eap, FALSE); + do_exmap(eap, FALSE, FALSE); } /* @@ -3230,7 +3291,7 @@ ex_map(exarg_T *eap) void ex_unmap(exarg_T *eap) { - do_exmap(eap, FALSE); + do_exmap(eap, FALSE, FALSE); } /* diff --git a/src/misc1.c b/src/misc1.c index 917e9d3067e4ba..fb6aa14bbed333 100644 --- a/src/misc1.c +++ b/src/misc1.c @@ -1541,7 +1541,7 @@ expand_env_esc( } #endif } - // home directory + // home directory else if ( src[1] == NUL || vim_ispathsep(src[1]) || vim_strchr((char_u *)" ,\t\n", src[1]) != NULL) @@ -1549,12 +1549,9 @@ expand_env_esc( var = homedir; tail = src + 1; } - else // user directory + else // user directory or dirhash { -#if defined(UNIX) || (defined(VMS) && defined(USER_HOME)) - /* - * Copy ~user to dst[], so we can put a NUL after it. - */ + // Copy ~user to dst[], so we can put a NUL after it. tail = src; var = dst; c = dstlen - 1; @@ -1564,77 +1561,86 @@ expand_env_esc( && !vim_ispathsep(*tail)) *var++ = *tail++; *var = NUL; + + // Prefer dirhash over username. + if (*dst != NUL) + var = find_dirhash(dst + 1); + + if (var == NULL) + { +#if defined(UNIX) || (defined(VMS) && defined(USER_HOME)) # ifdef UNIX - /* - * If the system supports getpwnam(), use it. - * Otherwise, or if getpwnam() fails, the shell is used to - * expand ~user. This is slower and may fail if the shell - * does not support ~user (old versions of /bin/sh). - */ + /* + * If the system supports getpwnam(), use it. + * Otherwise, or if getpwnam() fails, the shell is used to + * expand ~user. This is slower and may fail if the shell + * does not support ~user (old versions of /bin/sh). + */ # if defined(HAVE_GETPWNAM) && defined(HAVE_PWD_H) - { - // Note: memory allocated by getpwnam() is never freed. - // Calling endpwent() apparently doesn't help. - struct passwd *pw = (*dst == NUL) - ? NULL : getpwnam((char *)dst + 1); + { + // Note: memory allocated by getpwnam() is never freed. + // Calling endpwent() apparently doesn't help. + struct passwd *pw = (*dst == NUL) + ? NULL : getpwnam((char *)dst + 1); - var = (pw == NULL) ? NULL : (char_u *)pw->pw_dir; - } - if (var == NULL) + var = (pw == NULL) ? NULL : (char_u *)pw->pw_dir; + } + if (var == NULL) # endif - { - expand_T xpc; + { + expand_T xpc; - ExpandInit(&xpc); - xpc.xp_context = EXPAND_FILES; - var = ExpandOne(&xpc, dst, NULL, - WILD_ADD_SLASH|WILD_SILENT, WILD_EXPAND_FREE); - mustfree = TRUE; - } + ExpandInit(&xpc); + xpc.xp_context = EXPAND_FILES; + var = ExpandOne(&xpc, dst, NULL, + WILD_ADD_SLASH|WILD_SILENT, WILD_EXPAND_FREE); + mustfree = TRUE; + } # else // !UNIX, thus VMS - /* - * USER_HOME is a comma-separated list of - * directories to search for the user account in. - */ - { - char_u test[MAXPATHL], paths[MAXPATHL]; - size_t testlen; - char_u *path, *next_path, *ptr; - stat_T st; - - STRCPY(paths, USER_HOME); - next_path = paths; - while (*next_path) + /* + * USER_HOME is a comma-separated list of + * directories to search for the user account in. + */ { - for (path = next_path; *next_path && *next_path != ','; - next_path++); - if (*next_path) - *next_path++ = NUL; - testlen = vim_snprintf_safelen( - (char *)test, - sizeof(test), - "%s/%s", - path, - dst + 1); - if (mch_stat(test, &st) == 0) + char_u test[MAXPATHL], paths[MAXPATHL]; + size_t testlen; + char_u *path, *next_path, *ptr; + stat_T st; + + STRCPY(paths, USER_HOME); + next_path = paths; + while (*next_path) { - var = alloc(testlen + 1); - if (var != NULL) + for (path = next_path; *next_path && *next_path != ','; + next_path++); + if (*next_path) + *next_path++ = NUL; + testlen = vim_snprintf_safelen( + (char *)test, + sizeof(test), + "%s/%s", + path, + dst + 1); + if (mch_stat(test, &st) == 0) { - STRCPY(var, test); - mustfree = TRUE; + var = alloc(testlen + 1); + if (var != NULL) + { + STRCPY(var, test); + mustfree = TRUE; + } + break; } - break; } } - } # endif // UNIX #else - // cannot expand user's home directory, so don't try - var = NULL; - tail = (char_u *)""; // for gcc + // cannot expand user's home directory, so don't try + var = NULL; + tail = (char_u *)""; // for gcc #endif // UNIX || VMS + } } #ifdef BACKSLASH_IN_FILENAME diff --git a/src/netbeans.c b/src/netbeans.c index 516ac16da63b78..936b346c4bc32a 100644 --- a/src/netbeans.c +++ b/src/netbeans.c @@ -2324,7 +2324,7 @@ special_keys(char_u *args) vim_strncpy((char_u *)&keybuf[i], (char_u *)tok, KEYBUFLEN - i - 1); vim_snprintf(cmdbuf, sizeof(cmdbuf), "<%s> :nbkey %s", keybuf, keybuf); - do_map(MAPTYPE_MAP, (char_u *)cmdbuf, MODE_NORMAL, FALSE); + do_map(MAPTYPE_MAP, (char_u *)cmdbuf, MODE_NORMAL, FALSE, FALSE); } tok = strtok(NULL, " "); } diff --git a/src/proto/map.pro b/src/proto/map.pro index d696aabcd7bc09..99e708e539ca48 100644 --- a/src/proto/map.pro +++ b/src/proto/map.pro @@ -2,7 +2,7 @@ mapblock_T *get_maphash_list(int state, int c); mapblock_T *get_buf_maphash_list(int state, int c); int is_maphash_valid(void); -int do_map(int maptype, char_u *arg, int mode, int abbrev); +int do_map(int maptype, char_u *arg, int mode, int abbrev, int dir); void map_clear_mode(buf_T *buf, int mode, int local, int abbr); int mode_str2flags(char_u *modechars); int map_to_exists(char_u *str, char_u *modechars, int abbr); @@ -10,6 +10,7 @@ int map_to_exists_mode(char_u *rhs, int mode, int abbr); char_u *set_context_in_map_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forceit, int isabbrev, int isunmap, cmdidx_T cmdidx); int ExpandMappings(char_u *pat, regmatch_T *regmatch, int *numMatches, char_u ***matches); int check_abbr(int c, char_u *ptr, int col, int mincol); +char_u *find_dirhash(char_u *name); char_u *eval_map_expr(mapblock_T *mp, int c); char_u *vim_strsave_escape_csi(char_u *p); void vim_unescape_csi(char_u *p); @@ -28,6 +29,7 @@ int langmap_adjust_mb(int c); void langmap_init(void); char *did_set_langmap(optset_T *args); void ex_abbreviate(exarg_T *eap); +void ex_dirhash(exarg_T *eap); void ex_map(exarg_T *eap); void ex_unmap(exarg_T *eap); void ex_mapclear(exarg_T *eap); diff --git a/src/structs.h b/src/structs.h index 1fdc2ee1b90635..13561a4948932e 100644 --- a/src/structs.h +++ b/src/structs.h @@ -3243,6 +3243,7 @@ struct file_buffer // First abbreviation local to a buffer. mapblock_T *b_first_abbr; + mapblock_T *b_first_dir; // User commands local to the buffer. garray_T b_ucmds; diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak index 07bea0702a709b..41421f26de8032 100644 --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -130,6 +130,7 @@ NEW_TESTS = \ test_delete \ test_diffmode \ test_digraph \ + test_dirhash \ test_display \ test_edit \ test_environ \ @@ -423,6 +424,7 @@ NEW_TESTS_RES = \ test_delete.res \ test_diffmode.res \ test_digraph.res \ + test_dirhash.res \ test_display.res \ test_edit.res \ test_environ.res \ diff --git a/src/testdir/test_dirhash.vim b/src/testdir/test_dirhash.vim new file mode 100644 index 00000000000000..20548ed50b5d2e --- /dev/null +++ b/src/testdir/test_dirhash.vim @@ -0,0 +1,91 @@ +" Tests for :dirhash + +vim9script + +def ListHashes(): list + return execute(':dirhash')->trim()->split('[ \t\n]\+') +enddef + +def AssertHashes(want: list) + assert_equal(want, ListHashes()) +enddef + +# Test :dirhash and :undirhash +def g:Test_dirhash() + :mapclear + :mapclear! + + :dirhash + AssertHashes(['No', 'dirhash', 'found']) + + # :dirhash a directory, make sure :dirhash reports it. + :dirhash dir /hello/world + AssertHashes(['dir', '/hello/world']) + + # Add more, and remove the previous. + :dirhash s32 C:\Windows\system32 + :dirhash vim /usr/share/vim/vim92 + :undirhash dir + AssertHashes(['vim', '/usr/share/vim/vim92', 's32', 'C:\Windows\system32']) + + # Make sure :undirhash errors for unknown hashes. + var did_err = false + try + :undirhash unknown + catch /E1573/ + did_err = true + endtry + assert_equal(true, did_err, 'expected E1573 error on :undirhash unknown') + AssertHashes(['vim', '/usr/share/vim/vim92', 's32', 'C:\Windows\system32']) + + # Should not affect maplist() + assert_equal([], maplist()) + + :undirhash s32 + :undirhash vim +enddef + +# Make sure works as expected, and takes takes precedence over global. +def g:Test_dirhash_buffer() + :dirhash dir /buf + :dirhash dir /global + + AssertHashes(['dir', '@/buf', 'dir', '/global']) + assert_equal('/buf/file', expandcmd('~dir/file')) + + :undirhash dir + AssertHashes(['dir', '/global']) + assert_equal('/global/file', expandcmd('~dir/file')) + + :undirhash dir +enddef + +# Test expand() and expandcmd() +def g:Test_dirhash_expand() + :dirhash zxc /abc/def + + assert_equal('/abc/def', expandcmd('~zxc')) + assert_equal('/abc/def/', expandcmd('~zxc/')) + assert_equal('/abc/def/foo', expandcmd('~zxc/foo')) + assert_equal('~unknown', expandcmd('~unknown')) + + assert_equal('/abc/def', expand('~zxc')) + assert_equal('/abc/def/', expand('~zxc/')) + assert_equal('~unknown', expand('~unknown')) + + :undirhash zxc +enddef + +# Test on commandline +def g:Test_dirhash_cmdline() + :dirhash zxc /tmp + + :next ~zxc/file + call assert_equal('/tmp/file', expand('%')) + + :undirhash zxc +enddef + +defcompile + +# vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker From f90aa5551586ecb814a763b9ad397e8f987393d9 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Tue, 3 Mar 2026 02:14:47 +0000 Subject: [PATCH 2/2] Turns out macOS comes with an "unknown" user by default --- src/testdir/test_dirhash.vim | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/testdir/test_dirhash.vim b/src/testdir/test_dirhash.vim index 20548ed50b5d2e..cafdecedc4b129 100644 --- a/src/testdir/test_dirhash.vim +++ b/src/testdir/test_dirhash.vim @@ -31,11 +31,11 @@ def g:Test_dirhash() # Make sure :undirhash errors for unknown hashes. var did_err = false try - :undirhash unknown + :undirhash xxunknownuserxx catch /E1573/ did_err = true endtry - assert_equal(true, did_err, 'expected E1573 error on :undirhash unknown') + assert_equal(true, did_err, 'expected E1573 error on :undirhash xxunknownuserxx') AssertHashes(['vim', '/usr/share/vim/vim92', 's32', 'C:\Windows\system32']) # Should not affect maplist() @@ -67,11 +67,11 @@ def g:Test_dirhash_expand() assert_equal('/abc/def', expandcmd('~zxc')) assert_equal('/abc/def/', expandcmd('~zxc/')) assert_equal('/abc/def/foo', expandcmd('~zxc/foo')) - assert_equal('~unknown', expandcmd('~unknown')) + assert_equal('~xxunknownuserxx', expandcmd('~xxunknownuserxx')) assert_equal('/abc/def', expand('~zxc')) assert_equal('/abc/def/', expand('~zxc/')) - assert_equal('~unknown', expand('~unknown')) + assert_equal('~xxunknownuserxx', expand('~xxunknownuserxx')) :undirhash zxc enddef