diff --git a/runtime/menu.vim b/runtime/menu.vim index 662eea9403..768bd0b769 100644 --- a/runtime/menu.vim +++ b/runtime/menu.vim @@ -2,7 +2,7 @@ " You can also use this as a start for your own set of menus. " " Maintainer: The Vim Project -" Last Change: 2023 Aug 10 +" Last Change: 2025 Aug 10 " Former Maintainer: Bram Moolenaar " Note that ":an" (short for ":anoremenu") is often used to make a menu work @@ -790,8 +790,21 @@ func s:BMShow(...) endfunc func s:BMHash(name) - " Make name all upper case, so that chars are between 32 and 96 - let nm = substitute(a:name, ".*", '\U\0', "") + " Create a sortable numeric hash of the name. This number has to be within + " the bounds of a signed 32-bit integer as this is what Vim GUI uses + " internally for the index. + + " Make name all upper case, so that alphanumeric chars are between 32 and 96 + let nm = toupper(a:name) + + if char2nr(nm->slice(0, 1)) < 32 || char2nr(nm->slice(0, 1)) > 96 + " We don't have an ASCII character, so just return the raw character value + " for first character (clamped to 2^31) and set the high bit to make it + " sort after other items. This means only the first character will be + " sorted, unfortunately. + return or(and(char2nr(nm), 0x7fffffff), 0x40000000) + endif + if has("ebcdic") " HACK: Replace all non alphabetics with 'Z' " Just to make it work for now. @@ -800,13 +813,19 @@ func s:BMHash(name) else let sp = char2nr(' ') endif - " convert first six chars into a number for sorting: - return (char2nr(nm[0]) - sp) * 0x800000 + (char2nr(nm[1]) - sp) * 0x20000 + (char2nr(nm[2]) - sp) * 0x1000 + (char2nr(nm[3]) - sp) * 0x80 + (char2nr(nm[4]) - sp) * 0x20 + (char2nr(nm[5]) - sp) + " convert first five chars into a number for sorting by compressing each + " char into 5 bits (0-63), to a total of 30 bits. If any character is not + " ASCII, it will simply be clamped to prevent overflow. + " Nvim: no << operator + return (max([0, min([63, char2nr(nm->slice(0, 1)) - sp])]) * 0x1000000) + + \ (max([0, min([63, char2nr(nm->slice(1, 2)) - sp])]) * 0x40000) + + \ (max([0, min([63, char2nr(nm->slice(2, 3)) - sp])]) * 0x1000) + + \ (max([0, min([63, char2nr(nm->slice(3, 4)) - sp])]) * 0x40) + + \ max([0, min([63, char2nr(nm->slice(4, 5)) - sp])]) endfunc func s:BMHash2(name) - let nm = substitute(a:name, ".", '\L\0', "") - " Not exactly right for EBCDIC... + let nm = tolower(a:name->slice(0, 1)) if nm[0] < 'a' || nm[0] > 'z' return '&others.' elseif nm[0] <= 'd' diff --git a/test/old/testdir/test_gui.vim b/test/old/testdir/test_gui.vim index 9780d22e4f..f2136f58ac 100644 --- a/test/old/testdir/test_gui.vim +++ b/test/old/testdir/test_gui.vim @@ -44,4 +44,37 @@ func Test_colorscheme() redraw! endfunc +" Test that Buffers menu generates the correct index for different buffer +" names for sorting. +func Test_Buffers_Menu() + doautocmd LoadBufferMenu VimEnter + + " Non-ASCII characters only use the first character as idx + let idx_emoji = or(char2nr('πŸ˜‘'), 0x40000000) + + " Only first five letters are used for alphanumeric: + " ('a'-32) << 24 + ('b'-32) << 18 + ('c'-32) << 12 + ('d'-32) << 6 + ('e'-32) + let idx_abcde = 0x218A3925 + " ('a'-32) << 24 + ('b'-32) << 18 + ('c'-32) << 12 + ('d'-32) << 6 + ('f'-32) + let idx_abcdf = 0x218A3926 + " ('a'-32) << 24 + 63 (clamped) << 18 + ('c'-32) << 12 + ('d'-32) << 6 + ('e'-32) + let idx_a_emoji_cde = 0x21FE3925 + + let names = ['πŸ˜‘', 'πŸ˜‘1', 'πŸ˜‘2', 'abcde', 'abcdefghi', 'abcdf', 'aπŸ˜‘cde'] + let indices = [idx_emoji, idx_emoji, idx_emoji, idx_abcde, idx_abcde, idx_abcdf, idx_a_emoji_cde] + for i in range(len(names)) + let name = names[i] + let idx = indices[i] + exe ':badd ' .. name + let nr = bufnr('$') + + let cmd = printf(':amenu Buffers.%s\ (%d)', name, nr) + let menu = split(execute(cmd), '\n')[1] + call assert_inrange(0, 0x7FFFFFFF, idx) + call assert_match('^' .. idx .. ' '.. name, menu) + endfor + + %bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab