From 7e81ce1019626977f7dbab44fb2abb5517993359 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 25 Aug 2023 12:05:54 +0800 Subject: [PATCH] feat(guide): rewrite leader guide in lua --- autoload/SpaceVim/mapping/guide.vim | 1486 ++++++++++--------- bundle/neodev.nvim/types/nightly/vim.fn.lua | 2 +- lua/spacevim/api/vim.lua | 4 + lua/spacevim/api/vim/buffer.lua | 4 + lua/spacevim/api/vim/keys.lua | 46 +- lua/spacevim/plugin/guide.lua | 1226 ++++++++------- 6 files changed, 1510 insertions(+), 1258 deletions(-) diff --git a/autoload/SpaceVim/mapping/guide.vim b/autoload/SpaceVim/mapping/guide.vim index 362b3231e..86e66fc97 100644 --- a/autoload/SpaceVim/mapping/guide.vim +++ b/autoload/SpaceVim/mapping/guide.vim @@ -6,7 +6,6 @@ " License: GPLv3 "============================================================================= scriptencoding utf-8 - if exists('s:save_cpo') finish endif @@ -14,781 +13,830 @@ endif let s:save_cpo = &cpo set cpo&vim -" Load SpaceVim API -let s:CMP = SpaceVim#api#import('vim#compatible') -let s:STR = SpaceVim#api#import('data#string') -let s:KEY = SpaceVim#api#import('vim#key') -let s:VIM = SpaceVim#api#import('vim') -let s:BUFFER = SpaceVim#api#import('vim#buffer') -if has('nvim') - let s:FLOATING = SpaceVim#api#import('neovim#floating') +if has('nvim-0.9.0') + + function! SpaceVim#mapping#guide#parse_mappings() abort " {{{ + lua require("spacevim.plugin.guide").parse_mappings() + endfunction "}}} + function! SpaceVim#mapping#guide#start(vis, dict) abort " {{{ + lua require("spacevim.plugin.guide").start( + \ require("spacevim").eval("a:vis"), + \ require("spacevim").eval("a:dict") + \ ) + endfunction "}}} + function! SpaceVim#mapping#guide#has_configuration() abort "{{{ + return luaeval('require("spacevim.plugin.guide").has_configuration()') + endfunction "}}} + function! SpaceVim#mapping#guide#start_by_prefix(vis, key) abort " {{{ + lua require("spacevim.plugin.guide").start_by_prefix( + \ require("spacevim").eval("a:vis"), + \ require("spacevim").eval("a:key") + \ ) + endfunction "}}} + function! SpaceVim#mapping#guide#register_displayname(lhs, name) abort + lua require("spacevim.plugin.guide").register_displayname( + \ require("spacevim").eval("a:lhs"), + \ require("spacevim").eval("a:name") + \ ) + endfunction "}}} + function! SpaceVim#mapping#guide#populate_dictionary(key, dictname) abort " {{{ + lua require("spacevim.plugin.guide").populate_dictionary( + \ require("spacevim").eval("a:key"), + \ require("spacevim").eval("a:dictname") + \ ) + endfunction "}}} + function! SpaceVim#mapping#guide#register_prefix_descriptions(key, dictname) abort " {{{ + lua require("spacevim.plugin.guide").register_prefix_descriptions( + \ require("spacevim").eval("a:key"), + \ require("spacevim").eval("a:dictname") + \ ) + endfunction "}}} + function! SpaceVim#mapping#guide#displayfunc() abort + lua require("spacevim.plugin.guide").displayfunc() + endfunction else - let s:FLOATING = SpaceVim#api#import('vim#floating') -endif -let s:SL = SpaceVim#api#import('vim#statusline') -" guide specific var -let s:winid = -1 -let s:bufnr = -1 -let s:prefix_key_inp = [] -let s:lmap = {} -" this should be the history of s:lmap and s:guide_group -let s:undo_history = [] -function! SpaceVim#mapping#guide#has_configuration() abort "{{{ - return exists('s:desc_lookup') -endfunction "}}} - -function! SpaceVim#mapping#guide#register_prefix_descriptions(key, dictname) abort " {{{ - let key = a:key ==? '' ? ' ' : a:key - if !exists('s:desc_lookup') - call s:create_cache() - endif - if strlen(key) == 0 - let s:desc_lookup['top'] = a:dictname - return - endif - if !has_key(s:desc_lookup, key) - let s:desc_lookup[key] = a:dictname - endif -endfunction "}}} -function! s:create_cache() abort " {{{ - let s:desc_lookup = {} - let s:cached_dicts = {} -endfunction " }}} -function! s:create_target_dict(key) abort " {{{ - if has_key(s:desc_lookup, 'top') - let toplevel = deepcopy({s:desc_lookup['top']}) - let tardict = s:toplevel ? toplevel : get(toplevel, a:key, {}) - let mapdict = s:cached_dicts[a:key] - call s:merge(tardict, mapdict) - elseif has_key(s:desc_lookup, a:key) - let tardict = deepcopy({s:desc_lookup[a:key]}) - let mapdict = s:cached_dicts[a:key] - call s:merge(tardict, mapdict) + " Load SpaceVim API + let s:CMP = SpaceVim#api#import('vim#compatible') + let s:STR = SpaceVim#api#import('data#string') + let s:KEY = SpaceVim#api#import('vim#key') + let s:VIM = SpaceVim#api#import('vim') + let s:BUFFER = SpaceVim#api#import('vim#buffer') + if has('nvim') + let s:FLOATING = SpaceVim#api#import('neovim#floating') else - let tardict = s:cached_dicts[a:key] + let s:FLOATING = SpaceVim#api#import('vim#floating') endif - return tardict -endfunction " }}} -function! s:merge(dict_t, dict_o) abort " {{{ - let target = a:dict_t - let other = a:dict_o - for k in keys(target) - if type(target[k]) == type({}) && has_key(other, k) - if type(other[k]) == type({}) - if has_key(target[k], 'name') - let other[k].name = target[k].name - endif - call s:merge(target[k], other[k]) - elseif type(other[k]) == type([]) - if g:leaderGuide_flatten == 0 || type(target[k]) == type({}) - let target[k.'m'] = target[k] - endif - let target[k] = other[k] - if has_key(other, k.'m') && type(other[k.'m']) == type({}) - call s:merge(target[k.'m'], other[k.'m']) - endif - endif + let s:SL = SpaceVim#api#import('vim#statusline') + let s:LOG =SpaceVim#logger#derive('guide') + + " guide specific var + let s:winid = -1 + let s:bufnr = -1 + let s:prefix_key_inp = [] + let s:lmap = {} + " this should be the history of s:lmap and s:guide_group + let s:undo_history = [] + let s:registered_name = {} + + function! SpaceVim#mapping#guide#has_configuration() abort "{{{ + return exists('s:desc_lookup') + endfunction "}}} + + function! SpaceVim#mapping#guide#register_prefix_descriptions(key, dictname) abort " {{{ + let key = a:key ==? '' ? ' ' : a:key + if !exists('s:desc_lookup') + call s:create_cache() endif - endfor - call extend(target, other, 'keep') -endfunction " }}} - -" @vimlint(EVL103, 1, a:dictname) -function! SpaceVim#mapping#guide#populate_dictionary(key, dictname) abort " {{{ - call s:start_parser(a:key, s:cached_dicts[a:key]) -endfunction " }}} -" @vimlint(EVL103, 0, a:dictname) - -function! SpaceVim#mapping#guide#parse_mappings() abort " {{{ - for [k, v] in items(s:cached_dicts) - call s:start_parser(k, v) - endfor -endfunction " }}} - - -function! s:start_parser(key, dict) abort " {{{ - if a:key ==# '[KEYs]' - return - endif - let key = a:key ==? ' ' ? '' : a:key - - 0verbose let readmap = s:CMP.execute('map ' . key, 'silent') - - let lines = split(readmap, "\n") - let visual = s:vis ==# 'gv' ? 1 : 0 - - for line in lines - let mapd = maparg(split(line[3:])[0], line[0], 0, 1) - if mapd.lhs ==# '\\' - let mapd.feedkeyargs = '' - elseif mapd.noremap == 1 - let mapd.feedkeyargs = 'nt' + if strlen(key) == 0 + let s:desc_lookup['top'] = a:dictname + elseif !has_key(s:desc_lookup, key) + let s:desc_lookup[key] = a:dictname + endif + call s:LOG.debug('desc_lookup is:' . string(s:desc_lookup)) + endfunction "}}} + function! s:create_cache() abort " {{{ + let s:desc_lookup = {} + let s:cached_dicts = {} + endfunction " }}} + function! s:create_target_dict(key) abort " {{{ + if has_key(s:desc_lookup, 'top') + " use {expr} to eval viml value + let toplevel = deepcopy({s:desc_lookup['top']}) + let tardict = s:toplevel ? toplevel : get(toplevel, a:key, {}) + let mapdict = s:cached_dicts[a:key] + call s:merge(tardict, mapdict) + elseif has_key(s:desc_lookup, a:key) + let tardict = deepcopy({s:desc_lookup[a:key]}) + let mapdict = s:cached_dicts[a:key] + call s:merge(tardict, mapdict) else - let mapd.feedkeyargs = 'mt' + let tardict = s:cached_dicts[a:key] endif - if mapd.lhs =~# '.*' || mapd.lhs =~# '.*' - continue - endif - let mapd.display = s:format_displaystring(mapd.rhs) - let mapd.lhs = substitute(mapd.lhs, key, '', '') - let mapd.lhs = substitute(mapd.lhs, '', ' ', 'g') - let mapd.lhs = substitute(mapd.lhs, '', '', 'g') - let mapd.rhs = substitute(mapd.rhs, '', ''.mapd['sid'].'_', 'g') - if mapd.lhs !=# '' && mapd.display !~# 'LeaderGuide.*' - let mapd.lhs = s:string_to_keys(mapd.lhs) - if (visual && match(mapd.mode, '[vx ]') >= 0) || - \ (!visual && match(mapd.mode, '[vx]') == -1) - call s:add_map_to_dict(mapd, 0, a:dict) - endif - endif - endfor -endfunction " }}} - -function! s:add_map_to_dict(map, level, dict) abort " {{{ - if len(a:map.lhs) > a:level+1 - let curkey = a:map.lhs[a:level] - let nlevel = a:level+1 - if !has_key(a:dict, curkey) - let a:dict[curkey] = { 'name' : g:leaderGuide_default_group_name } - " mapping defined already, flatten this map - elseif type(a:dict[curkey]) == type([]) && g:leaderGuide_flatten - let cmd = s:escape_mappings(a:map) - let curkey = join(a:map.lhs[a:level+0:], '') - let nlevel = a:level - if !has_key(a:dict, curkey) - let a:dict[curkey] = [cmd, a:map.display] - endif - elseif type(a:dict[curkey]) == type([]) && g:leaderGuide_flatten == 0 - let cmd = s:escape_mappings(a:map) - let curkey = curkey.'m' - if !has_key(a:dict, curkey) - let a:dict[curkey] = { 'name' : g:leaderGuide_default_group_name } - endif - endif - " next level - if type(a:dict[curkey]) == type({}) - call s:add_map_to_dict(a:map, nlevel, a:dict[curkey]) - endif - else - let cmd = s:escape_mappings(a:map) - if !has_key(a:dict, a:map.lhs[a:level]) - let a:dict[a:map.lhs[a:level]] = [cmd, a:map.display] - " spot is taken already, flatten existing submaps - elseif type(a:dict[a:map.lhs[a:level]]) == type({}) && g:leaderGuide_flatten - let childmap = s:flattenmap(a:dict[a:map.lhs[a:level]], a:map.lhs[a:level]) - for it in keys(childmap) - let a:dict[it] = childmap[it] - endfor - let a:dict[a:map.lhs[a:level]] = [cmd, a:map.display] - endif - endif -endfunction " }}} -" @vimlint(EVL111, 1, Fun) -function! s:format_displaystring(map) abort " {{{ - let g:leaderGuide#displayname = a:map - for Fun in g:leaderGuide_displayfunc - call Fun() - endfor - let display = g:leaderGuide#displayname - unlet g:leaderGuide#displayname - return display -endfunction " }}} -" @vimlint(EVL111, 0, Fun) -function! s:flattenmap(dict, str) abort " {{{ - let ret = {} - for kv in keys(a:dict) - if type(a:dict[kv]) == type([]) - let toret = {} - let toret[a:str.kv] = a:dict[kv] - return toret - elseif type(a:dict[kv]) == type({}) - call extend(ret, s:flattenmap(a:dict[kv], a:str.kv)) - endif - endfor - return ret -endfunction " }}} - - -function! s:escape_mappings(mapping) abort " {{{ - let rstring = substitute(a:mapping.rhs, '\', '\\\\', 'g') - let rstring = substitute(rstring, '<\([^<>]*\)>', '\\<\1>', 'g') - let rstring = substitute(rstring, '"', '\\"', 'g') - let rstring = 'call feedkeys("'.rstring.'", "'.a:mapping.feedkeyargs.'")' - return rstring -endfunction " }}} -function! s:string_to_keys(input) abort " {{{ - " Avoid special case: <> - let retlist = [] - if match(a:input, '<.\+>') != -1 - let si = 0 - let go = 1 - while si < len(a:input) - if go - if a:input[si] ==# ' ' - call add(retlist, '[SPC]') - else - call add(retlist, a:input[si]) + return tardict + endfunction " }}} + function! s:merge(dict_t, dict_o) abort " {{{ + let target = a:dict_t + let other = a:dict_o + for k in keys(target) + if type(target[k]) == type({}) && has_key(other, k) + if type(other[k]) == type({}) + if has_key(target[k], 'name') + let other[k].name = target[k].name + endif + call s:merge(target[k], other[k]) + elseif type(other[k]) == type([]) + if g:leaderGuide_flatten == 0 || type(target[k]) == type({}) + let target[k.'m'] = target[k] + endif + let target[k] = other[k] + if has_key(other, k.'m') && type(other[k.'m']) == type({}) + call s:merge(target[k.'m'], other[k.'m']) + endif endif - else - let retlist[-1] .= a:input[si] - endif - if a:input[si] ==? '<' - let go = 0 - elseif a:input[si] ==? '>' - let go = 1 - end - let si += 1 - endw - else - for it in split(a:input, '\zs') - if it ==# ' ' - call add(retlist, '[SPC]') - else - call add(retlist, it) endif endfor - endif - return retlist -endfunction " }}} -function! s:escape_keys(inp) abort " {{{ - let ret = substitute(a:inp, '<', '', '') - return substitute(ret, '|', '', '') -endfunction " }}} + call extend(target, other, 'keep') + endfunction " }}} -function! s:calc_layout() abort " {{{ - let ret = {} - let smap = filter(copy(s:lmap), 'v:key !=# "name"') - let ret.n_items = len(smap) - let length = values(map(smap, - \ 'strdisplaywidth(repeat(" ", 8 - strlen(v:key)) . "[".v:key."]".'. - \ '(type(v:val) == type({}) ? v:val["name"] : v:val[1]))')) - let maxlength = max(length) + g:leaderGuide_hspace - if g:leaderGuide_vertical - let ret.n_rows = winheight(0) - 2 - let ret.n_cols = ret.n_items / ret.n_rows + (ret.n_items != ret.n_rows) - let ret.col_width = maxlength - let ret.win_dim = ret.n_cols * ret.col_width - else - let ret.n_cols = winwidth(s:winid) >= maxlength ? winwidth(s:winid) / maxlength : 1 - let ret.col_width = winwidth(s:winid) / ret.n_cols - let ret.n_rows = ret.n_items / ret.n_cols + (fmod(ret.n_items,ret.n_cols) > 0 ? 1 : 0) - let ret.win_dim = ret.n_rows - endif - return ret -endfunction " }}} + " @vimlint(EVL103, 1, a:dictname) + function! SpaceVim#mapping#guide#populate_dictionary(key, dictname) abort " {{{ + call s:start_parser(a:key, s:cached_dicts[a:key]) + endfunction " }}} + " @vimlint(EVL103, 0, a:dictname) -" icon -> number -> A-Za-z -" 65-90 97-122 -function! s:get_key_number(key) abort - return char2nr(a:key ==# '[SPC]' ? ' ' : a:key ==? '' ? "\t" : a:key) -endfunction + function! SpaceVim#mapping#guide#parse_mappings() abort " {{{ + for [k, v] in items(s:cached_dicts) + call s:start_parser(k, v) + endfor + endfunction " }}} -function! s:compare_key(i1, i2) abort - let a = s:get_key_number(a:i1) - let b = s:get_key_number(a:i2) - if a - b == 32 && a >= 97 && a <= 122 - return -1 - elseif b - a == 32 && b >= 97 && b <= 122 - return 1 - elseif a >= 97 && a <= 122 && b >= 97 && b <= 122 - return a == b ? 0 : a > b ? 1 : -1 - elseif a >= 65 && a <= 90 && b >= 65 && b <= 90 - return a == b ? 0 : a > b ? 1 : -1 - elseif a >= 97 && a <= 122 && b >= 65 && b <= 90 - return s:compare_key(nr2char(a), nr2char(b + 32)) - elseif a >= 65 && a <= 90 && b >= 97 && b <= 122 - return s:compare_key(nr2char(a), nr2char(b - 32)) - endif - return a == b ? 0 : a > b ? 1 : -1 -endfunction -function! s:create_string(layout) abort " {{{ - let l = a:layout - let l.capacity = l.n_rows * l.n_cols - let overcap = l.capacity - l.n_items - let overh = l.n_cols - overcap - let n_rows = l.n_rows - 1 + function! s:start_parser(key, dict) abort " {{{ + if a:key ==# '[KEYs]' + return + endif + let key = a:key ==? ' ' ? '' : a:key - let rows = [] - let row = 0 - let col = 0 - let smap = sort(filter(keys(s:lmap), 'v:val !=# "name"'), function('s:compare_key')) - for k in smap - let offset = repeat(' ', 8 - strlen(k)) - let desc = type(s:lmap[k]) == type({}) ? s:lmap[k].name : s:lmap[k][1] - if g:spacevim_leader_guide_theme == 'whichkey' - let displaystring = offset . k .' -> '.desc + 0verbose let readmap = s:CMP.execute('map ' . key, 'silent') + + let lines = split(readmap, "\n") + let visual = s:vis ==# 'gv' ? 1 : 0 + + for line in lines + let mapd = maparg(split(line[3:])[0], line[0], 0, 1) + if mapd.lhs ==# '\\' + let mapd.feedkeyargs = '' + elseif mapd.noremap == 1 + let mapd.feedkeyargs = 'nt' + else + let mapd.feedkeyargs = 'mt' + endif + if mapd.lhs =~# '.*' || mapd.lhs =~# '.*' + continue + endif + let mapd.display = s:format_displaystring(mapd.rhs) + let mapd.lhs = substitute(mapd.lhs, key, '', '') + let mapd.lhs = substitute(mapd.lhs, '', ' ', 'g') + let mapd.lhs = substitute(mapd.lhs, '', '', 'g') + let mapd.rhs = substitute(mapd.rhs, '', ''.mapd['sid'].'_', 'g') + if mapd.lhs !=# '' && mapd.display !~# 'LeaderGuide.*' + let mapd.lhs = s:string_to_keys(mapd.lhs) + if (visual && match(mapd.mode, '[vx ]') >= 0) || + \ (!visual && match(mapd.mode, '[vx]') == -1) + call s:add_map_to_dict(mapd, 0, a:dict) + endif + endif + endfor + endfunction " }}} + + function! s:add_map_to_dict(map, level, dict) abort " {{{ + if len(a:map.lhs) > a:level+1 + let curkey = a:map.lhs[a:level] + let nlevel = a:level+1 + if !has_key(a:dict, curkey) + let a:dict[curkey] = { 'name' : g:leaderGuide_default_group_name } + " mapping defined already, flatten this map + elseif type(a:dict[curkey]) == type([]) && g:leaderGuide_flatten + let cmd = s:escape_mappings(a:map) + let curkey = join(a:map.lhs[a:level+0:], '') + let nlevel = a:level + if !has_key(a:dict, curkey) + let a:dict[curkey] = [cmd, a:map.display] + endif + elseif type(a:dict[curkey]) == type([]) && g:leaderGuide_flatten == 0 + let cmd = s:escape_mappings(a:map) + let curkey = curkey.'m' + if !has_key(a:dict, curkey) + let a:dict[curkey] = { 'name' : g:leaderGuide_default_group_name } + endif + endif + " next level + if type(a:dict[curkey]) == type({}) + call s:add_map_to_dict(a:map, nlevel, a:dict[curkey]) + endif else - let displaystring = offset . '['. k .'] '.desc + let cmd = s:escape_mappings(a:map) + if !has_key(a:dict, a:map.lhs[a:level]) + let a:dict[a:map.lhs[a:level]] = [cmd, a:map.display] + " spot is taken already, flatten existing submaps + elseif type(a:dict[a:map.lhs[a:level]]) == type({}) && g:leaderGuide_flatten + let childmap = s:flattenmap(a:dict[a:map.lhs[a:level]], a:map.lhs[a:level]) + for it in keys(childmap) + let a:dict[it] = childmap[it] + endfor + let a:dict[a:map.lhs[a:level]] = [cmd, a:map.display] + endif endif - let crow = get(rows, row, []) - if empty(crow) - call add(rows, crow) - endif - call add(crow, displaystring) - call add(crow, repeat(' ', l.col_width - strdisplaywidth(displaystring))) + endfunction " }}} + " @vimlint(EVL111, 1, Fun) + function! s:format_displaystring(map) abort " {{{ + let g:leaderGuide#displayname = a:map + for Fun in g:leaderGuide_displayfunc + call Fun() + endfor + let display = g:leaderGuide#displayname + unlet g:leaderGuide#displayname + return display + endfunction " }}} + " @vimlint(EVL111, 0, Fun) + function! s:flattenmap(dict, str) abort " {{{ + let ret = {} + for kv in keys(a:dict) + if type(a:dict[kv]) == type([]) + let toret = {} + let toret[a:str.kv] = a:dict[kv] + return toret + elseif type(a:dict[kv]) == type({}) + call extend(ret, s:flattenmap(a:dict[kv], a:str.kv)) + endif + endfor + return ret + endfunction " }}} - if !g:leaderGuide_sort_horizontal - if row >= n_rows - 1 - if overh > 0 && row < n_rows - let overh -= 1 - let row += 1 + + function! s:escape_mappings(mapping) abort " {{{ + let rstring = substitute(a:mapping.rhs, '\', '\\\\', 'g') + let rstring = substitute(rstring, '<\([^<>]*\)>', '\\<\1>', 'g') + let rstring = substitute(rstring, '"', '\\"', 'g') + let rstring = 'call feedkeys("'.rstring.'", "'.a:mapping.feedkeyargs.'")' + return rstring + endfunction " }}} + function! s:string_to_keys(input) abort + " Avoid special case: <> + let retlist = [] + if match(a:input, '<.\+>') != -1 + let si = 0 + let go = 1 + while si < len(a:input) + if go + if a:input[si] ==# ' ' + call add(retlist, '[SPC]') + else + call add(retlist, a:input[si]) + endif else - let row = 0 - let col += 1 + let retlist[-1] .= a:input[si] + endif + if a:input[si] ==? '<' + let go = 0 + elseif a:input[si] ==? '>' + let go = 1 + endif + let si += 1 + endwhile + else + for it in split(a:input, '\zs') + if it ==# ' ' + call add(retlist, '[SPC]') + else + call add(retlist, it) + endif + endfor + endif + return retlist + endfunction + function! s:escape_keys(inp) abort " {{{ + let ret = substitute(a:inp, '<', '', '') + return substitute(ret, '|', '', '') + endfunction " }}} + + function! s:calc_layout() abort " {{{ + let ret = {} + let smap = filter(copy(s:lmap), 'v:key !=# "name"') + let ret.n_items = len(smap) + let length = values(map(smap, + \ 'strdisplaywidth(repeat(" ", 8 - strlen(v:key)) . "[".v:key."]".'. + \ '(type(v:val) == type({}) ? v:val["name"] : v:val[1]))')) + let maxlength = max(length) + g:leaderGuide_hspace + call s:LOG.debug('maxlength is:' . maxlength) + if g:leaderGuide_vertical + let ret.n_rows = winheight(0) - 2 + let ret.n_cols = ret.n_items / ret.n_rows + (ret.n_items != ret.n_rows) + let ret.col_width = maxlength + let ret.win_dim = ret.n_cols * ret.col_width + else + let ret.n_cols = winwidth(s:winid) >= maxlength ? winwidth(s:winid) / maxlength : 1 + let ret.col_width = winwidth(s:winid) / ret.n_cols + let ret.n_rows = ret.n_items / ret.n_cols + (fmod(ret.n_items,ret.n_cols) > 0 ? 1 : 0) + let ret.win_dim = ret.n_rows + endif + call s:LOG.debug('layout is:' . string(ret)) + return ret + endfunction " }}} + + " icon -> number -> A-Za-z + " 65-90 97-122 + function! s:get_key_number(key) abort + return char2nr(a:key ==# '[SPC]' ? ' ' : a:key ==? '' ? "\t" : a:key) + endfunction + + function! s:compare_key(i1, i2) abort + let a = s:get_key_number(a:i1) + let b = s:get_key_number(a:i2) + if a - b == 32 && a >= 97 && a <= 122 + return -1 + elseif b - a == 32 && b >= 97 && b <= 122 + return 1 + elseif a >= 97 && a <= 122 && b >= 97 && b <= 122 + return a == b ? 0 : a > b ? 1 : -1 + elseif a >= 65 && a <= 90 && b >= 65 && b <= 90 + return a == b ? 0 : a > b ? 1 : -1 + elseif a >= 97 && a <= 122 && b >= 65 && b <= 90 + return s:compare_key(nr2char(a), nr2char(b + 32)) + elseif a >= 65 && a <= 90 && b >= 97 && b <= 122 + return s:compare_key(nr2char(a), nr2char(b - 32)) + endif + return a == b ? 0 : a > b ? 1 : -1 + endfunction + + function! s:create_string(layout) abort " {{{ + let l = a:layout + let l.capacity = l.n_rows * l.n_cols + let overcap = l.capacity - l.n_items + let overh = l.n_cols - overcap + let n_rows = l.n_rows - 1 + + let rows = [] + let row = 0 + let col = 0 + let smap = sort(filter(keys(s:lmap), 'v:val !=# "name"'), function('s:compare_key')) + for k in smap + let offset = repeat(' ', 8 - strlen(k)) + let desc = type(s:lmap[k]) == type({}) ? s:lmap[k].name : s:lmap[k][1] + if g:spacevim_leader_guide_theme == 'whichkey' + let displaystring = offset . k .' -> '.desc + else + let displaystring = offset . '['. k .'] '.desc + endif + let crow = get(rows, row, []) + if empty(crow) + call add(rows, crow) + endif + call add(crow, displaystring) + call add(crow, repeat(' ', l.col_width - strdisplaywidth(displaystring))) + + if !g:leaderGuide_sort_horizontal + if row >= n_rows - 1 + if overh > 0 && row < n_rows + let overh -= 1 + let row += 1 + else + let row = 0 + let col += 1 + endif + else + let row += 1 endif else - let row += 1 + if col == l.n_cols - 1 + let row +=1 + let col = 0 + else + let col += 1 + endif endif - else - if col == l.n_cols - 1 - let row +=1 - let col = 0 + endfor + let r = [] + let mlen = 0 + for ro in rows + let line = join(ro, '') + call add(r, line) + if strdisplaywidth(line) > mlen + let mlen = strdisplaywidth(line) + endif + endfor + let output = join(r, "\n") + return output + endfunction " }}} + + let s:VIMH = SpaceVim#api#import('vim#highlight') + function! s:highlight_cursor() abort + let info = { + \ 'name' : 'SpaceVimGuideCursor', + \ 'guibg' : synIDattr(synIDtrans(synID(line('.'), col('.'), 1)), 'guifg'), + \ 'guifg' : synIDattr(synIDtrans(synID(line('.'), col('.'), 1)), 'guibg'), + \ 'ctermbg' : synIDattr(synIDtrans(synID(line('.'), col('.'), 1)), 'ctermfg'), + \ 'ctermfg' : synIDattr(synIDtrans(synID(line('.'), col('.'), 1)), 'ctermbg'), + \ } + hi! def link SpaceVimGuideCursor Cursor + call s:VIMH.hi(info) + if s:vis ==# 'gv' + " [bufnum, lnum, col, off] + let begin = getpos("'<") + let end = getpos("'>") + if begin[1] == end[1] + let s:cursor_hi = s:CMP.matchaddpos('SpaceVimGuideCursor', [[begin[1], min([begin[2], end[2]]), abs(begin[2] - end[2]) + 1]]) else - let col += 1 + let pos = [[begin[1], begin[2], len(getline(begin[1])) - begin[2] + 1], + \ [end[1], 1, end[2]], + \ ] + for lnum in range(begin[1] + 1, end[1] - 1) + call add(pos, [lnum, 1, len(getline(lnum))]) + endfor + let s:cursor_hi = s:CMP.matchaddpos('SpaceVimGuideCursor', pos) + endif + else + let s:cursor_hi = s:CMP.matchaddpos('SpaceVimGuideCursor', [[line('.'), col('.'), 1]]) + endif + endfunction + + function! s:remove_cursor_highlight() abort + try + call matchdelete(s:cursor_hi) + catch + endtry + endfunction + + " @vimlint(EVL102, 1, l:string) + function! s:start_buffer() abort " {{{ + let s:winv = winsaveview() + let s:winnr = winnr() + let s:winres = winrestcmd() + let [s:winid, s:bufnr] = s:winopen() + let layout = s:calc_layout() + let string = s:create_string(layout) + + if g:leaderGuide_max_size + let layout.win_dim = min([g:leaderGuide_max_size, layout.win_dim]) + endif + + call setbufvar(s:bufnr, '&modifiable', 1) + if s:FLOATING.exists() + let rst = s:FLOATING.win_config(s:winid, + \ { + \ 'relative': 'editor', + \ 'width' : &columns, + \ 'height' : layout.win_dim + 2, + \ 'row' : &lines - layout.win_dim - 4, + \ 'col' : 0 + \ }) + else + if g:leaderGuide_vertical + noautocmd execute 'vert res '.layout.win_dim + else + noautocmd execute 'res '.layout.win_dim endif endif - endfor - let r = [] - let mlen = 0 - for ro in rows - let line = join(ro, '') - call add(r, line) - if strdisplaywidth(line) > mlen - let mlen = strdisplaywidth(line) - endif - endfor - let output = join(r, "\n") - return output -endfunction " }}} - -let s:VIMH = SpaceVim#api#import('vim#highlight') -function! s:highlight_cursor() abort - let info = { - \ 'name' : 'SpaceVimGuideCursor', - \ 'guibg' : synIDattr(synIDtrans(synID(line('.'), col('.'), 1)), 'guifg'), - \ 'guifg' : synIDattr(synIDtrans(synID(line('.'), col('.'), 1)), 'guibg'), - \ 'ctermbg' : synIDattr(synIDtrans(synID(line('.'), col('.'), 1)), 'ctermfg'), - \ 'ctermfg' : synIDattr(synIDtrans(synID(line('.'), col('.'), 1)), 'ctermbg'), - \ } - hi! def link SpaceVimGuideCursor Cursor - call s:VIMH.hi(info) - if s:vis ==# 'gv' - " [bufnum, lnum, col, off] - let begin = getpos("'<") - let end = getpos("'>") - if begin[1] == end[1] - let s:cursor_hi = s:CMP.matchaddpos('SpaceVimGuideCursor', [[begin[1], min([begin[2], end[2]]), abs(begin[2] - end[2]) + 1]]) + if s:FLOATING.exists() + " when using floating windows, and the flaating windows do not support + " statusline, add extra black line at top and button of the content. + call s:BUFFER.buf_set_lines(s:bufnr, 0, -1, 0, [''] + split(string, "\n") + ['']) else - let pos = [[begin[1], begin[2], len(getline(begin[1])) - begin[2] + 1], - \ [end[1], 1, end[2]], - \ ] - for lnum in range(begin[1] + 1, end[1] - 1) - call add(pos, [lnum, 1, len(getline(lnum))]) - endfor - let s:cursor_hi = s:CMP.matchaddpos('SpaceVimGuideCursor', pos) + call s:BUFFER.buf_set_lines(s:bufnr, 0, -1, 0, split(string, "\n")) endif - else - let s:cursor_hi = s:CMP.matchaddpos('SpaceVimGuideCursor', [[line('.'), col('.'), 1]]) - endif -endfunction - -function! s:remove_cursor_highlight() abort - try - call matchdelete(s:cursor_hi) - catch - endtry -endfunction - -" @vimlint(EVL102, 1, l:string) -function! s:start_buffer() abort " {{{ - let s:winv = winsaveview() - let s:winnr = winnr() - let s:winres = winrestcmd() - let [s:winid, s:bufnr] = s:winopen() - let layout = s:calc_layout() - let string = s:create_string(layout) - - if g:leaderGuide_max_size - let layout.win_dim = min([g:leaderGuide_max_size, layout.win_dim]) - endif - - call setbufvar(s:bufnr, '&modifiable', 1) - if s:FLOATING.exists() - let rst = s:FLOATING.win_config(s:winid, - \ { - \ 'relative': 'editor', - \ 'width' : &columns, - \ 'height' : layout.win_dim + 2, - \ 'row' : &lines - layout.win_dim - 4, - \ 'col' : 0 - \ }) - else - if g:leaderGuide_vertical - noautocmd execute 'vert res '.layout.win_dim - else - noautocmd execute 'res '.layout.win_dim - endif - endif - if s:FLOATING.exists() - " when using floating windows, and the flaating windows do not support - " statusline, add extra black line at top and button of the content. - call s:BUFFER.buf_set_lines(s:bufnr, 0, -1, 0, [''] + split(string, "\n") + ['']) - else - call s:BUFFER.buf_set_lines(s:bufnr, 0, -1, 0, split(string, "\n")) - endif - call setbufvar(s:bufnr, '&modifiable', 0) - redraw! - call s:wait_for_input() -endfunction " }}} -" @vimlint(EVL102, 0, l:string) - -function! s:handle_input(input) abort " {{{ - call s:winclose() - if type(a:input) ==? type({}) - let s:lmap = a:input - call s:start_buffer() - else - let s:prefix_key_inp = [] - call feedkeys(s:vis.s:reg.s:count, 'ti') - redraw! - try - unsilent execute a:input[0] - catch - unsilent echom v:exception - endtry - endif -endfunction " }}} - -" wait for in input sub function should be not block vim -function! s:wait_for_input() abort " {{{ - redraw! - let inp = s:VIM.getchar() - if inp ==# "\" - let s:prefix_key_inp = [] - let s:undo_history = [] - let s:guide_help_mode = 0 - call s:winclose() - doautocmd WinEnter - elseif s:guide_help_mode ==# 1 - call s:submode_mappings(inp) - let s:guide_help_mode = 0 - elseif inp ==# "\" - let s:guide_help_mode = 1 - call s:updateStatusline() + call setbufvar(s:bufnr, '&modifiable', 0) redraw! call s:wait_for_input() - else - if inp ==# ' ' - let inp = '[SPC]' + endfunction " }}} + " @vimlint(EVL102, 0, l:string) + + function! s:handle_input(input) abort " {{{ + call s:winclose() + if type(a:input) ==? type({}) + let s:lmap = a:input + call s:start_buffer() else - let inp = s:KEY.char2name(inp) + let s:prefix_key_inp = [] + call feedkeys(s:vis.s:reg.s:count, 'ti') + redraw! + try + unsilent execute a:input[0] + catch + unsilent echom v:exception + endtry endif - let fsel = get(s:lmap, inp) - if !empty(fsel) - call add(s:prefix_key_inp, inp) - call add(s:undo_history, s:lmap) - call s:handle_input(fsel) - else + endfunction " }}} + + " wait for in input sub function should be not block vim + function! s:wait_for_input() abort " {{{ + redraw! + let inp = s:VIM.getchar() + if inp ==# "\" + let s:prefix_key_inp = [] + let s:undo_history = [] + let s:guide_help_mode = 0 call s:winclose() doautocmd WinEnter - let keys = get(s:, 'prefix_key_inp', []) - let name = SpaceVim#mapping#leader#getName(s:prefix_key) - let _keys = join(keys, '-') - if empty(_keys) - call s:build_mpt(['key bindings is not defined: ', name . '-' . inp]) - else - call s:build_mpt(['key bindings is not defined: ', name . '-' . _keys . '-' . inp]) - endif - let s:prefix_key_inp = [] + elseif s:guide_help_mode ==# 1 + call s:submode_mappings(inp) let s:guide_help_mode = 0 + elseif inp ==# "\" + let s:guide_help_mode = 1 + call s:updateStatusline() + redraw! + call s:wait_for_input() + else + if inp ==# ' ' + let inp = '[SPC]' + else + let inp = s:KEY.char2name(inp) + endif + let fsel = get(s:lmap, inp) + if !empty(fsel) + call add(s:prefix_key_inp, inp) + call add(s:undo_history, s:lmap) + call s:handle_input(fsel) + else + call s:winclose() + doautocmd WinEnter + let keys = get(s:, 'prefix_key_inp', []) + let name = SpaceVim#mapping#leader#getName(s:prefix_key) + let _keys = join(keys, '-') + if empty(_keys) + call s:build_mpt(['key bindings is not defined: ', name . '-' . inp]) + else + call s:build_mpt(['key bindings is not defined: ', name . '-' . _keys . '-' . inp]) + endif + let s:prefix_key_inp = [] + let s:guide_help_mode = 0 + endif endif - endif -endfunction " }}} + endfunction " }}} -function! s:build_mpt(mpt) abort - normal! : - echohl Comment - if type(a:mpt) == 1 - echon a:mpt - elseif type(a:mpt) == 3 - echon join(a:mpt) - endif - echohl NONE -endfunction - - -" change this func, do not focus to the new windows, and return winid. - -function! s:winopen() abort " {{{ - call s:highlight_cursor() - let pos = g:leaderGuide_position ==? 'topleft' ? 'topleft' : 'botright' - if s:FLOATING.exists() - if !bufexists(s:bufnr) - let s:bufnr = s:BUFFER.create_buf(v:false, v:true) + function! s:build_mpt(mpt) abort + normal! : + echohl Comment + if type(a:mpt) == 1 + echon a:mpt + elseif type(a:mpt) == 3 + echon join(a:mpt) endif - let s:winid = s:FLOATING.open_win(s:bufnr, v:true, - \ { - \ 'relative': 'editor', - \ 'width' : &columns, - \ 'height' : 12, - \ 'row' : &lines - 14, - \ 'col' : 0 + echohl NONE + endfunction + + + " change this func, do not focus to the new windows, and return winid. + + function! s:winopen() abort " {{{ + call s:highlight_cursor() + let pos = g:leaderGuide_position ==? 'topleft' ? 'topleft' : 'botright' + if s:FLOATING.exists() + if !bufexists(s:bufnr) + let s:bufnr = s:BUFFER.create_buf(v:false, v:true) + endif + let s:winid = s:FLOATING.open_win(s:bufnr, v:true, + \ { + \ 'relative': 'editor', + \ 'width' : &columns, + \ 'height' : 12, + \ 'row' : &lines - 14, + \ 'col' : 0 + \ }) + else + if bufexists(s:bufnr) + let qfbuf = &buftype ==# 'quickfix' + let splitcmd = g:leaderGuide_vertical ? ' 1vs' : ' 1sp' + noautocmd execute pos . splitcmd + let bnum = bufnr('%') + noautocmd execute 'buffer '.s:bufnr + cmapclear + if qfbuf + noautocmd execute bnum.'bwipeout!' + endif + else + let splitcmd = g:leaderGuide_vertical ? ' 1vnew' : ' 1new' + noautocmd execute pos.splitcmd + let s:bufnr = bufnr('%') + augroup guide_autocmd + autocmd! + autocmd WinLeave call s:winclose() + augroup END + endif + let s:winid = winnr() + endif + let s:guide_help_mode = 0 + + if exists('&winhighlight') + call s:VIM.setbufvar(s:bufnr, { + \ '&winhighlight' : 'Normal:Pmenu,Search:', + \ }) + endif + + call s:VIM.setbufvar(s:bufnr, { + \ '&filetype' : 'leaderGuide', + \ '&number' : 0, + \ '&relativenumber' : 0, + \ '&list' : 0, + \ '&modeline' : 0, + \ '&wrap' : 0, + \ '&buflisted' : 0, + \ '&buftype' : 'nofile', + \ '&bufhidden' : 'unload', + \ '&swapfile' : 0, + \ '&cursorline' : 0, + \ '&cursorcolumn' : 0, + \ '&colorcolumn' : '', + \ '&winfixwidth' : 1, + \ '&winfixheight' : 1, \ }) + + " @fixme not sure if the listchars should be changed! + " setlocal listchars= + call s:updateStatusline() + call s:toggle_hide_cursor() + return [s:winid, s:bufnr] + endfunction " }}} + + if s:SL.support_float() + function! s:updateStatusline() abort + call SpaceVim#mapping#guide#theme#hi() + let gname = get(s:guide_group, 'name', '') + if !empty(gname) + let gname = ' - ' . gname[1:] + " let gname = substitute(gname,' ', '\\ ', 'g') + endif + let keys = get(s:, 'prefix_key_inp', []) + " let keys = substitute(keys, '\', '\\\', 'g') + noautocmd let winid = s:SL.open_float([ + \ ['Guide: ', 'LeaderGuiderPrompt'], + \ [' ', 'LeaderGuiderSep1'], + \ [SpaceVim#mapping#leader#getName(s:prefix_key) + \ . join(keys, '') . gname, 'LeaderGuiderName'], + \ [' ', 'LeaderGuiderSep2'], + \ [s:guide_help_msg(0), 'LeaderGuiderFill'], + \ [repeat(' ', 999), 'LeaderGuiderFill'], + \ ]) + call SpaceVim#logger#debug('key binding guide float statusline winid:' . winid) + endfunction + function! s:close_float_statusline() abort + call SpaceVim#logger#debug('close float statusline winid:' . s:SL.__winid) + call s:SL.close_float() + endfunction else - if bufexists(s:bufnr) - let qfbuf = &buftype ==# 'quickfix' - let splitcmd = g:leaderGuide_vertical ? ' 1vs' : ' 1sp' - noautocmd execute pos . splitcmd - let bnum = bufnr('%') - noautocmd execute 'buffer '.s:bufnr - cmapclear - if qfbuf - noautocmd execute bnum.'bwipeout!' + function! s:updateStatusline() abort + call SpaceVim#mapping#guide#theme#hi() + let gname = get(s:guide_group, 'name', '') + if !empty(gname) + let gname = ' - ' . gname[1:] + endif + let keys = get(s:, 'prefix_key_inp', []) + call setbufvar(s:bufnr, '&statusline', '%#LeaderGuiderPrompt# Guide: ' . + \ '%#LeaderGuiderSep1#' . s:lsep . + \ '%#LeaderGuiderName# ' . + \ SpaceVim#mapping#leader#getName(s:prefix_key) + \ . join(keys, '') . gname + \ . ' %#LeaderGuiderSep2#' . s:lsep . '%#LeaderGuiderFill#' + \ . s:guide_help_msg(0)) + endfunction + endif + + function! Test_st() abort + call s:updateStatusline() + endfunction + + function! s:guide_help_msg(escape) abort + if s:guide_help_mode == 1 + let msg = ' n -> next-page, p -> previous-page, u -> undo-key' + else + let msg = ' [C-h paging/help]' + endif + return a:escape ? substitute(msg,' ', '\\ ', 'g') : msg + endfunction + + let s:t_ve = '' + function! s:toggle_hide_cursor() abort + let t_ve = &t_ve + let &t_ve = s:t_ve + let s:t_ve = t_ve + endfunction + + + function! s:winclose() abort " {{{ + call s:toggle_hide_cursor() + if s:FLOATING.exists() + call s:FLOATING.win_close(s:winid, 1) + if s:SL.support_float() + call s:close_float_statusline() endif else - let splitcmd = g:leaderGuide_vertical ? ' 1vnew' : ' 1new' - noautocmd execute pos.splitcmd - let s:bufnr = bufnr('%') - augroup guide_autocmd - autocmd! - autocmd WinLeave call s:winclose() - augroup END - endif - let s:winid = winnr() - endif - let s:guide_help_mode = 0 - - if exists('&winhighlight') - call s:VIM.setbufvar(s:bufnr, { - \ '&winhighlight' : 'Normal:Pmenu,Search:', - \ }) - endif - - call s:VIM.setbufvar(s:bufnr, { - \ '&filetype' : 'leaderGuide', - \ '&number' : 0, - \ '&relativenumber' : 0, - \ '&list' : 0, - \ '&modeline' : 0, - \ '&wrap' : 0, - \ '&buflisted' : 0, - \ '&buftype' : 'nofile', - \ '&bufhidden' : 'unload', - \ '&swapfile' : 0, - \ '&cursorline' : 0, - \ '&cursorcolumn' : 0, - \ '&colorcolumn' : '', - \ '&winfixwidth' : 1, - \ '&winfixheight' : 1, - \ }) - - " @fixme not sure if the listchars should be changed! - " setlocal listchars= - call s:updateStatusline() - call s:toggle_hide_cursor() - return [s:winid, s:bufnr] -endfunction " }}} - -if s:SL.support_float() - function! s:updateStatusline() abort - call SpaceVim#mapping#guide#theme#hi() - let gname = get(s:guide_group, 'name', '') - if !empty(gname) - let gname = ' - ' . gname[1:] - " let gname = substitute(gname,' ', '\\ ', 'g') - endif - let keys = get(s:, 'prefix_key_inp', []) - " let keys = substitute(keys, '\', '\\\', 'g') - noautocmd let winid = s:SL.open_float([ - \ ['Guide: ', 'LeaderGuiderPrompt'], - \ [' ', 'LeaderGuiderSep1'], - \ [SpaceVim#mapping#leader#getName(s:prefix_key) - \ . join(keys, '') . gname, 'LeaderGuiderName'], - \ [' ', 'LeaderGuiderSep2'], - \ [s:guide_help_msg(0), 'LeaderGuiderFill'], - \ [repeat(' ', 999), 'LeaderGuiderFill'], - \ ]) - call SpaceVim#logger#debug('key binding guide float statusline winid:' . winid) - endfunction - function! s:close_float_statusline() abort - call SpaceVim#logger#debug('close float statusline winid:' . s:SL.__winid) - call s:SL.close_float() - endfunction -else - function! s:updateStatusline() abort - call SpaceVim#mapping#guide#theme#hi() - let gname = get(s:guide_group, 'name', '') - if !empty(gname) - let gname = ' - ' . gname[1:] - endif - let keys = get(s:, 'prefix_key_inp', []) - call setbufvar(s:bufnr, '&statusline', '%#LeaderGuiderPrompt# Guide: ' . - \ '%#LeaderGuiderSep1#' . s:lsep . - \ '%#LeaderGuiderName# ' . - \ SpaceVim#mapping#leader#getName(s:prefix_key) - \ . join(keys, '') . gname - \ . ' %#LeaderGuiderSep2#' . s:lsep . '%#LeaderGuiderFill#' - \ . s:guide_help_msg(0)) - endfunction -endif - -function! Test_st() abort - call s:updateStatusline() -endfunction - -function! s:guide_help_msg(escape) abort - if s:guide_help_mode == 1 - let msg = ' n -> next-page, p -> previous-page, u -> undo-key' - else - let msg = ' [C-h paging/help]' - endif - return a:escape ? substitute(msg,' ', '\\ ', 'g') : msg -endfunction - -let s:t_ve = '' -function! s:toggle_hide_cursor() abort - let t_ve = &t_ve - let &t_ve = s:t_ve - let s:t_ve = t_ve -endfunction - - -function! s:winclose() abort " {{{ - call s:toggle_hide_cursor() - if s:FLOATING.exists() - call s:FLOATING.win_close(s:winid, 1) - if s:SL.support_float() - call s:close_float_statusline() - endif - else - noautocmd execute s:winid.'wincmd w' - if s:winid == winnr() - noautocmd close - redraw! - exe s:winres - let s:winid = -1 - noautocmd execute s:winnr.'wincmd w' - call winrestview(s:winv) - if exists('*nvim_open_win') - doautocmd WinEnter + noautocmd execute s:winid.'wincmd w' + if s:winid == winnr() + noautocmd close + redraw! + exe s:winres + let s:winid = -1 + noautocmd execute s:winnr.'wincmd w' + call winrestview(s:winv) + if exists('*nvim_open_win') + doautocmd WinEnter + endif endif endif - endif - call s:remove_cursor_highlight() -endfunction " }}} -function! s:page_down() abort " {{{ - call feedkeys("\", 'n') - call feedkeys("\", 'x') - redraw! - call s:wait_for_input() -endfunction " }}} -function! s:page_undo() abort " {{{ - call s:winclose() - if len(s:prefix_key_inp) > 0 - call remove(s:prefix_key_inp, -1) - endif - if len(s:undo_history) > 0 - let s:lmap = remove(s:undo_history, -1) - endif - call s:start_buffer() -endfunction " }}} -function! s:page_up() abort " {{{ - call feedkeys("\", 'n') - call feedkeys("\", 'x') - redraw! - call s:wait_for_input() -endfunction " }}} - -function! s:handle_submode_mapping(cmd) abort " {{{ - let s:guide_help_mode = 0 - call s:updateStatusline() - if a:cmd ==# 'n' - call s:page_down() - elseif a:cmd ==# 'p' - call s:page_up() - elseif a:cmd ==# 'u' - call s:page_undo() - else + call s:remove_cursor_highlight() + endfunction " }}} + function! s:page_down() abort " {{{ + call feedkeys("\", 'n') + call feedkeys("\", 'x') + redraw! + call s:wait_for_input() + endfunction " }}} + function! s:page_undo() abort " {{{ call s:winclose() - endif -endfunction " }}} -function! s:submode_mappings(key) abort " {{{ - silent call s:handle_submode_mapping(a:key) -endfunction " }}} -function! s:mapmaparg(maparg) abort " {{{ - let noremap = a:maparg.noremap ? 'noremap' : 'map' - let buffer = a:maparg.buffer ? ' ' : '' - let silent = a:maparg.silent ? ' ' : '' - let nowait = a:maparg.nowait ? ' ' : '' - let st = a:maparg.mode . '' . noremap . ' ' . nowait . silent . buffer - \ . '' .a:maparg.lhs . ' ' . a:maparg.rhs - execute st -endfunction " }}} + if len(s:prefix_key_inp) > 0 + call remove(s:prefix_key_inp, -1) + endif + if len(s:undo_history) > 0 + let s:lmap = remove(s:undo_history, -1) + endif + call s:start_buffer() + endfunction " }}} + function! s:page_up() abort " {{{ + call feedkeys("\", 'n') + call feedkeys("\", 'x') + redraw! + call s:wait_for_input() + endfunction " }}} -function! s:get_register() abort "{{{ - if match(&clipboard, 'unnamedplus') >= 0 - let clip = '+' - elseif match(&clipboard, 'unnamed') >= 0 - let clip = '*' - else - let clip = '"' - endif - return clip -endfunction "}}} -function! SpaceVim#mapping#guide#start_by_prefix(vis, key) abort " {{{ - if a:key ==# ' ' && exists('b:spacevim_lang_specified_mappings') - let g:_spacevim_mappings_space.l = b:spacevim_lang_specified_mappings - endif - let s:guide_help_mode = 0 - let s:vis = a:vis ? 'gv' : '' - let s:count = v:count != 0 ? v:count : '' - let s:toplevel = a:key ==? ' ' - let s:prefix_key = a:key - let s:guide_group = {} + function! s:handle_submode_mapping(cmd) abort " {{{ + let s:guide_help_mode = 0 + call s:updateStatusline() + if a:cmd ==# 'n' + call s:page_down() + elseif a:cmd ==# 'p' + call s:page_up() + elseif a:cmd ==# 'u' + call s:page_undo() + else + call s:winclose() + endif + endfunction " }}} + function! s:submode_mappings(key) abort " {{{ + silent call s:handle_submode_mapping(a:key) + endfunction " }}} + function! s:mapmaparg(maparg) abort " {{{ + let noremap = a:maparg.noremap ? 'noremap' : 'map' + let buffer = a:maparg.buffer ? ' ' : '' + let silent = a:maparg.silent ? ' ' : '' + let nowait = a:maparg.nowait ? ' ' : '' + let st = a:maparg.mode . '' . noremap . ' ' . nowait . silent . buffer + \ . '' .a:maparg.lhs . ' ' . a:maparg.rhs + execute st + endfunction " }}} - if has('nvim') && !exists('s:reg') - let s:reg = '' - else - let s:reg = v:register != s:get_register() ? '"'.v:register : '' - endif + function! s:get_register() abort "{{{ + if match(&clipboard, 'unnamedplus') >= 0 + let clip = '+' + elseif match(&clipboard, 'unnamed') >= 0 + let clip = '*' + else + let clip = '"' + endif + return clip + endfunction "}}} + function! SpaceVim#mapping#guide#start_by_prefix(vis, key) abort " {{{ + if a:key ==# ' ' && exists('b:spacevim_lang_specified_mappings') + let g:_spacevim_mappings_space.l = b:spacevim_lang_specified_mappings + endif + let s:guide_help_mode = 0 + let s:vis = a:vis ? 'gv' : '' + let s:count = v:count != 0 ? v:count : '' + let s:toplevel = a:key ==? ' ' + let s:prefix_key = a:key + let s:guide_group = {} - if !has_key(s:cached_dicts, a:key) || g:leaderGuide_run_map_on_popup - "first run - let s:cached_dicts[a:key] = {} - call s:start_parser(a:key, s:cached_dicts[a:key]) - endif + if has('nvim') && !exists('s:reg') + let s:reg = '' + else + let s:reg = v:register != s:get_register() ? '"'.v:register : '' + endif - if has_key(s:desc_lookup, a:key) || has_key(s:desc_lookup , 'top') - let rundict = s:create_target_dict(a:key) - else - let rundict = s:cached_dicts[a:key] - endif - let s:lmap = rundict - call s:start_buffer() -endfunction " }}} -function! SpaceVim#mapping#guide#start(vis, dict) abort " {{{ - let s:vis = a:vis ? 'gv' : 0 - let s:lmap = a:dict - call s:start_buffer() -endfunction " }}} + if !has_key(s:cached_dicts, a:key) || g:leaderGuide_run_map_on_popup + "first run + let s:cached_dicts[a:key] = {} + call s:start_parser(a:key, s:cached_dicts[a:key]) + endif -if !exists('g:leaderGuide_displayfunc') - function! s:leaderGuide_display() abort + if has_key(s:desc_lookup, a:key) || has_key(s:desc_lookup , 'top') + let rundict = s:create_target_dict(a:key) + else + let rundict = s:cached_dicts[a:key] + endif + let s:lmap = rundict + call s:LOG.debug('lmap is:' . string(s:lmap)) + call s:start_buffer() + endfunction " }}} + function! SpaceVim#mapping#guide#start(vis, dict) abort " {{{ + let s:vis = a:vis ? 'gv' : 0 + let s:lmap = a:dict + call s:start_buffer() + endfunction " }}} + function! SpaceVim#mapping#guide#register_displayname(lhs, name) abort + call extend(s:registered_name, {a:lhs : a:name}) + endfunction + function! SpaceVim#mapping#guide#displayfunc() abort if has_key(s:registered_name, g:leaderGuide#displayname) return s:registered_name[g:leaderGuide#displayname] endif let g:leaderGuide#displayname = substitute(g:leaderGuide#displayname, '\c$', '', '') endfunction - let g:leaderGuide_displayfunc = [function('s:leaderGuide_display')] endif -let s:registered_name = {} -function! SpaceVim#mapping#guide#register_displayname(lhs, name) abort - call extend(s:registered_name, {a:lhs : a:name}) -endfunction +if !exists('g:leaderGuide_displayfunc') + let g:leaderGuide_displayfunc = [function('SpaceVim#mapping#guide#displayfunc')] +endif + if get(g:, 'mapleader', '\') ==# ' ' call SpaceVim#mapping#guide#register_prefix_descriptions(' ', diff --git a/bundle/neodev.nvim/types/nightly/vim.fn.lua b/bundle/neodev.nvim/types/nightly/vim.fn.lua index bfcf201fc..ccc0b4eaf 100644 --- a/bundle/neodev.nvim/types/nightly/vim.fn.lua +++ b/bundle/neodev.nvim/types/nightly/vim.fn.lua @@ -5655,7 +5655,7 @@ function vim.fn.matchadd(group, pattern, priority, id, dict) end -- ```vim -- GetGroup()->matchaddpos([23, 11]) -- ``` ---- @param pos number +--- @param pos number|table --- @param priority? any --- @param id? any --- @param dict? table diff --git a/lua/spacevim/api/vim.lua b/lua/spacevim/api/vim.lua index 93f61f80c..77e4ad1a9 100644 --- a/lua/spacevim/api/vim.lua +++ b/lua/spacevim/api/vim.lua @@ -23,6 +23,10 @@ function M.getchar(...) end end +function M.setbufvar(buf, opts) + +end + function M.getchar2nr(...) local status, ret = pcall(vim.fn.getchar, ...) if not status then diff --git a/lua/spacevim/api/vim/buffer.lua b/lua/spacevim/api/vim/buffer.lua index 5de538b62..5ff670ace 100644 --- a/lua/spacevim/api/vim/buffer.lua +++ b/lua/spacevim/api/vim/buffer.lua @@ -8,6 +8,10 @@ local M = {} +function M.create_buf(listed, scratch) + return vim.api.nvim_create_buf(listed, scratch) +end + function M.set_lines(bufnr, startindex, endindex, replacement) if startindex < 0 then startindex = #vim.buffer(bufnr) + 1 + startindex diff --git a/lua/spacevim/api/vim/keys.lua b/lua/spacevim/api/vim/keys.lua index 91b3d2774..c326e74bb 100644 --- a/lua/spacevim/api/vim/keys.lua +++ b/lua/spacevim/api/vim/keys.lua @@ -1,18 +1,48 @@ --!/usr/bin/lua local M = {} +local specified_keys = {} function M.t(str) - if vim.api ~= nil and vim.api.nvim_replace_termcodes ~= nil then - -- https://github.com/neovim/neovim/issues/17369 - local ret = vim.api.nvim_replace_termcodes(str, false, true, true):gsub("\128\254X", "\128") - return ret + if vim.api ~= nil and vim.api.nvim_replace_termcodes ~= nil then + -- https://github.com/neovim/neovim/issues/17369 + local ret = vim.api.nvim_replace_termcodes(str, false, true, true):gsub('\128\254X', '\128') + return ret + else + -- local ret = vim.fn.execute('echon "\\' .. str .. '"') + -- ret = ret:gsub('<80>', '\128') + -- return ret + return vim.eval(string.format('"\\%s"', str)) + end +end + +function M.char2name(c) + if #c == 1 then + return M.nr2name(vim.fn.char2nr(c)) + end + return specified_keys[c] or c +end + +function M.nr2name(nr) + if type(nr) == 'number' then + if nr == 32 then + return 'SPC' + elseif nr == 4 then + return '' + elseif nr == 3 then + return '' + elseif nr == 9 then + return '' + elseif nr == 92 then + return '' + elseif nr == 27 then + return '' else - -- local ret = vim.fn.execute('echon "\\' .. str .. '"') - -- ret = ret:gsub('<80>', '\128') - -- return ret - return vim.eval(string.format('"\\%s"', str)) + return vim.fn.nr2char(nr) end + else + return specified_keys[nr] or '' + end end return M diff --git a/lua/spacevim/plugin/guide.lua b/lua/spacevim/plugin/guide.lua index 0dcd035b3..4efc9bd5a 100644 --- a/lua/spacevim/plugin/guide.lua +++ b/lua/spacevim/plugin/guide.lua @@ -1,21 +1,35 @@ ---!/usr/bin/lua - +--============================================================================= +-- guide.lua --- Key binding guide for spacevim +-- Copyright (c) 2016-2023 Wang Shidong & Contributors +-- Author: Wang Shidong < wsdjeg@outlook.com > +-- URL: https://spacevim.org +-- License: GPLv3 +--============================================================================= local M = {} --- load apis +local log = require('spacevim.logger').derive('guide') +local Key = require('spacevim.api').import('vim.keys') local cmp = require('spacevim.api').import('vim.compatible') local buffer = require('spacevim.api').import('vim.buffer') local VIM = require('spacevim.api').import('vim') local SL = require('spacevim.api').import('vim.statusline') +-- all local values should be listed here: + local desc_lookup = {} local cached_dicts = {} +local reg = '' local winid = -1 +local count = '' +local toplevel = false +local prefix_key +local guide_group = {} + local bufnr = -1 local prefix_key_inp = {} @@ -23,161 +37,102 @@ local prefix_key_inp = {} local lmap = {} local undo_history = {} - -function M.has_configuration() - return desc_lookup ~= nil -end - - -function M.register_prefix_descriptions(key, dictname) - if key == '' then - key = ' ' - end - if desc_lookup == nil then - create_cache() - end - if cmp.fn.strlen(key) == 0 then - desc_lookup['top'] = dictname - else - if desc_lookup[key] == nil then - desc_lookup[key] = dictname - end - end -end - +local registered_name = {} -- the flag for guide help mode, the default is false local guide_help_mode = false -local function create_cache() - desc_lookup = {} +local vis = '' - cached_dicts = {} +-- local function without callout + +local wait_for_input + +local cursor_hilight_id = -1 + +local function create_cache() + desc_lookup = {} + + cached_dicts = {} end -local function create_target_dict(key) - local toplevel = {} - local tardict = {} - local mapdict = {} - if desc_lookup['top'] ~= nil then - toplevel = cmp.fn.deepcopy({desc_lookup['top']}) - if toplevel then - tardict = toplevel - else - tardict = toplevel[key] or {} - end - mapdict = cached_dicts[key] - merge(tardict, mapdict) - elseif desc_lookup[key] ~= nil then - tardict = cmp.fn.deepcopy({desc_lookup[key]}) - mapdict = cached_dicts[key] - else - tardict = cached_dicts[key] - end - return tardict - +function M.has_configuration() + return desc_lookup ~= nil +end +function M.register_prefix_descriptions(key, dictname) + if key == '' then + key = ' ' + end + if desc_lookup == nil then + create_cache() + end + if #key == 0 then + desc_lookup['top'] = dictname + elseif desc_lookup[key] == nil then + desc_lookup[key] = dictname + end + log.debug('desc_lookup is:' .. vim.inspect(desc_lookup)) end local function merge(dict_t, dict_o) - local target = dict_t - local other = dict_o - for k, v in ipairs(target) do - if vim.fn.type(target[k]) == 4 and vim.fn.has_key(other, k) then - if vim.fn.type(other[k]) == 4 then - if vim.fn.has_key(target[k], 'name') then - other[k].name = target[k].name - end - merge(target[k], other[k]) - elseif vim.fn.type(other[k]) == 3 then - if vim.g.leaderGuide_flatten == 0 or vim.fn.type(target[k]) == 4 then - target[k .. 'm'] = target[k] - end - target[k] = other[k] - if vim.fn.has_key(other, k .. 'm') and vim.fn.type(other[k .. 'm']) == 4 then - merge(target[k .. 'm'], other[k .. 'm']) - end - end + local target = dict_t + local other = dict_o + for k, _ in ipairs(target) do + if vim.fn.type(target[k]) == 4 and vim.fn.has_key(other, k) then + if vim.fn.type(other[k]) == 4 then + if vim.fn.has_key(target[k], 'name') then + other[k].name = target[k].name end + merge(target[k], other[k]) + elseif vim.fn.type(other[k]) == 3 then + if vim.g.leaderGuide_flatten == 0 or vim.fn.type(target[k]) == 4 then + target[k .. 'm'] = target[k] + end + target[k] = other[k] + if vim.fn.has_key(other, k .. 'm') and vim.fn.type(other[k .. 'm']) == 4 then + merge(target[k .. 'm'], other[k .. 'm']) + end + end end - vim.fn.extend(target, other, 'keep') + end + vim.fn.extend(target, other, 'keep') end -function M.populate_dictionary(key, dictname) - start_parser(key, cached_dicts[key]) -end - -function M.parse_mappings() - for k, v in ipairs(cached_dicts) do - start_parser(k, v) +local function create_target_dict(key) + local topdict = {} + local tardict = {} + local mapdict = {} + if desc_lookup['top'] ~= nil then + topdict = cmp.fn.deepcopy(vim.api.nvim_eval(desc_lookup['top'])) + if toplevel then + tardict = topdict + else + tardict = topdict[key] or {} end -end - -local function start_parser(key, dict) - if key == '[KEYs]' then - return '' - end - - if key == ' ' then key = '' end - - local readmap = cmp.execute('map ' .. key, 'silent') - - for _, line in ipairs(cmp.fn.split(readmap, "\n")) do - local mapd = cmp.fn.maparg( - cmp.split( - string.sub(line, 3, string.len(line)) - )[1], string.sub(line, 1, 1), 0, 1) - if mapd.lhs == "\\" then - mapd.feedkeyargs = '' - elseif mapd.noremap == 1 then - mapd.feedkeyargs = 'nt' - else - mapd.feedkeyargs = 'mt' - end - if mapd.lhs == '.*' or mapd.lhs == '.*' then - goto continue - end - mapd.display = format_displaystring(mapd.rhs) - mapd.lhs = cmp.fn.substitute(mapd.lhs, key, '', '') - mapd.lhs = cmp.fn.substitute(mapd.lhs, '', ' ', 'g') - mapd.lhs = cmp.fn.substitute(mapd.lhs, '', '', 'g') - mapd.rhs = cmp.fn.substitute(mapd.rhs, '', '' .. mapd['sid'] .. '_', 'g') - if mapd.lhs ~= '' and mapd.display ~= 'LeaderGuide.*' then - end - ::continue:: - end -end - - -local function add_map_to_dict(map, level, dict) - + mapdict = cached_dicts[key] + merge(tardict, mapdict) + elseif desc_lookup[key] ~= nil then + tardict = cmp.fn.deepcopy(vim.api.nvim_eval(desc_lookup[key])) + mapdict = cached_dicts[key] + else + tardict = cached_dicts[key] + end + return tardict end local function format_displaystring(map) - for _, f in ipairs(map) do - pcall(f) + vim.g['leaderGuide#displayname'] = map + + if vim.g['leaderGuide_displayfunc'] then + for _, f in ipairs(vim.g['leaderGuide_displayfunc']) do + pcall(f) end + end - return vim.g['leaderGuide#displayname'] or '' - -end - -local function flattenmap(dict, str) - - local ret = {} - - for kv, _ in ipairs(dict) do - if vim.fn.type(dict[kv]) == 3 then - local toret = {} - toret[str .. kv] = dict[kv] - return toret - elseif vim.fn.type(dict[kv]) == 4 then - vim.fn.extend(ret, flattenmap(dict[kv], str .. kv)) - end - end - - return ret - + local display = vim.g['leaderGuide#displayname'] + vim.cmd('unlet g:leaderGuide#displayname') + return display end local function escape_mappings(mapping) @@ -186,495 +141,706 @@ local function escape_mappings(mapping) rstring = vim.fn.substitute(rstring, '"', '\\\\"', 'g') rstring = 'call feedkeys("' .. rstring .. '", "' .. mapping.feedkeyargs .. '")' return rstring +end +local function flattenmap(dict, str) + local ret = {} + + for kv, _ in ipairs(dict) do + if vim.fn.type(dict[kv]) == 3 then + local toret = {} + toret[str .. kv] = dict[kv] + return toret + elseif vim.fn.type(dict[kv]) == 4 then + vim.fn.extend(ret, flattenmap(dict[kv], str .. kv)) + end + end + + return ret +end + +local function add_map_to_dict(map, level, dict) + if #map.lhs > level then + local curkey = map.lhs[level + 1] + local nlevel = level + 1 + if not dict[curkey] then + dict[curkey] = { name = vim.g.leaderGuide_default_group_name } + elseif vim.tbl_islist(dict[curkey]) and vim.g.leaderGuide_flatten == 1 then + local cmd = escape_mappings(map) + curkey = table.concat(map.lhs, '', level) + nlevel = level + if not dict[curkey] then + dict[curkey] = { cmd, map.display } + end + elseif vim.tbl_islist(dict[curkey]) and vim.g.leaderGuide_flatten == 0 then + curkey = curkey .. 'm' + if not dict[curkey] then + dict[curkey] = { name = vim.g.leaderGuide_default_group_name } + end + end + if not vim.tbl_islist(dict[curkey]) then + add_map_to_dict(map, nlevel, dict[curkey]) + end + else + local cmd = escape_mappings(map) + log.debug(vim.inspect(map)) + log.debug(level) + if not dict[map.lhs[level]] then + dict[map.lhs[level]] = { cmd, map.display } + elseif not vim.tbl_islist(dict[map.lhs[level]]) and vim.g.leaderGuide_flatten == 1 then + local childmap = flattenmap(dict[map.lhs[level]], map.lhs[level]) + for it, _ in pairs(childmap) do + dict[it] = childmap[it] + end + + dict[map.lhs[level]] = { cmd, map.display } + end + end end local function string_to_keys(input) + log.debug('string_to_keys input:' .. input) + local retlist = {} - local retlist = {} - - if vim.fn.match(input, [[<.\+>]]) ~= -1 then - local si = 0 - local go = true - while si < vim.fn.len(input) do - if go then - if input[si] == ' ' then - vim.fn.add(retlist, '[SPC]') - else - vim.fn.add(retlist, input[si]) - end - else - retlist[-1] = retlist[-1] .. input[si] - end - if input[si] == '<' then - go = false - else - go = true - end - si = si + 1 - end - else - for _, it in ipairs(vim.fn.split(input, [[\zs]])) do - if it == ' ' then - vim.fn.add(retlist, '[SPC]') - else - vim.fn.add(retlist, it) - end + if vim.fn.match(input, [[<.\+>]]) ~= -1 then + local si = 1 + local go = true + while si <= #input do + if go then + if string.sub(input, si, si) == ' ' then + table.insert(retlist, '[SPC]') + else + table.insert(retlist, string.sub(input, si, si)) end + else + retlist[#retlist] = retlist[#retlist] .. string.sub(input, si, si) + end + if string.sub(input, si, si) == '<' then + go = false + elseif string.sub(input, si, si) == '>' then + go = true + end + si = si + 1 end - return retlist - + else + for _, it in ipairs(vim.fn.split(input, [[\zs]])) do + if it == ' ' then + table.insert(retlist, '[SPC]') + else + table.insert(retlist, it) + end + end + end + log.debug('string_to_keys output:' .. vim.inspect(retlist)) + return retlist end -local function escape_keys(inp) - local ret = cmp.fn.substitute(inp, '<', '', '') - return cmp.fn.substitute(ret, '|', '', '') -end +local function start_parser(key, dict) + if key == '[KEYs]' then + return '' + end + if key == ' ' then + key = '' + end + + local readmap = cmp.execute('map ' .. key, 'silent') + local visual = false + if vis == 'gv' then + visual = true + else + visual = false + end + + for _, line in ipairs(cmp.fn.split(readmap, '\n')) do + local name = vim.fn.split(string.sub(line, 4, #line))[1] + log.debug('name is:' .. name) + log.debug('line is:' .. line) + local mapd = cmp.fn.maparg(name, string.sub(line, 1, 1), 0, 1) + log.debug('mapd is:' .. vim.inspect(mapd)) + if mapd.lhs == '\\' then + mapd.feedkeyargs = '' + elseif mapd.noremap == 1 then + mapd.feedkeyargs = 'nt' + else + mapd.feedkeyargs = 'mt' + end + if mapd.lhs == '.*' or mapd.lhs == '.*' then + goto continue + end + mapd.display = format_displaystring(mapd.rhs) + mapd.lhs = cmp.fn.substitute(mapd.lhs, key, '', '') + mapd.lhs = cmp.fn.substitute(mapd.lhs, '', ' ', 'g') + mapd.lhs = cmp.fn.substitute(mapd.lhs, '', '', 'g') + mapd.rhs = cmp.fn.substitute(mapd.rhs, '', '' .. mapd['sid'] .. '_', 'g') + if mapd.lhs ~= '' and mapd.display ~= 'LeaderGuide.*' then + log.debug('mapd is:' .. vim.inspect(mapd)) + mapd.lhs = string_to_keys(mapd.lhs) + log.debug('mapd is:' .. vim.inspect(mapd)) + if + (visual and vim.fn.match(mapd.mode, '[vx ]') >= 0) + or (not visual and vim.fn.match(mapd.mode, '[vx ]') == -1) + then + add_map_to_dict(mapd, 1, dict) + end + end + ::continue:: + end +end local function calc_layout() - local ret = {} + local ret = {} - local smap = vim.fn.filter(vim.fn.copy(lmap), 'v:key !=# "name"') - ret.n_items = vim.fn.len(smap) - local length = vim.fn.values(vim.fn.map(smap, 'strdisplaywidth("[".v:key."]".(type(v:val) == type({}) ? v:val["name"] : v:val[1]))')) - local maxlength = vim.fn.max(length) + vim.g.leaderGuide_hspace + log.debug('calc_layout:') + log.debug('lmap is:\n' .. vim.inspect(lmap)) - if vim.g.leaderGuide_vertical == 1 then - ret.n_rows = vim.fn.winheight(0) - 2 - ret.n_cols = ret.n_items / ret.n_rows - if ret.n_items ~= ret.n_rows then - ret.n_cols = ret.n_cols + 1 - end - ret.col_width = maxlength - ret.win_dim = ret.n_cols * ret.col_width + local smap = vim.fn.filter(vim.fn.copy(lmap), 'v:key !=# "name"') + log.debug('smap is:\n' .. vim.inspect(smap)) + log.debug('#smap is:' .. #smap) + ret.n_items = vim.fn.len(smap) + log.debug('n_items is:' .. ret.n_items) + local length = {} + for k, v in pairs(smap) do + if v.name then + table.insert(length, vim.fn.strdisplaywidth('[' .. k .. ']' .. v.name)) else - if vim.fn.winwidth(winid) >= maxlength then - ret.n_cols = vim.fn.winwidth(winid) / maxlength - else - ret.n_cols = 1 - end - ret.col_width = vim.fn.winwidth(winid) / ret.n_cols - ret.n_rows = ret.n_items / ret.n_cols - if vim.fn.fmod(ret.n_items, ret.n_cols) > 0 then - ret.n_rows = ret.n_rows + 1 - end - ret.win_dim = ret.n_rows + table.insert(length, vim.fn.strdisplaywidth('[' .. k .. ']' .. v[2])) end - return ret + end + log.debug('length is:' .. vim.inspect(length)) + local maxlength = vim.fn.max(length) + vim.g.leaderGuide_hspace + log.debug('maxlength is:' .. maxlength) + + if vim.g.leaderGuide_vertical == 1 then + ret.n_rows = vim.fn.winheight(0) - 2 + ret.n_cols = math.floor(ret.n_items / ret.n_rows) + if ret.n_items ~= ret.n_rows then + ret.n_cols = ret.n_cols + 1 + end + ret.col_width = maxlength + ret.win_dim = ret.n_cols * ret.col_width + else + if vim.fn.winwidth(winid) >= maxlength then + ret.n_cols = math.floor(vim.fn.winwidth(winid) / maxlength) + else + ret.n_cols = 1 + end + ret.col_width = math.floor(vim.fn.winwidth(winid) / ret.n_cols) + ret.n_rows = math.floor(ret.n_items / ret.n_cols) + if vim.fn.fmod(ret.n_items, ret.n_cols) > 0 then + ret.n_rows = ret.n_rows + 1 + end + ret.win_dim = ret.n_rows + end + log.debug('layout is:' .. vim.inspect(ret)) + return ret end local function get_key_number(key) - if key == '[SPC]' then - return 32 - elseif key == '' then - return 9 - else - return cmp.fn.char2nr(key) - end + if key == '[SPC]' then + return 32 + elseif key == '' then + return 9 + else + return cmp.fn.char2nr(key) + end end local function compare_key(i1, i2) - local a = get_key_number(i1) - local b = get_key_number(i2) - if a - b == 32 and a >= 97 and a <= 122 then - return -1 - elseif b - a == 32 and b >= 97 and b <= 122 then - return 1 - elseif a >= 97 and a <= 122 and b >= 97 and b <= 122 then - if a == b then return 0 elseif a > b then return 1 else return -1 end - elseif a >= 65 and a <= 90 and b >= 65 and b <= 90 then - if a == b then return 0 elseif a > b then return 1 else return -1 end - elseif a >= 97 and a <= 122 and b >= 65 and b <= 90 then - return compare_key(cmp.fn.nr2char(a), cmp.fn.nr2char(b + 32)) - elseif a >= 65 and a <= 90 and b >= 97 and b <= 122 then - return compare_key(cmp.fn.nr2char(a), cmp.fn.nr2char(b - 32)) + local a = get_key_number(i1) + local b = get_key_number(i2) + if a - b == 32 and a >= 97 and a <= 122 then + return true + elseif b - a == 32 and b >= 97 and b <= 122 then + return false + elseif a >= 97 and a <= 122 and b >= 97 and b <= 122 then + if a == b then + return false + elseif a > b then + return false + else + return true end - if a == b then return 0 elseif a > b then return 1 else return -1 end + elseif a >= 65 and a <= 90 and b >= 65 and b <= 90 then + if a == b then + return false + elseif a > b then + return false + else + return true + end + elseif a >= 97 and a <= 122 and b >= 65 and b <= 90 then + return compare_key(cmp.fn.nr2char(a), cmp.fn.nr2char(b + 32)) + elseif a >= 65 and a <= 90 and b >= 97 and b <= 122 then + return compare_key(cmp.fn.nr2char(a), cmp.fn.nr2char(b - 32)) + end + if a == b then + return false + elseif a > b then + return false + else + return true + end end - local function create_string(layout) - local l = layout + log.debug('create string:') + log.debug('layout is:' .. vim.inspect(layout)) + local l = layout - l.capacity = l.n_rows * l.n_cols + l.capacity = l.n_rows * l.n_cols - local overcap = l.capacity - l.n_cols + local overcap = l.capacity - l.n_cols - local overh = l.n_cols - overcap + local overh = l.n_cols - overcap - local n_rows = l.n_rows - 1 + local n_rows = l.n_rows - 1 - local rows = {} + local rows = {} - local row = 0 + local row = 1 - local col = 0 + local col = 1 - local smap vim.fn.sort(vim.fn.filter(vim.fn.keys(lmap), 'v:val !=# "name"'), compare_key) - - for k,_ in ipairs(smap) do - local desc = '' - if vim.fn.type(lmap[k]) == 4 then - desc = lmap[k].name - else - desc = lmap[k][2] - end - local displaystring = '[' .. k .. ']' .. desc - local crow = vim.fn.get(rows, row, {}) - - if vim.fn.empty(crow) == 1 then - vim.fn.add(rows, crow) - end - vim.fn.add(crow, displaystring) - vim.fn.add(crow, vim.fn['repeat'](' ', l.col_width - vim.fn.strdisplaywidth(displaystring))) - if vim.g.leaderGuide_sort_horizontal == 0 then - if overh >= n_rows - 1 then - if overh > 0 and row < n_rows then - overh = overh - 1 - row = row + 1 - else - row = 0 - col = col + 1 - end - else - row = row + 1 - end - else - if col == l.n_cols - 1 then - row = row + 1 - col = 0 - else - col = col + 1 - end - end + local crow = {} + log.debug('lmap is:' .. vim.inspect(lmap)) + local smap = {} + for k, _ in pairs(lmap) do + if k ~= 'name' then + table.insert(smap, k) end - local r = {} - local mlen = 0 - for _, ro in ipairs(rows) do - local line = vim.fn.join(ro, '') - vim.fn.add(r, line) - if vim.fn.strdisplaywidth(line) > mlen then - mlen = vim.fn.strdisplaywidth(line) - end + end + table.sort(smap, compare_key) + -- log.debug('smap is:' .. vim.inspect(smap)) + + for _, k in ipairs(smap) do + local desc = '' + if lmap[k].name then + desc = lmap[k].name + else + desc = lmap[k][2] or '' end - local output = vim.fn.join(r, "\n") - return output + local offset = string.rep(' ', 8 - #k) + local displaystring + if vim.g.spacevim_leader_guide_theme == 'whichkey' then + displaystring = offset .. k .. ' -> ' .. desc + else + displaystring = offset .. '[' .. k .. '] ' .. desc + end + crow = rows[row] or {} + -- log.debug('crow is:' .. vim.inspect(crow)) + + if #crow == 0 then + table.insert(rows, crow) + end + -- if the displaystring is too long + if #displaystring > l.col_width then + table.insert(crow, string.sub(displaystring, 1, l.col_width -5) .. '... ') + else + table.insert(crow, displaystring) + table.insert(crow, vim.fn['repeat'](' ', l.col_width - vim.fn.strdisplaywidth(displaystring))) + end + if vim.g.leaderGuide_sort_horizontal == 0 then + log.debug('row is:' .. row) + if row > n_rows then + if overh > 0 and row < n_rows then + overh = overh - 1 + row = row + 1 + else + row = 1 + col = col + 1 + end + else + row = row + 1 + end + else + if col == l.n_cols - 1 then + row = row + 1 + col = 0 + else + col = col + 1 + end + end + end + local r = { '' } + local mlen = 0 + for _, ro in ipairs(rows) do + local line = table.concat(ro, '') + table.insert(r, line) + if vim.fn.strdisplaywidth(line) > mlen then + mlen = vim.fn.strdisplaywidth(line) + end + end + table.insert(r, '') + log.debug('string is:\n' .. table.concat(r, '\n')) + return r end local function highlight_cursor() - + log.debug( + 'highlight cursor: line ' .. vim.fn.line('.') .. ' col ' .. vim.fn.col('.') .. ' vis ' .. vis + ) + vim.cmd('hi! def link SpaceVimGuideCursor Cursor') + if vis == 'gv' then + local _begin = vim.fn.getpos("'<") + local _end = vim.fn.getpos("'>") + local pos = {} + if _begin[2] == _end[2] then + pos = { { _begin[2], math.min(_begin[3], _end[3]), math.abs(_begin[3] - _end[3]) + 1 } } + else + pos = { + { _begin[2], _begin[3], vim.fn.len(vim.fn.getline(_begin[2])) - _begin[3] + 1 }, + { _end[2], 1, _end[3] }, + } + for _, lnum in ipairs(vim.fn.range(_begin[2] + 1, _end[2] - 1)) do + table.insert(pos, { lnum, 1, vim.fn.len(vim.fn.getline(lnum)) }) + end + end + log.debug('pos:' .. vim.inspect(pos)) + cursor_hilight_id = vim.fn.matchaddpos('SpaceVimGuideCursor', pos) + else + cursor_hilight_id = + vim.fn.matchaddpos('SpaceVimGuideCursor', { { vim.fn.line('.'), vim.fn.col('.'), 1 } }) + end end local function remove_cursor_highlight() - -end - -local function start_buffer() - local winv = cmp.fn.winsaveview() - local winnr = cmp.fn.winnr() - local winres = cmp.fn.winrestcmd() - local winid = 0 - local bufnr = 0 - winid, bufnr = winopen() - local layout = calc_layout() - local text = create_string(layout) - if vim.g.leaderGuide_max_size then - layout.win_dim = cmp.fn.min({vim.g.leaderGuide_max_size, layout.win_dim}) - end - cmp.fn.setbufvar(bufnr, '&modifiable', 1) - if floating.exists() then - else - if vim.g.leaderGuide_vertical then - else - end - end - if floating.exists() then - else - end - cmp.fn.setbufvar(bufnr, '&modifiable', 0) - - -- radraw! - - wait_for_input() - -end - -local function handle_input(input) - winclose() - if type(input) == 'table' then - lmap = input - start_buffer() - else - prefix_key_inp = {} - cmp.fn.feedkeys(vis .. reg .. count, 'ti') - - --- redraw! - - local ok, errors = pcall(vim.fn.execute, input[1]) - if not ok then - print(vim.v.exception) - end - - end - - -end - -local function wait_for_input() - local t = vim.api.nvim_replace_termcodes - local inp = VIM.getchar() - if inp == t('') then - guide_help_mode = true - updateStatusline() - -- redraw! - wait_for_input() - else - if inp == ' ' then - inp = '[SPC]' - else - inp = KEY.char2name(inp) - end - local fsel = vim.fn.get(lmap, inp) - if vim.fn.empty(fsel) == 1 then - vim.fn.add(prefix_key_inp, inp) - vim.fn.add(undo_history, lmap) - handle_input(fsel) - else - winclose() - vim.cmd('doautocmd WinEnter') - local keys = prefix_key_inp - local name = M.getName(prefix_key) - local _keys = vim.fn.join(keys, '-') - if vim.fn.empty(_keys) == 1 then - build_mpt({'Key bindings is not defined: ', name .. '-' .. inp}) - else - build_mpt({'key bindings is not defined: ', name .. '-' .. _keys .. '-' .. inp}) - end - prefix_key_inp = {} - guide_help_mode = false - end - end - - -end - - -local function build_mpt(mpt) - - vim.fn.execute('normal! :') - - vim.fn.execute('echohl Comment') - - if type(mpt) == 'string' then - print(mpt) - else - end - - vim.fn.execute('echohl NONE') - -end - -local function winopen() - -end - - -local updateStatusline - - -if SL.support_float() then - updateStatusline = function() - end -else - updateStatusline = function() - end -end - -local function close_float_statusline() - + pcall(vim.fn.matchdelete, cursor_hilight_id) end local function guide_help_msg(escape) - local msg = '' - if guide_help_mode then - msg = ' n -> next-page, p -> previous-page, u -> undo-key' - else - msg = ' [C-h paging/help]' - end - if escape then - return vim.fn.substitute(msg, ' ', '\\ ', 'g') - else - return msg - end - + local msg + if guide_help_mode then + msg = ' n -> next-page, p -> previous-page, u -> undo-key' + else + msg = ' [C-h paging/help]' + end + if escape then + return vim.fn.substitute(msg, ' ', '\\\\ ', 'g') + else + return msg + end end -local function toggle_hide_cursor() +local function updateStatusline() + vim.fn['SpaceVim#mapping#guide#theme#hi']() + local gname = guide_group.name or '' + if #gname > 0 then + gname = ' - ' .. string.sub(gname, 2) + end + local keys = prefix_key_inp + SL.open_float({ + { 'Guide: ', 'LeaderGuiderPrompt' }, + { ' ', 'LeaderGuiderSep1' }, + { + vim.fn['SpaceVim#mapping#leader#getName'](prefix_key) .. table.concat(keys, '') .. gname, + 'LeaderGuiderName', + }, + { ' ', 'LeaderGuiderSep2' }, + { guide_help_msg(false), 'LeaderGuiderFill' }, + { string.rep(' ', 999), 'LeaderGuiderFill' }, + }) end +local function setlocalopt(buf, win, opts) + for o, value in pairs(opts) do + local info = vim.api.nvim_get_option_info2(o, {}) + if info.scope == 'win' then + vim.api.nvim_set_option_value(o, value, { + win = win, + }) + elseif info.scope == 'buf' then + vim.api.nvim_set_option_value(o, value, { + buf = buf, + }) + end + end +end +local function winopen() + highlight_cursor() + if not vim.api.nvim_buf_is_valid(bufnr) then + bufnr = buffer.create_buf(false, true) + end + winid = vim.api.nvim_open_win(bufnr, true, { + relative = 'editor', + width = vim.o.columns, + height = 12, + row = vim.o.lines - 14, + col = 0, + }) + guide_help_mode = false + setlocalopt(bufnr, winid, { + winhighlight = 'Normal:Pmenu,Search:', + filetype = 'leaderGuide', + number = false, + relativenumber = false, + list = false, + modeline = false, + wrap = false, + buflisted = false, + buftype = 'nofile', + bufhidden = 'unload', + swapfile = false, + cursorline = false, + cursorcolumn = false, + colorcolumn = '', + winfixwidth = true, + winfixheight = true, + }) + updateStatusline() + return winid, bufnr +end +local function start_buffer() + winid, bufnr = winopen() + local layout = calc_layout() + local text = create_string(layout) + if vim.g.leaderGuide_max_size then + layout.win_dim = cmp.fn.min({ vim.g.leaderGuide_max_size, layout.win_dim }) + end + cmp.fn.setbufvar(bufnr, '&modifiable', 1) + -- always in neovim >= 0.9.0 + vim.api.nvim_win_set_config(winid, { + relative = 'editor', + width = vim.o.columns, + height = layout.win_dim + 2, + row = vim.o.lines - layout.win_dim - 4, + col = 0, + }) + + log.debug('text is:\n' .. vim.inspect(text)) + + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, text) + + cmp.fn.setbufvar(bufnr, '&modifiable', 0) + + vim.cmd('redraw!') + + wait_for_input() +end + +local function close_float_statusline() + SL.close_float() +end local function winclose() - toggle_hide_cursor() - if FLOATING.exists() then - FLOATING.win_close(winid, 1) - if SL.support_float() then - close_float_statusline() - end - else - vim.cmd('noautocmd execute ' .. winid ..'wincmd w') - if winid == vim.fn.winnr() then - vim.cmd('noautocmd close') - -- redraw! - - vim.execute(winres) - - winid = -1 - vim.cmd('noautocmd execute ' .. winnr .. 'wincmd w') - vim.fn.winrestview(winv) - if vim.fn.exists('*nvim_open_win') then - vim.cmd('doautocmd WinEnter') - end - end - end - remove_cursor_highlight() - + vim.api.nvim_win_close(winid, true) + close_float_statusline() + remove_cursor_highlight() end +local function handle_input(input) + log.debug('handle_input:' .. vim.inspect(input)) + winclose() + if not vim.tbl_islist(input) then + lmap = input + start_buffer() + else + prefix_key_inp = {} + cmp.fn.feedkeys(vis .. reg .. count, 'ti') + + --- redraw! + + local ok, _ = pcall(vim.fn.execute, input[1]) + if not ok then + print(vim.v.exception) + end + end +end local function page_down() + log.debug('page down') + -- vim.api.nvim_feedkeys(Key.t(''), 'n', false) + vim.api.nvim_feedkeys(Key.t(''), 'x', false) + vim.cmd('redraw!') + wait_for_input() end local function page_undo() - - winclose() - if vim.fn.len(prefix_key_inp) > 0 then - vim.fn.remove(prefix_key_inp, -1) - end - if vim.fn.len(undo_history) > 0 then - lmap = vim.fn.remove(undo_history, -1) - end - start_buffer() + winclose() + if #prefix_key_inp then + table.remove(prefix_key_inp) + end + if #undo_history > 0 then + lmap = table.remove(undo_history) + end + start_buffer() end local function page_up() + log.debug('page up') + -- vim.api.nvim_feedkeys(Key.t(''), 'n', false) + vim.api.nvim_feedkeys(Key.t(''), 'x', false) + vim.cmd('redraw!') + wait_for_input() end - - local function handle_submode_mapping(cmd) + guide_help_mode = false - guide_help_mode = false - - updateStatusline() - - if cmd == 'n' then - page_down() - elseif cmd == 'p' then - page_up() - elseif cmd == 'u' then - page_undo() - else - winclose() - end + updateStatusline() + if cmd == 'n' then + page_down() + elseif cmd == 'p' then + page_up() + elseif cmd == 'u' then + page_undo() + else + winclose() + end end - local function submode_mappings(key) - handle_submode_mapping(key) + handle_submode_mapping(key) end -local function mapmaparg(maparg) - local map = '' - local buffer = '' - local silent = '' - local nowait = '' +local function warn_not_defined(mpt) + vim.cmd('redraw') + vim.api.nvim_echo({ + { mpt[1], 'Comment' }, + { mpt[2], 'Wildmenu' }, + }, false, {}) +end - if maparg.noremap == 1 then - map = 'noremap' +wait_for_input = function() + log.debug('wait for input:') + local t = Key.t + local inp = VIM.getchar() + log.debug('inp is:' .. inp) + if inp == t('') then + prefix_key_inp = {} + undo_history = {} + guide_help_mode = false + winclose() + vim.cmd('doautocmd WinEnter') + elseif guide_help_mode then + submode_mappings(inp) + guide_help_mode = false + elseif inp == t('') then + guide_help_mode = true + updateStatusline() + vim.cmd('redraw!') + wait_for_input() + else + if inp == ' ' then + inp = '[SPC]' else - map = 'map' + inp = Key.char2name(inp) end - - if maparg.buffer == 1 then - buffer = '' + local fsel = lmap[inp] or '' + if vim.fn.empty(fsel) == 0 then + table.insert(prefix_key_inp, inp) + table.insert(undo_history, lmap) + handle_input(fsel) + else + winclose() + vim.cmd('doautocmd WinEnter') + local keys = prefix_key_inp + local name = M.getName(prefix_key) + local _keys = vim.fn.join(keys, '-') + if vim.fn.empty(_keys) == 1 then + warn_not_defined({ 'Key bindings is not defined: ', name .. '-' .. inp }) + else + warn_not_defined({ 'key bindings is not defined: ', name .. '-' .. _keys .. '-' .. inp }) + end + prefix_key_inp = {} + guide_help_mode = false end - - if maparg.silent == 1 then silent = '' end - if maparg.nowait == 1 then nowait = '' end - local st = maparg.mode .. '' .. map .. ' ' .. nowait .. silent .. buffer .. '' .. maparg.lhs .. ' ' .. maparg.rhs - vim.execute(st) + end end - local function get_register() - if vim.fn.match(vim.o.clipboard, 'unnamedplus') >= 0 then - return '+' - elseif vim.fn.match(vim.o.clipboard, 'unnamed') >= 0 then - return '*' - else - return '"' - end + if vim.fn.match(vim.o.clipboard, 'unnamedplus') >= 0 then + return '+' + elseif vim.fn.match(vim.o.clipboard, 'unnamed') >= 0 then + return '*' + else + return '"' + end end function M.start_by_prefix(_vis, _key) + log.debug('start by prefix:' .. _key .. ' vis:' .. _vis) - if _key == ' ' and vim.fn.exists('b:spacevim_lang_specified_mappings') == 1 then - vim.g._spacevim_mappings_space.l = vim.b.spacevim_lang_specified_mappings - end - guide_help_mode = false - if _vis then - vis = 'gv' - else - vis = '' - end - - if vim.v.count ~= 0 then - count = vim.v.count - else - count = '' - end - - if _key == ' ' then toplevel = true else toplevel = false end - prefix_key = _key - guide_group = {} - - if vim.v.register ~= get_register() then - reg = '"' .. vim.v.register - else - reg = '' - end + if _key == ' ' and vim.fn.exists('b:spacevim_lang_specified_mappings') == 1 then + vim.g._spacevim_mappings_space.l = vim.b['spacevim_lang_specified_mappings'] + end + guide_help_mode = false + -- _vis is 0 or 1 + if _vis == '1' then + vis = 'gv' + else + vis = '' + end + log.debug('vis is:' .. vis) + if vim.v.count ~= 0 then + count = vim.v.count + else + count = '' + end + if _key == ' ' then + toplevel = true + else + toplevel = false + end + prefix_key = _key + guide_group = {} + if vim.v.register ~= get_register() then + reg = '"' .. vim.v.register + else + reg = '' + end + if not cached_dicts[_key] or vim.g.leaderGuide_run_map_on_popup then + cached_dicts[_key] = {} + start_parser(_key, cached_dicts[_key]) + end + if desc_lookup[_key] or desc_lookup['top'] then + lmap = create_target_dict(_key) + else + lmap = cached_dicts[_key] + end + start_buffer() end function M.start(_vis, _dict) - if _vis == 'gv' then - vis = 'gv' - else - vis = 0 - end - lmap = _dict - start_buffer() + if _vis == 'gv' then + vis = 'gv' + else + vis = '' + end + lmap = _dict + start_buffer() end function M.register_displayname(lhs, name) + registered_name[lhs] = name +end +function M.displayfunc() + if registered_name[vim.g['leaderGuide#displayname']] then + vim.g['leaderGuide#displayname'] = registered_name[vim.g['leaderGuide#displayname']] + end +end + +function M.populate_dictionary(key, _) + start_parser(key, cached_dicts[key]) +end + +function M.parse_mappings() + for k, v in ipairs(cached_dicts) do + start_parser(k, v) + end +end + +function M.getName(p) + return vim.fn['SpaceVim#mapping#leader#getName'](p) end return M