" You can also use this as a start for your own set of menus.
"
" Maintainer: The Vim Project <https://github.com/vim/vim>
-" Last Change: 2023 Aug 10
+" Last Change: 2025 Aug 10
" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Note that ":an" (short for ":anoremenu") is often used to make a menu work
enddef
def s:BMHash(name: string): number
- # Make name all upper case, so that chars are between 32 and 96
- var nm = substitute(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
+ var nm = toupper(name)
+
+ if char2nr(nm[0]) < 32 || char2nr(nm[0]) > 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
+
var sp: number
if has("ebcdic")
# HACK: Replace all non alphabetics with 'Z'
else
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.
+ return (max([0, min([63, char2nr(nm[0]) - sp])]) << 24) +
+ (max([0, min([63, char2nr(nm[1]) - sp])]) << 18) +
+ (max([0, min([63, char2nr(nm[2]) - sp])]) << 12) +
+ (max([0, min([63, char2nr(nm[3]) - sp])]) << 6) +
+ max([0, min([63, char2nr(nm[4]) - sp])])
enddef
def s:BMHash2(name: string): string
- var nm = substitute(name, ".", '\L\0', "")
+ var nm = tolower(name[0])
if nm[0] < 'a' || nm[0] > 'z'
return '&others.'
elseif nm[0] <= 'd'
call assert_equal(['g:cursorhold_triggered=0'], found)
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