2023-08-25 12:05:54 +08:00
|
|
|
--=============================================================================
|
|
|
|
-- 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
|
|
|
|
--=============================================================================
|
2022-09-25 19:12:46 +08:00
|
|
|
|
|
|
|
local M = {}
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local log = require('spacevim.logger').derive('guide')
|
|
|
|
local Key = require('spacevim.api').import('vim.keys')
|
2022-09-25 19:12:46 +08:00
|
|
|
|
|
|
|
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')
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
-- all local values should be listed here:
|
|
|
|
|
2022-09-25 19:12:46 +08:00
|
|
|
local desc_lookup = {}
|
|
|
|
|
|
|
|
local cached_dicts = {}
|
2023-08-25 12:05:54 +08:00
|
|
|
local reg = ''
|
2022-09-25 19:12:46 +08:00
|
|
|
|
|
|
|
local winid = -1
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local count = ''
|
|
|
|
local toplevel = false
|
|
|
|
local prefix_key
|
|
|
|
local guide_group = {}
|
|
|
|
|
2022-09-25 19:12:46 +08:00
|
|
|
local bufnr = -1
|
|
|
|
|
|
|
|
local prefix_key_inp = {}
|
|
|
|
|
|
|
|
local lmap = {}
|
|
|
|
|
|
|
|
local undo_history = {}
|
2023-08-25 12:05:54 +08:00
|
|
|
local registered_name = {}
|
2022-09-25 19:12:46 +08:00
|
|
|
|
|
|
|
-- the flag for guide help mode, the default is false
|
|
|
|
local guide_help_mode = false
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local vis = ''
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
-- local function without callout
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local wait_for_input
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local cursor_hilight_id = -1
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local function create_cache()
|
|
|
|
desc_lookup = {}
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
cached_dicts = {}
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
function M.has_configuration()
|
|
|
|
return desc_lookup ~= nil
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
function M.register_prefix_descriptions(key, dictname)
|
|
|
|
if key == '<Space>' 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
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local function merge(dict_t, dict_o)
|
|
|
|
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
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
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]
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
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'])
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
end
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
end
|
|
|
|
vim.fn.extend(target, other, 'keep')
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
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 {}
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
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
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local function format_displaystring(map)
|
|
|
|
vim.g['leaderGuide#displayname'] = map
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
if vim.g['leaderGuide_displayfunc'] then
|
|
|
|
for _, f in ipairs(vim.g['leaderGuide_displayfunc']) do
|
|
|
|
pcall(f)
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
end
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local display = vim.g['leaderGuide#displayname']
|
|
|
|
vim.cmd('unlet g:leaderGuide#displayname')
|
|
|
|
return display
|
2022-09-25 19:12:46 +08:00
|
|
|
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
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local function flattenmap(dict, str)
|
|
|
|
local ret = {}
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
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))
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
end
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
return ret
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
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)
|
|
|
|
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
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local function string_to_keys(input)
|
|
|
|
log.debug('string_to_keys input:' .. input)
|
|
|
|
local retlist = {}
|
|
|
|
|
|
|
|
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
|
|
|
|
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 start_parser(key, dict)
|
|
|
|
if key == '[KEYs]' then
|
|
|
|
return ''
|
|
|
|
end
|
|
|
|
|
|
|
|
if key == ' ' then
|
|
|
|
key = '<Space>'
|
|
|
|
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)
|
|
|
|
if mapd.lhs == '\\' then
|
|
|
|
mapd.feedkeyargs = ''
|
|
|
|
elseif mapd.noremap == 1 then
|
|
|
|
mapd.feedkeyargs = 'nt'
|
|
|
|
else
|
|
|
|
mapd.feedkeyargs = 'mt'
|
|
|
|
end
|
|
|
|
if mapd.lhs == '<Plug>.*' or mapd.lhs == '<SNR>.*' 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, '<Space>', ' ', 'g')
|
|
|
|
mapd.lhs = cmp.fn.substitute(mapd.lhs, '<Tab>', '<C-I>', 'g')
|
|
|
|
mapd.rhs = cmp.fn.substitute(mapd.rhs, '<SID>', '<SNR>' .. mapd['sid'] .. '_', 'g')
|
|
|
|
if mapd.lhs ~= '' and mapd.display ~= 'LeaderGuide.*' then
|
|
|
|
mapd.lhs = string_to_keys(mapd.lhs)
|
|
|
|
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
|
2022-09-25 19:12:46 +08:00
|
|
|
|
|
|
|
local function calc_layout()
|
2023-08-25 12:05:54 +08:00
|
|
|
local ret = {}
|
|
|
|
|
|
|
|
|
|
|
|
local smap = vim.fn.filter(vim.fn.copy(lmap), 'v:key !=# "name"')
|
|
|
|
ret.n_items = vim.fn.len(smap)
|
|
|
|
local length = {}
|
|
|
|
for k, v in pairs(smap) do
|
|
|
|
if v.name then
|
|
|
|
table.insert(length, vim.fn.strdisplaywidth('[' .. k .. ']' .. v.name))
|
2022-09-25 19:12:46 +08:00
|
|
|
else
|
2023-08-25 12:05:54 +08:00
|
|
|
table.insert(length, vim.fn.strdisplaywidth('[' .. k .. ']' .. v[2]))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
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 = 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
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
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
|
|
|
|
return ret
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
local function get_key_number(key)
|
2023-08-25 12:05:54 +08:00
|
|
|
if key == '[SPC]' then
|
|
|
|
return 32
|
|
|
|
elseif key == '<Tab>' then
|
|
|
|
return 9
|
|
|
|
else
|
|
|
|
return cmp.fn.char2nr(key)
|
|
|
|
end
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
local function compare_key(i1, i2)
|
2023-08-25 12:05:54 +08:00
|
|
|
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
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
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
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
local function create_string(layout)
|
2023-08-25 12:05:54 +08:00
|
|
|
local l = layout
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
l.capacity = l.n_rows * l.n_cols
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local overcap = l.capacity - l.n_cols
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local overh = l.n_cols - overcap
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local n_rows = l.n_rows - 1
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local rows = {}
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local row = 1
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local col = 1
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local crow = {}
|
|
|
|
local smap = {}
|
|
|
|
for k, _ in pairs(lmap) do
|
|
|
|
if k ~= 'name' then
|
|
|
|
table.insert(smap, k)
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
end
|
|
|
|
table.sort(smap, compare_key)
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
for _, k in ipairs(smap) do
|
|
|
|
local desc = ''
|
|
|
|
if lmap[k].name then
|
|
|
|
desc = lmap[k].name
|
2022-09-25 19:12:46 +08:00
|
|
|
else
|
2023-08-25 12:05:54 +08:00
|
|
|
desc = lmap[k][2] or ''
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
local offset = string.rep(' ', 8 - #k)
|
|
|
|
local displaystring
|
|
|
|
if vim.g.spacevim_leader_guide_theme == 'whichkey' then
|
|
|
|
displaystring = offset .. k .. ' -> ' .. desc
|
2022-09-25 19:12:46 +08:00
|
|
|
else
|
2023-08-25 12:05:54 +08:00
|
|
|
displaystring = offset .. '[' .. k .. '] ' .. desc
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
crow = rows[row] or {}
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
if #crow == 0 then
|
|
|
|
table.insert(rows, crow)
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
-- if the displaystring is too long
|
|
|
|
if #displaystring > l.col_width then
|
|
|
|
table.insert(crow, string.sub(displaystring, 1, l.col_width -5) .. '... ')
|
2022-09-25 19:12:46 +08:00
|
|
|
else
|
2023-08-25 12:05:54 +08:00
|
|
|
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
|
|
|
|
if row > n_rows then
|
|
|
|
if overh > 0 and row < n_rows then
|
|
|
|
overh = overh - 1
|
|
|
|
row = row + 1
|
2022-09-25 19:12:46 +08:00
|
|
|
else
|
2023-08-25 12:05:54 +08:00
|
|
|
row = 1
|
|
|
|
col = col + 1
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
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, '')
|
|
|
|
return r
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local function highlight_cursor()
|
|
|
|
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 } }
|
2022-09-25 19:12:46 +08:00
|
|
|
else
|
2023-08-25 12:05:54 +08:00
|
|
|
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
|
|
|
|
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
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local function remove_cursor_highlight()
|
|
|
|
pcall(vim.fn.matchdelete, cursor_hilight_id)
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
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 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
|
2022-09-25 19:12:46 +08:00
|
|
|
local function winopen()
|
2023-08-25 12:05:54 +08:00
|
|
|
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
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
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,
|
|
|
|
})
|
2022-09-25 19:12:46 +08:00
|
|
|
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, text)
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
cmp.fn.setbufvar(bufnr, '&modifiable', 0)
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
vim.cmd('redraw!')
|
|
|
|
|
|
|
|
wait_for_input()
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
local function close_float_statusline()
|
2023-08-25 12:05:54 +08:00
|
|
|
SL.close_float()
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
local function winclose()
|
|
|
|
vim.api.nvim_win_close(winid, true)
|
|
|
|
close_float_statusline()
|
|
|
|
remove_cursor_highlight()
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local function handle_input(input)
|
|
|
|
winclose()
|
|
|
|
if not vim.tbl_islist(input) then
|
|
|
|
lmap = input
|
|
|
|
start_buffer()
|
|
|
|
else
|
|
|
|
prefix_key_inp = {}
|
|
|
|
cmp.fn.feedkeys(vis .. reg .. count, 'ti')
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
--- redraw!
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local ok, _ = pcall(vim.fn.execute, input[1])
|
|
|
|
if not ok then
|
|
|
|
print(vim.v.exception)
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
end
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
local function page_down()
|
2023-08-25 12:05:54 +08:00
|
|
|
-- vim.api.nvim_feedkeys(Key.t('<C-c>'), 'n', false)
|
|
|
|
vim.api.nvim_feedkeys(Key.t('<C-d>'), 'x', false)
|
|
|
|
vim.cmd('redraw!')
|
|
|
|
wait_for_input()
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
local function page_undo()
|
2023-08-25 12:05:54 +08:00
|
|
|
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()
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
local function page_up()
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
-- vim.api.nvim_feedkeys(Key.t('<C-c>'), 'n', false)
|
|
|
|
vim.api.nvim_feedkeys(Key.t('<C-u>'), 'x', false)
|
|
|
|
vim.cmd('redraw!')
|
|
|
|
wait_for_input()
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
local function handle_submode_mapping(cmd)
|
2023-08-25 12:05:54 +08:00
|
|
|
guide_help_mode = false
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
updateStatusline()
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
if cmd == 'n' then
|
|
|
|
page_down()
|
|
|
|
elseif cmd == 'p' then
|
|
|
|
page_up()
|
|
|
|
elseif cmd == 'u' then
|
|
|
|
page_undo()
|
|
|
|
else
|
|
|
|
winclose()
|
|
|
|
end
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
local function submode_mappings(key)
|
2023-08-25 12:05:54 +08:00
|
|
|
handle_submode_mapping(key)
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
local function warn_not_defined(mpt)
|
|
|
|
vim.cmd('redraw')
|
|
|
|
vim.api.nvim_echo({
|
|
|
|
{ mpt[1], 'Comment' },
|
|
|
|
{ mpt[2], 'Wildmenu' },
|
|
|
|
}, false, {})
|
|
|
|
end
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
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('<Esc>') 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('<C-h>') then
|
|
|
|
guide_help_mode = true
|
|
|
|
updateStatusline()
|
|
|
|
vim.cmd('redraw!')
|
|
|
|
wait_for_input()
|
|
|
|
else
|
|
|
|
if inp == ' ' then
|
|
|
|
inp = '[SPC]'
|
2022-09-25 19:12:46 +08:00
|
|
|
else
|
2023-08-25 12:05:54 +08:00
|
|
|
inp = Key.char2name(inp)
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
2023-08-25 12:05:54 +08:00
|
|
|
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
|
|
|
|
end
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
local function get_register()
|
2023-08-25 12:05:54 +08:00
|
|
|
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
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
function M.start_by_prefix(_vis, _key)
|
2023-08-25 12:05:54 +08:00
|
|
|
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
|
|
|
|
-- _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
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
function M.start(_vis, _dict)
|
|
|
|
if _vis == 'gv' then
|
|
|
|
vis = 'gv'
|
|
|
|
else
|
|
|
|
vis = ''
|
|
|
|
end
|
|
|
|
lmap = _dict
|
|
|
|
start_buffer()
|
|
|
|
end
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
function M.register_displayname(lhs, name)
|
|
|
|
registered_name[lhs] = name
|
|
|
|
end
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
function M.displayfunc()
|
|
|
|
if registered_name[vim.g['leaderGuide#displayname']] then
|
|
|
|
vim.g['leaderGuide#displayname'] = registered_name[vim.g['leaderGuide#displayname']]
|
|
|
|
end
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
function M.populate_dictionary(key, _)
|
|
|
|
start_parser(key, cached_dicts[key])
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
function M.parse_mappings()
|
|
|
|
for k, v in ipairs(cached_dicts) do
|
|
|
|
start_parser(k, v)
|
|
|
|
end
|
|
|
|
end
|
2022-09-25 19:12:46 +08:00
|
|
|
|
2023-08-25 12:05:54 +08:00
|
|
|
function M.getName(p)
|
|
|
|
return vim.fn['SpaceVim#mapping#leader#getName'](p)
|
2022-09-25 19:12:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
return M
|