--!/usr/bin/lua local M = {} -- load apis 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') local desc_lookup = {} local cached_dicts = {} local winid = -1 local bufnr = -1 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 -- the flag for guide help mode, the default is false local guide_help_mode = false 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 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 end 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) 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) end local function format_displaystring(map) for _, f in ipairs(map) do pcall(f) 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 end local function escape_mappings(mapping) local rstring = vim.fn.substitute(mapping.rhs, [[\]], [[\\\\]], 'g') rstring = vim.fn.substitute(rstring, [[<\([^<>]*\)>]], '\\\\<\\1>', 'g') rstring = vim.fn.substitute(rstring, '"', '\\\\"', 'g') rstring = 'call feedkeys("' .. rstring .. '", "' .. mapping.feedkeyargs .. '")' return rstring end local function string_to_keys(input) 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 end end return retlist end local function escape_keys(inp) local ret = cmp.fn.substitute(inp, '<', '', '') return cmp.fn.substitute(ret, '|', '', '') end local function calc_layout() 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 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 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 end 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 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)) end if a == b then return 0 elseif a > b then return 1 else return -1 end end local function create_string(layout) local l = layout l.capacity = l.n_rows * l.n_cols local overcap = l.capacity - l.n_cols local overh = l.n_cols - overcap local n_rows = l.n_rows - 1 local rows = {} local row = 0 local col = 0 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 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 local output = vim.fn.join(r, "\n") return output end local function highlight_cursor() 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() 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 end local function toggle_hide_cursor() 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() end local function page_down() 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() end local function page_up() end local function handle_submode_mapping(cmd) 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 end local function submode_mappings(key) handle_submode_mapping(key) end local function mapmaparg(maparg) local map = '' local buffer = '' local silent = '' local nowait = '' if maparg.noremap == 1 then map = 'noremap' else map = 'map' end if maparg.buffer == 1 then buffer = '' 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 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 end function M.start_by_prefix(_vis, _key) 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 end function M.start(_vis, _dict) if _vis == 'gv' then vis = 'gv' else vis = 0 end lmap = _dict start_buffer() end function M.register_displayname(lhs, name) end return M