navigator.lua/lua/navigator/util.lua
2024-02-02 15:29:27 +11:00

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