mirror of
https://github.com/ray-x/navigator.lua
synced 2024-11-05 12:00:21 +00:00
631 lines
13 KiB
Lua
631 lines
13 KiB
Lua
-- retreives data form file
|
|
-- and line to highlight
|
|
-- Some of function copied from https://github.com/RishabhRD/nvim-lsputils
|
|
local M = { log_path = vim.lsp.get_log_path() }
|
|
-- local is_windows = uv.os_uname().version:match("Windows")
|
|
pcall(require, 'guihua') -- lazy load
|
|
local guihua = require('guihua.util')
|
|
local nvim_0_8
|
|
local vfn = vim.fn
|
|
local api = vim.api
|
|
local uv = vim.uv or vim.loop
|
|
|
|
local os_name = uv.os_uname().sysname
|
|
local is_win = os_name:find('Windows') or os_name:find('MINGW')
|
|
M.path_sep = function()
|
|
if is_win then
|
|
return '\\'
|
|
else
|
|
return '/'
|
|
end
|
|
end
|
|
|
|
local path_sep = M.path_sep()
|
|
|
|
M.path_cur = function()
|
|
if is_win then
|
|
return '.\\'
|
|
else
|
|
return './'
|
|
end
|
|
end
|
|
|
|
M.round = function(x, r)
|
|
r = r or 0.5
|
|
return math.max(0, math.floor(x - r))
|
|
end
|
|
|
|
function M.get_data_from_file(filename, startLine)
|
|
local displayLine
|
|
if startLine < 3 then
|
|
displayLine = startLine
|
|
startLine = 0
|
|
else
|
|
startLine = startLine - 2
|
|
displayLine = 2
|
|
end
|
|
local uri = 'file:///' .. filename
|
|
local bufnr = vim.uri_to_bufnr(uri)
|
|
if not api.nvim_buf_is_loaded(bufnr) then
|
|
vfn.bufload(bufnr)
|
|
end
|
|
local data = api.nvim_buf_get_lines(bufnr, startLine, startLine + 8, false)
|
|
if data == nil or vim.tbl_isempty(data) then
|
|
startLine = nil
|
|
else
|
|
local len = #data
|
|
startLine = startLine + 1
|
|
for i = 1, len, 1 do
|
|
data[i] = startLine .. ' ' .. data[i]
|
|
startLine = startLine + 1
|
|
end
|
|
end
|
|
return { data = data, line = displayLine }
|
|
end
|
|
|
|
function M.io_read(filename)
|
|
local f = io.open(filename, 'r')
|
|
if f == nil then
|
|
return nil
|
|
end
|
|
local content = f:read('*a') -- *a or *all reads the whole file
|
|
f:close()
|
|
return content
|
|
end
|
|
|
|
function M.rm_file(filename)
|
|
return os.remove(filename)
|
|
end
|
|
|
|
function M.file_exists(name)
|
|
local f = io.open(name, 'r')
|
|
if f ~= nil then
|
|
io.close(f)
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
M.merge = function(t1, t2)
|
|
for k, v in pairs(t2) do
|
|
t1[k] = v
|
|
end
|
|
return t1
|
|
end
|
|
|
|
M.map = function(modes, key, result, options)
|
|
options = M.merge({ noremap = true, silent = false, expr = false, nowait = false }, options or {})
|
|
local buffer = options.buffer
|
|
options.buffer = nil
|
|
|
|
if type(modes) ~= 'table' then
|
|
modes = { modes }
|
|
end
|
|
|
|
for i = 1, #modes do
|
|
if buffer then
|
|
api.nvim_buf_set_keymap(0, modes[i], key, result, options)
|
|
else
|
|
api.nvim_set_keymap(modes[i], key, result, options)
|
|
end
|
|
end
|
|
end
|
|
|
|
function M.get_base(path)
|
|
local len = #path
|
|
for i = len, 1, -1 do
|
|
if path:sub(i, i) == path_sep then
|
|
local ret = path:sub(i + 1, len)
|
|
return ret
|
|
end
|
|
end
|
|
return ''
|
|
end
|
|
|
|
local function getDir(path)
|
|
local data = {}
|
|
local len = #path
|
|
if len <= 1 then
|
|
return nil
|
|
end
|
|
local last_index = 1
|
|
for i = 2, len do
|
|
local cur_char = path:sub(i, i)
|
|
if cur_char == path_sep then
|
|
local my_data = path:sub(last_index + 1, i - 1)
|
|
table.insert(data, my_data)
|
|
last_index = i
|
|
end
|
|
end
|
|
return data
|
|
end
|
|
|
|
function M.get_relative_path(base_path, my_path)
|
|
M.trace('rel path', base_path, my_path)
|
|
base_path = string.lower(base_path)
|
|
my_path = string.lower(my_path)
|
|
local base_data = getDir(base_path)
|
|
if base_data == nil then
|
|
M.log('base data is nil')
|
|
return
|
|
end
|
|
local my_data = getDir(my_path)
|
|
if vim.fn.empty(my_data) == 1 then
|
|
M.log('my data is nil', my_path)
|
|
return
|
|
end
|
|
local base_len = #base_data
|
|
local my_len = #my_data
|
|
|
|
if base_len > my_len then
|
|
M.log('incorrect dir format: base data', base_data, 'my data', my_data)
|
|
return my_path
|
|
end
|
|
|
|
if base_data[1] ~= my_data[1] then
|
|
M.log('base data is not same', base_data[1], my_data[1])
|
|
return my_path
|
|
end
|
|
|
|
local cur = 0
|
|
for i = 1, base_len do
|
|
if base_data[i] ~= my_data[i] then
|
|
break
|
|
end
|
|
cur = i
|
|
end
|
|
local data = ''
|
|
for i = cur + 1, my_len do
|
|
data = data .. my_data[i] .. path_sep
|
|
end
|
|
data = data .. M.get_base(my_path)
|
|
return data
|
|
end
|
|
|
|
M.log = function(...)
|
|
return { ... }
|
|
end
|
|
M.trace = function(...)
|
|
return { ... }
|
|
end
|
|
|
|
local level = 'info'
|
|
|
|
function M.setup()
|
|
if _NgConfigValues.debug == true then
|
|
level = 'debug'
|
|
elseif _NgConfigValues.debug == 'trace' then
|
|
level = 'trace'
|
|
end
|
|
local default_config =
|
|
{ use_console = false, use_file = true, level = level, plugin = 'navigator' }
|
|
if _NgConfigValues.debug_console_output then
|
|
default_config.use_console = true
|
|
default_config.use_file = false
|
|
end
|
|
|
|
M._log = require('guihua.log').new(default_config, true)
|
|
if _NgConfigValues.debug then
|
|
-- add log to you lsp.log
|
|
M.trace = M._log.trace
|
|
M.info = M._log.info
|
|
M.warn = M._log.warn
|
|
M.error = M._log.error
|
|
M.log = M.info
|
|
end
|
|
end
|
|
|
|
function M.fmt(...)
|
|
M._log.fmt_info(...)
|
|
end
|
|
|
|
function M.split(inputstr, sep)
|
|
if sep == nil then
|
|
sep = '%s'
|
|
end
|
|
local t = {}
|
|
for str in string.gmatch(inputstr, '([^' .. sep .. ']+)') do
|
|
table.insert(t, str)
|
|
end
|
|
return t
|
|
end
|
|
|
|
function M.quickfix_extract(line)
|
|
-- check if it is a line of file pos been selected
|
|
local split = M.split
|
|
line = vim.trim(line)
|
|
local sep = split(line, ' ')
|
|
if #sep < 2 then
|
|
M.log(line)
|
|
return nil
|
|
end
|
|
sep = split(sep[1], ':')
|
|
if #sep < 3 then
|
|
M.log(line)
|
|
return nil
|
|
end
|
|
local location = {
|
|
uri = 'file:///' .. sep[1],
|
|
range = { start = { line = sep[2] - 3 > 0 and sep[2] - 3 or 1 } },
|
|
}
|
|
location.range['end'] = { line = sep[2] + 15 }
|
|
return location
|
|
end
|
|
|
|
function M.getArgs(inputstr)
|
|
local sep = '%s'
|
|
local t = {}
|
|
local cmd
|
|
for str in string.gmatch(inputstr, '([^' .. sep .. ']+)') do
|
|
if not cmd then
|
|
cmd = str
|
|
else
|
|
table.insert(t, str)
|
|
end
|
|
end
|
|
return cmd, t
|
|
end
|
|
|
|
function M.p(t)
|
|
vim.notify(vim.inspect(t), vim.log.levels.INFO)
|
|
end
|
|
|
|
function M.printError(msg)
|
|
vim.cmd('echohl ErrorMsg')
|
|
vim.cmd(string.format([[echomsg '%s']], msg))
|
|
vim.cmd('echohl None')
|
|
end
|
|
|
|
function M.reload()
|
|
vim.lsp.stop_client(vim.lsp.get_active_clients())
|
|
vim.cmd([[edit]])
|
|
end
|
|
|
|
function M.open_log()
|
|
local path = vim.lsp.get_log_path()
|
|
vim.cmd('edit ' .. path)
|
|
end
|
|
if not table.pack then
|
|
table.pack = function(...)
|
|
return { n = select('#', ...), ... }
|
|
end
|
|
end
|
|
function M.show(...)
|
|
local string = ''
|
|
|
|
local args = table.pack(...)
|
|
|
|
for i = 1, args.n do
|
|
string = string .. tostring(args[i]) .. '\t'
|
|
end
|
|
|
|
return string .. '\n'
|
|
end
|
|
|
|
function M.split2(s, sep)
|
|
local fields = {}
|
|
|
|
sep = sep or ' '
|
|
local pattern = string.format('([^%s]+)', sep)
|
|
_ = string.gsub(s, pattern, function(c)
|
|
fields[#fields + 1] = c
|
|
end)
|
|
|
|
return fields
|
|
end
|
|
|
|
function M.trim_and_pad(txt)
|
|
local len = #txt
|
|
if len <= 1 then
|
|
return
|
|
end
|
|
local tab_en = txt[1] == '\t' or false
|
|
txt = vim.trim(txt)
|
|
if tab_en then
|
|
if len - txt > 2 then
|
|
return ' ' .. txt
|
|
end
|
|
if len - txt > 0 then
|
|
return ' ' .. txt
|
|
end
|
|
end
|
|
local rep = math.min(12, len - #txt)
|
|
return string.rep(' ', rep / 4) .. txt
|
|
end
|
|
|
|
M.open_file = function(filename)
|
|
api.nvim_command(string.format('e! %s', filename))
|
|
end
|
|
|
|
M.open_file_at = guihua.open_file_at
|
|
|
|
-- function M.exists(var)
|
|
-- for k, _ in pairs(_G) do
|
|
-- if k == var then
|
|
-- return true
|
|
-- end
|
|
-- end
|
|
-- end
|
|
|
|
local exclude_ft = { 'scrollbar', 'help', 'NvimTree' }
|
|
function M.exclude(fname)
|
|
for i = 1, #exclude_ft do
|
|
if string.find(fname, exclude_ft[i]) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- virtual text
|
|
|
|
-- name space search
|
|
local nss
|
|
local bufs
|
|
|
|
function M.set_virt_eol(bufnr, lnum, chunks, priority, id)
|
|
if nss == nil then
|
|
nss = api.nvim_create_namespace('navigator_search')
|
|
end
|
|
bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr
|
|
bufs[bufnr] = true
|
|
-- id may be nil
|
|
return api.nvim_buf_set_extmark(
|
|
bufnr,
|
|
nss,
|
|
lnum,
|
|
-1,
|
|
{ id = id, virt_text = chunks, priority = priority }
|
|
)
|
|
end
|
|
|
|
function M.clear_buf(bufnr)
|
|
if not bufnr then
|
|
return
|
|
end
|
|
bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr
|
|
if bufs[bufnr] then
|
|
if api.nvim_buf_is_valid(bufnr) then
|
|
api.nvim_buf_clear_namespace(bufnr, nss, 0, -1)
|
|
-- nvim_buf_del_extmark
|
|
end
|
|
bufs[bufnr] = nil
|
|
end
|
|
end
|
|
|
|
function M.clear_all_buf()
|
|
for bufnr in pairs(bufs) do
|
|
M.clear_buf(bufnr)
|
|
end
|
|
bufs = {}
|
|
end
|
|
|
|
function M.get_current_winid()
|
|
return api.nvim_get_current_win()
|
|
end
|
|
|
|
function M.nvim_0_8()
|
|
if nvim_0_8 ~= nil then
|
|
return nvim_0_8
|
|
end
|
|
nvim_0_8 = vfn.has('nvim-0.8') == 1
|
|
if nvim_0_8 == false then
|
|
M.log('Please use navigator 0.4 version for neovim version < 0.8')
|
|
vim.notify('Please use navigator 0.4 version for neovim version < 0.8', vim.log.levels.ERROR)
|
|
end
|
|
return nvim_0_8
|
|
end
|
|
|
|
function M.mk_handler(fn)
|
|
return function(...)
|
|
return fn(...)
|
|
end
|
|
end
|
|
|
|
function M.partial(func, arg)
|
|
return function(...)
|
|
return func(arg, ...)
|
|
end
|
|
end
|
|
|
|
function M.partial2(func, arg1, arg2)
|
|
return function(...)
|
|
return func(arg1, arg2, ...)
|
|
end
|
|
end
|
|
|
|
function M.partial3(func, arg1, arg2, arg3)
|
|
return function(...)
|
|
return func(arg1, arg2, arg3, ...)
|
|
end
|
|
end
|
|
|
|
function M.partial4(func, arg1, arg2, arg3, arg4)
|
|
return function(...)
|
|
return func(arg1, arg2, arg3, arg4, ...)
|
|
end
|
|
end
|
|
|
|
function M.empty(t)
|
|
if t == nil then
|
|
return true
|
|
end
|
|
|
|
if type(t) ~= 'table' then
|
|
return false
|
|
end
|
|
return next(t) == nil
|
|
end
|
|
|
|
function M.encoding(client)
|
|
if client == nil then
|
|
client = 1
|
|
end
|
|
|
|
if type(client) == 'number' then
|
|
client = vim.lsp.get_client_by_id(client) or {}
|
|
end
|
|
local oe = client.offset_encoding
|
|
if oe == nil then
|
|
return 'utf-8'
|
|
end
|
|
if type(oe) == 'table' then
|
|
return oe[1]
|
|
end
|
|
return oe
|
|
end
|
|
|
|
-- alternatively: use vim.notify("namespace does not exist or is anonymous", vim.log.levels.ERROR)
|
|
|
|
function M.warn(msg)
|
|
vim.notify('WRN: ' .. msg, vim.log.levels.WARN)
|
|
end
|
|
|
|
function M.error(msg)
|
|
vim.notify('ERR: ' .. msg, vim.log.levels.EROR)
|
|
end
|
|
|
|
function M.info(msg)
|
|
vim.notify('INF: ' .. msg, vim.log.levels.INFO)
|
|
end
|
|
|
|
function M.dedup(locations)
|
|
local m = math.min(10, #locations) -- dedup first 10 elements
|
|
local dict = {}
|
|
local del = {}
|
|
for i = 1, m, 1 do
|
|
local value = locations[i]
|
|
local range = value.range or value.originSelectionRange or value.targetRange
|
|
if not range then
|
|
break
|
|
end
|
|
local key = (value.uri or range.uri or value.targetUri or '')
|
|
.. ':'
|
|
.. tostring(range.start.line)
|
|
.. ':'
|
|
.. tostring(range.start.character)
|
|
.. ':'
|
|
.. tostring(range['end'].line)
|
|
.. ':'
|
|
.. tostring(range['end'].character)
|
|
if dict[key] == nil then
|
|
dict[key] = i
|
|
else
|
|
local j = dict[key]
|
|
if not locations[j].definition then
|
|
table.insert(del, i)
|
|
else
|
|
table.insert(del, j)
|
|
end
|
|
end
|
|
end
|
|
table.sort(del)
|
|
for i = #del, 1, -1 do
|
|
M.log('remove ', del[i])
|
|
table.remove(locations, del[i])
|
|
end
|
|
return locations
|
|
end
|
|
|
|
function M.range_inside(outer, inner)
|
|
if outer == nil or inner == nil then
|
|
return false
|
|
end
|
|
if outer.start == nil or outer['end'] == nil or inner.start == nil or inner['end'] == nil then
|
|
return false
|
|
end
|
|
return outer.start.line <= inner.start.line and outer['end'].line >= inner['end'].line
|
|
end
|
|
|
|
function M.dirname(pathname)
|
|
local strip_dir_pat = path_sep .. '([^' .. path_sep .. ']+)$'
|
|
local strip_sep_pat = path_sep .. '$'
|
|
if not pathname or #pathname == 0 then
|
|
return
|
|
end
|
|
local result = pathname:gsub(strip_sep_pat, ''):gsub(strip_dir_pat, '')
|
|
if #result == 0 then
|
|
return '/'
|
|
end
|
|
return result
|
|
end
|
|
|
|
function M.sub_match(str)
|
|
local _, j = string.gsub(str, [["]], '')
|
|
if j % 2 == 1 then
|
|
str = str .. '"'
|
|
end
|
|
_, j = string.gsub(str, [[']], '')
|
|
if j % 2 == 1 then
|
|
str = str .. [[']]
|
|
end
|
|
str = str .. ''
|
|
return str
|
|
end
|
|
|
|
function M.try_trim_markdown_code_blocks(lines)
|
|
local language_id = lines[1]:match('^```(.*)')
|
|
if language_id then
|
|
local has_inner_code_fence = false
|
|
for i = 2, (#lines - 1) do
|
|
local line = lines[i]
|
|
if line:sub(1, 3) == '```' then
|
|
has_inner_code_fence = true
|
|
break
|
|
end
|
|
end
|
|
-- No inner code fences + starting with code fence = hooray.
|
|
if not has_inner_code_fence then
|
|
table.remove(lines, 1)
|
|
table.remove(lines)
|
|
return language_id
|
|
end
|
|
end
|
|
return 'markdown'
|
|
end
|
|
|
|
function M.trim_empty_lines(lines)
|
|
local new_list = {}
|
|
for i, str in ipairs(lines) do
|
|
if str ~= '' and str then
|
|
table.insert(new_list, str)
|
|
end
|
|
end
|
|
return new_list
|
|
end
|
|
|
|
function M.for_each_buffer_client(bufnr, fn)
|
|
local clients
|
|
if vim.lsp.get_clients then -- nightly nvim 0.10
|
|
clients = vim.lsp.get_clients({ bufnr = bufnr })
|
|
else
|
|
clients = vim.lsp.buf_get_clients()
|
|
end
|
|
for _, client in pairs(clients) do
|
|
fn(client, client.id, bufnr)
|
|
end
|
|
end
|
|
|
|
function M.binding_remap(fn, key)
|
|
return function(...)
|
|
if fn(...) ~= true and key then -- the function failed fallback to key
|
|
M.log(key)
|
|
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(key, true, false, true), 'n', true)
|
|
end
|
|
end
|
|
end
|
|
|
|
function M.mk_handler_remap(fn, fallback)
|
|
return function(...)
|
|
if fn(...) ~= true then -- the function failed fallback to key
|
|
M.log('fallback, ', fallback)
|
|
if fallback then
|
|
M.log('fallback')
|
|
fallback()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return M
|