chore(commons): upgrade 'commons' (#32)

perf(disabled): allow disable specific filenames/filetypes/workspaces (#32)
This commit is contained in:
linrongbin16
2024-03-06 17:45:17 +08:00
committed by GitHub
parent c786cf1f9f
commit e9b0096fb6
38 changed files with 3032 additions and 775 deletions

View File

@@ -10,52 +10,46 @@ concurrency:
group: ${{ github.ref }}-ci
cancel-in-progress: true
jobs:
pr_conventional_commit:
name: PR Conventional Commit
commit:
name: Commit
if: ${{ github.ref != 'refs/heads/main' }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: ytanikin/PRConventionalCommits@1.1.0
with:
task_types: '["feat","fix","docs","test","ci","refactor","perf","chore","revert","break"]'
luacheck:
name: Lua Check
- uses: actions/checkout@v4
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/checkout@v4
- name: Install commons.nvim
if: ${{ github.ref != 'refs/heads/main' }}
shell: bash
run: |
git clone --depth=1 https://github.com/linrongbin16/commons.nvim.git ~/.commons.nvim
rm -rf ./lua/gentags/commons
mkdir -p ./lua/gentags/commons
cp -rf ~/.commons.nvim/lua/commons/*.lua ./lua/gentags/commons
cp -rf ~/.commons.nvim/lua/commons ./lua/gentags/
cp ~/.commons.nvim/version.txt ./lua/gentags/commons/version.txt
cd ./lua/gentags/commons
find . -type f -name '*.lua' -exec sed -i 's/require("commons/require("gentags.commons/g' {} \;
- name: Luacheck
uses: lunarmodules/luacheck@v1
with:
args: lua --config .luacheckrc
- name: Stylua
uses: JohnnyMorganz/stylua-action@v3
find ./lua/gentags/commons -type f -name '*.lua' -exec sed -i 's/require("commons./require("gentags.commons./g' {} \;
- uses: cargo-bins/cargo-binstall@main
- name: Selene
run: |
cargo binstall --no-confirm selene
selene --config selene.toml ./lua
- uses: JohnnyMorganz/stylua-action@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: latest
args: --config-path .stylua.toml ./lua ./test
- name: Auto Commit
- uses: stefanzweifel/git-auto-commit-action@v4
if: ${{ github.ref != 'refs/heads/main' }}
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "chore(pr): auto-commit"
unit_test:
name: Unit Test
needs:
- luacheck
- lint
strategy:
matrix:
nvim_version: [stable, nightly, v0.7.0]
@@ -78,7 +72,6 @@ jobs:
- name: Run test cases
shell: bash
run: |
luarocks install luacheck
luarocks install luacov
luarocks install cluacov
luarocks install vusted

View File

@@ -1,4 +0,0 @@
globals = { "vim", "describe", "before_each", "it", "assert" }
max_line_length = 500
unused = false
unused_args = false

View File

@@ -5,4 +5,6 @@ modules = {
exclude = {
"lua/gentags/commons/*.lua",
"lua/gentags/commons/*/*.lua",
"lua/gentags/commons/*/*/*.lua",
}

View File

@@ -2,21 +2,19 @@
# gentags.nvim
<p align="center">
<a href="https://github.com/neovim/neovim/releases/v0.7.0"><img alt="Neovim" src="https://img.shields.io/badge/Neovim-v0.7+-57A143?logo=neovim&logoColor=57A143" /></a>
<a href="https://github.com/linrongbin16/commons.nvim"><img alt="commons.nvim" src="https://custom-icon-badges.demolab.com/badge/Powered_by-commons.nvim-teal?logo=heart&logoColor=fff&labelColor=deeppink" /></a>
<a href="https://luarocks.org/modules/linrongbin16/gentags.nvim"><img alt="luarocks" src="https://custom-icon-badges.demolab.com/luarocks/v/linrongbin16/gentags.nvim?label=LuaRocks&labelColor=063B70&logo=tag&logoColor=fff&color=blue" /></a>
<a href="https://github.com/linrongbin16/gentags.nvim/actions/workflows/ci.yml"><img alt="ci.yml" src="https://img.shields.io/github/actions/workflow/status/linrongbin16/gentags.nvim/ci.yml?label=GitHub%20CI&labelColor=181717&logo=github&logoColor=fff" /></a>
<a href="https://app.codecov.io/github/linrongbin16/gentags.nvim"><img alt="codecov" src="https://img.shields.io/codecov/c/github/linrongbin16/gentags.nvim?logo=codecov&logoColor=F01F7A&label=Codecov" /></a>
<p align="left">
<a href="https://github.com/neovim/neovim/releases/v0.7.0"><img alt="require" src="https://img.shields.io/badge/require-0.7%2B-blue" /></a>
<a href="https://github.com/linrongbin16/commons.nvim"><img alt="commons.nvim" src="https://img.shields.io/badge/power_by-commons.nvim-pink" /></a>
<a href="https://luarocks.org/modules/linrongbin16/gentags.nvim"><img alt="luarocks" src="https://img.shields.io/luarocks/v/linrongbin16/gentags.nvim" /></a>
<a href="https://github.com/linrongbin16/gentags.nvim/actions/workflows/ci.yml"><img alt="ci.yml" src="https://img.shields.io/github/actions/workflow/status/linrongbin16/gentags.nvim/ci.yml?label=ci" /></a>
<a href="https://app.codecov.io/github/linrongbin16/gentags.nvim"><img alt="codecov" src="https://img.shields.io/codecov/c/github/linrongbin16/gentags.nvim/main?label=codecov" /></a>
</p>
<p align="center"><i>
Tags generator/management for old school vimers in Neovim.
</i></p>
To be honest, it seems that tags have become useless in Neovim today, LSP and treesitter replaced tags and make (Neo)vim a modern editor.
While there exists gap when LSP server or treesitter implementations are insufficient, which brings tags back to us as a supplement to fill the gap.
To be honest, it seems that tags have become useless in Neovim today, LSP and treesitter replaced tags and make (Neo)vim a modern editor. While there exists gap when LSP server or treesitter implementations are insufficient, which brings tags back to us as a supplement to fill the gap.
## Table of Contents
@@ -41,7 +39,7 @@ While there exists gap when LSP server or treesitter implementations are insuffi
## Requirements
- Neovim &ge; 0.7.0.
- Neovim &ge; 0.7.
- [universal-ctags](https://github.com/universal-ctags/ctags) (default backend).
PRs are welcome to add other backends.

View File

@@ -8,3 +8,5 @@ coverage:
threshold: 90%
ignore:
- "lua/gentags/commons/*.lua"
- "lua/gentags/commons/*/*.lua"
- "lua/gentags/commons/*/*.lua"

View File

@@ -16,7 +16,7 @@ M.setup = function(opts)
file_log = cfg.debug.file_log,
file_log_name = "gentags.log",
})
local logger = logging.get("gentags") --[[@as commons.logging.Logger]]
local logger = logging.get("gentags")
-- cache dir
logger:ensure(
@@ -36,8 +36,11 @@ M.setup = function(opts)
logging
.get("gentags")
:debug("|setup| enter buffer:%s", vim.inspect(event))
require("gentags.dispatcher").load()
require("gentags.dispatcher").init()
local dispatcher = require("gentags.dispatcher")
if dispatcher.enabled() then
dispatcher.load()
dispatcher.init()
end
end,
})
@@ -51,7 +54,10 @@ M.setup = function(opts)
logging
.get("gentags")
:debug("|setup| write buffer:%s", vim.inspect(event))
require("gentags.dispatcher").update()
local dispatcher = require("gentags.dispatcher")
if dispatcher.enabled() then
dispatcher.update()
end
end,
})

View File

@@ -1,6 +1,6 @@
local uv = vim.uv or vim.loop
--- @class SystemOpts
--- @class vim.SystemOpts
--- @field stdin? string|string[]|true
--- @field stdout? fun(err:string?, data: string?)|false
--- @field stderr? fun(err:string?, data: string?)|false
@@ -61,7 +61,7 @@ end
--- @field wait fun(self: vim.SystemObj, timeout?: integer): vim.SystemCompleted
--- @field kill fun(self: vim.SystemObj, signal: integer|string)
--- @field write fun(self: vim.SystemObj, data?: string|string[])
--- @field is_closing fun(self: vim.SystemObj): boolean?
--- @field is_closing fun(self: vim.SystemObj): boolean
local SystemObj = {}
--- @param state vim.SystemState
@@ -94,14 +94,14 @@ function SystemObj:wait(timeout)
local done = vim.wait(timeout or state.timeout or MAX_TIMEOUT, function()
return state.result ~= nil
end)
end, nil, true)
if not done then
-- Send sigkill since this cannot be caught
self:_timeout(SIG.KILL)
vim.wait(timeout or state.timeout or MAX_TIMEOUT, function()
return state.result ~= nil
end)
end, nil, true)
end
return state.result
@@ -140,7 +140,7 @@ end
--- @return boolean
function SystemObj:is_closing()
local handle = self._state.handle
return handle == nil or handle:is_closing()
return handle == nil or handle:is_closing() or false
end
---@param output fun(err:string?, data: string?)|false
@@ -302,7 +302,7 @@ end
--- Run a system command
---
--- @param cmd string[]
--- @param opts? SystemOpts
--- @param opts? vim.SystemOpts
--- @param on_exit? fun(out: vim.SystemCompleted)
--- @return vim.SystemObj
function M.run(cmd, opts, on_exit)

114
lua/gentags/commons/api.lua Normal file
View File

@@ -0,0 +1,114 @@
local NVIM_VERSION_0_8 = false
local NVIM_VERSION_0_9 = false
do
NVIM_VERSION_0_8 = require("gentags.commons.version").ge({ 0, 8 })
NVIM_VERSION_0_9 = require("gentags.commons.version").ge({ 0, 9 })
end
local M = {}
-- buffer {
--- @param bufnr integer
--- @param name string
--- @return any
M.get_buf_option = function(bufnr, name)
if NVIM_VERSION_0_8 then
return vim.api.nvim_get_option_value(name, { buf = bufnr })
else
return vim.api.nvim_buf_get_option(bufnr, name)
end
end
--- @param bufnr integer
--- @param name string
--- @param value any
M.set_buf_option = function(bufnr, name, value)
if NVIM_VERSION_0_8 then
return vim.api.nvim_set_option_value(name, value, { buf = bufnr })
else
return vim.api.nvim_buf_set_option(bufnr, name, value)
end
end
-- buffer }
-- window {
--- @param winnr integer
--- @param name string
--- @return any
M.get_win_option = function(winnr, name)
if NVIM_VERSION_0_8 then
return vim.api.nvim_get_option_value(name, { win = winnr })
else
return vim.api.nvim_win_get_option(winnr, name)
end
end
--- @param winnr integer
--- @param name string
--- @param value any
--- @return any
M.set_win_option = function(winnr, name, value)
if NVIM_VERSION_0_8 then
return vim.api.nvim_set_option_value(name, value, { win = winnr })
else
return vim.api.nvim_win_set_option(winnr, name, value)
end
end
-- window }
-- highlight {
--- @param hl string
--- @return {fg:integer?,bg:integer?,[string]:any,ctermfg:integer?,ctermbg:integer?,cterm:{fg:integer?,bg:integer?,[string]:any}?}
M.get_hl = function(hl)
if NVIM_VERSION_0_9 then
return vim.api.nvim_get_hl(0, { name = hl, link = false })
else
---@diagnostic disable-next-line: undefined-field
local ok1, rgb_value = pcall(vim.api.nvim_get_hl_by_name, hl, true)
if not ok1 then
return vim.empty_dict()
end
---@diagnostic disable-next-line: undefined-field
local ok2, cterm_value = pcall(vim.api.nvim_get_hl_by_name, hl, false)
if not ok2 then
return vim.empty_dict()
end
local result = vim.tbl_deep_extend("force", rgb_value, {
ctermfg = cterm_value.foreground,
ctermbg = cterm_value.background,
cterm = cterm_value,
})
result.fg = result.foreground
result.bg = result.background
result.sp = result.special
result.cterm.fg = result.cterm.foreground
result.cterm.bg = result.cterm.background
result.cterm.sp = result.cterm.special
return result
end
end
--- @param ... string?
--- @return {fg:integer?,bg:integer?,[string]:any,ctermfg:integer?,ctermbg:integer?,cterm:{fg:integer?,bg:integer?,[string]:any}?}, integer, string?
M.get_hl_with_fallback = function(...)
for i, hl in ipairs({ ... }) do
if type(hl) == "string" then
local hl_value = M.get_hl(hl)
if type(hl_value) == "table" and not vim.tbl_isempty(hl_value) then
return hl_value, i, hl
end
end
end
return vim.empty_dict(), -1, nil
end
-- highlight }
return M

View File

@@ -1,56 +0,0 @@
local M = {}
-- buffer {
--- @param bufnr integer
--- @param name string
--- @return any
M.get_buf_option = function(bufnr, name)
if vim.fn.has("nvim-0.8") > 0 then
return vim.api.nvim_get_option_value(name, { buf = bufnr })
else
return vim.api.nvim_buf_get_option(bufnr, name)
end
end
--- @param bufnr integer
--- @param name string
--- @param value any
M.set_buf_option = function(bufnr, name, value)
if vim.fn.has("nvim-0.8") > 0 then
return vim.api.nvim_set_option_value(name, value, { buf = bufnr })
else
return vim.api.nvim_buf_set_option(bufnr, name, value)
end
end
-- buffer }
-- window {
--- @param winnr integer
--- @param name string
--- @return any
M.get_win_option = function(winnr, name)
if vim.fn.has("nvim-0.8") > 0 then
return vim.api.nvim_get_option_value(name, { win = winnr })
else
return vim.api.nvim_win_get_option(winnr, name)
end
end
--- @param winnr integer
--- @param name string
--- @param value any
--- @return any
M.set_win_option = function(winnr, name, value)
if vim.fn.has("nvim-0.8") > 0 then
return vim.api.nvim_set_option_value(name, value, { win = winnr })
else
return vim.api.nvim_win_set_option(winnr, name, value)
end
end
-- window }
return M

View File

@@ -1,4 +1,4 @@
---@diagnostic disable: undefined-doc-name, luadoc-miss-module-name, deprecated
---@diagnostic disable: luadoc-miss-module-name, undefined-doc-name
--- Small async library for Neovim plugins
--- @module async

View File

@@ -0,0 +1,38 @@
local M = {}
--- @param highlight string
--- @param attr "fg"|"bg"|string
--- @return string?
M.get_color = function(highlight, attr)
assert(type(highlight) == "string")
assert(attr == "fg" or attr == "bg" or attr == "sp")
local get_hl = require("gentags.commons.api").get_hl
local hl_value = get_hl(highlight)
if type(hl_value) == "table" and type(hl_value[attr]) == "number" then
return string.format("#%06x", hl_value[attr])
end
return nil
end
--- @param highlights string|string[]
--- @param attr "fg"|"bg"|string
--- @param fallback_color string?
--- @return string?, integer, string?
M.get_color_with_fallback = function(highlights, attr, fallback_color)
assert(type(highlights) == "string" or type(highlights) == "table")
assert(type(attr) == "string")
local hls = type(highlights) == "string" and { highlights } or highlights --[[@as table]]
local get_hl = require("gentags.commons.api").get_hl
for i, hl in ipairs(hls) do
local hl_value = get_hl(hl)
if type(hl_value) == "table" and type(hl_value[attr]) == "number" then
return string.format("#%06x", hl_value[attr]), i, hl
end
end
return fallback_color, -1, nil
end
return M

View File

@@ -0,0 +1,291 @@
---@diagnostic disable: lowercase-global
-----------------------------------------------------------------------------
-- Provides support for color manipulation in HSL color space.
--
-- http://sputnik.freewisdom.org/lib/colors/
--
-- License: MIT/X
--
-- (c) 2008 Yuri Takhteyev (yuri@freewisdom.org) *
--
-- * rgb_to_hsl() implementation was contributed by Markus Fleck-Graffe.
-----------------------------------------------------------------------------
module(..., package.seeall)
local Color = {}
local Color_mt = { __metatable = {}, __index = Color }
-----------------------------------------------------------------------------
-- Instantiates a new "color".
--
-- @param H hue (0-360) _or_ an RGB string ("#930219")
-- @param S saturation (0.0-1.0)
-- @param L lightness (0.0-1.0)
-- @return an instance of Color
-----------------------------------------------------------------------------
function new(H, S, L)
if type(H) == "string" and H:sub(1, 1) == "#" and H:len() == 7 then
H, S, L = rgb_string_to_hsl(H)
end
assert(Color_mt)
return setmetatable({ H = H, S = S, L = L }, Color_mt)
end
-----------------------------------------------------------------------------
-- Converts an HSL triplet to RGB
-- (see http://homepages.cwi.nl/~steven/css/hsl.html).
--
-- @param h hue (0-360)
-- @param s saturation (0.0-1.0)
-- @param L lightness (0.0-1.0)
-- @return an R, G, and B component of RGB
-----------------------------------------------------------------------------
function hsl_to_rgb(h, s, L)
h = h / 360
local m1, m2
if L <= 0.5 then
m2 = L * (s + 1)
else
m2 = L + s - L * s
end
m1 = L * 2 - m2
local function _h2rgb(m1, m2, h)
if h < 0 then
h = h + 1
end
if h > 1 then
h = h - 1
end
if h * 6 < 1 then
return m1 + (m2 - m1) * h * 6
elseif h * 2 < 1 then
return m2
elseif h * 3 < 2 then
return m1 + (m2 - m1) * (2 / 3 - h) * 6
else
return m1
end
end
return _h2rgb(m1, m2, h + 1 / 3), _h2rgb(m1, m2, h), _h2rgb(m1, m2, h - 1 / 3)
end
-----------------------------------------------------------------------------
-- Converts an RGB triplet to HSL.
-- (see http://easyrgb.com)
--
-- @param r red (0.0-1.0)
-- @param g green (0.0-1.0)
-- @param b blue (0.0-1.0)
-- @return corresponding H, S and L components
-----------------------------------------------------------------------------
function rgb_to_hsl(r, g, b)
--r, g, b = r/255, g/255, b/255
local min = math.min(r, g, b)
local max = math.max(r, g, b)
local delta = max - min
local h, s, l = 0, 0, ((min + max) / 2)
if l > 0 and l < 0.5 then
s = delta / (max + min)
end
if l >= 0.5 and l < 1 then
s = delta / (2 - max - min)
end
if delta > 0 then
if max == r and max ~= g then
h = h + (g - b) / delta
end
if max == g and max ~= b then
h = h + 2 + (b - r) / delta
end
if max == b and max ~= r then
h = h + 4 + (r - g) / delta
end
h = h / 6
end
if h < 0 then
h = h + 1
end
if h > 1 then
h = h - 1
end
return h * 360, s, l
end
function rgb_string_to_hsl(rgb)
return rgb_to_hsl(
tonumber(rgb:sub(2, 3), 16) / 256,
tonumber(rgb:sub(4, 5), 16) / 256,
tonumber(rgb:sub(6, 7), 16) / 256
)
end
-----------------------------------------------------------------------------
-- Converts the color to an RGB string.
--
-- @return a 6-digit RGB representation of the color prefixed
-- with "#" (suitable for inclusion in HTML)
-----------------------------------------------------------------------------
function Color:to_rgb()
local r, g, b = hsl_to_rgb(self.H, self.S, self.L)
local rgb = { hsl_to_rgb(self.H, self.S, self.L) }
local buffer = "#"
for i, v in ipairs(rgb) do
buffer = buffer .. string.format("%02x", math.floor(v * 256 + 0.5))
end
return buffer
end
-----------------------------------------------------------------------------
-- Creates a new color with hue different by delta.
--
-- @param delta a delta for hue.
-- @return a new instance of Color.
-----------------------------------------------------------------------------
function Color:hue_offset(delta)
return new((self.H + delta) % 360, self.S, self.L)
end
-----------------------------------------------------------------------------
-- Creates a complementary color.
--
-- @return a new instance of Color
-----------------------------------------------------------------------------
function Color:complementary()
return self:hue_offset(180)
end
-----------------------------------------------------------------------------
-- Creates two neighboring colors (by hue), offset by "angle".
--
-- @param angle the difference in hue between this color and the
-- neighbors
-- @return two new instances of Color
-----------------------------------------------------------------------------
function Color:neighbors(angle)
local angle = angle or 30
return self:hue_offset(angle), self:hue_offset(360 - angle)
end
-----------------------------------------------------------------------------
-- Creates two new colors to make a triadic color scheme.
--
-- @return two new instances of Color
-----------------------------------------------------------------------------
function Color:triadic()
return self:neighbors(120)
end
-----------------------------------------------------------------------------
-- Creates two new colors, offset by angle from this colors complementary.
--
-- @param angle the difference in hue between the complementary and
-- the returned colors
-- @return two new instances of Color
-----------------------------------------------------------------------------
function Color:split_complementary(angle)
return self:neighbors(180 - (angle or 30))
end
-----------------------------------------------------------------------------
-- Creates a new color with saturation set to a new value.
--
-- @param saturation the new saturation value (0.0 - 1.0)
-- @return a new instance of Color
-----------------------------------------------------------------------------
function Color:desaturate_to(saturation)
return new(self.H, saturation, self.L)
end
-----------------------------------------------------------------------------
-- Creates a new color with saturation set to a old saturation times r.
--
-- @param r the multiplier for the new saturation
-- @return a new instance of Color
-----------------------------------------------------------------------------
function Color:desaturate_by(r)
return new(self.H, self.S * r, self.L)
end
-----------------------------------------------------------------------------
-- Creates a new color with lightness set to a new value.
--
-- @param lightness the new lightness value (0.0 - 1.0)
-- @return a new instance of Color
-----------------------------------------------------------------------------
function Color:lighten_to(lightness)
return new(self.H, self.S, lightness)
end
-----------------------------------------------------------------------------
-- Creates a new color with lightness set to a old lightness times r.
--
-- @param r the multiplier for the new lightness
-- @return a new instance of Color
-----------------------------------------------------------------------------
function Color:lighten_by(r)
return new(self.H, self.S, self.L * r)
end
-----------------------------------------------------------------------------
-- Creates n variations of this color using supplied function and returns
-- them as a table.
--
-- @param f the function to create variations
-- @param n the number of variations
-- @return a table with n values containing the new colors
-----------------------------------------------------------------------------
function Color:variations(f, n)
n = n or 5
local results = {}
for i = 1, n do
table.insert(results, f(self, i, n))
end
return results
end
-----------------------------------------------------------------------------
-- Creates n tints of this color and returns them as a table
--
-- @param n the number of tints
-- @return a table with n values containing the new colors
-----------------------------------------------------------------------------
function Color:tints(n)
local f = function(color, i, n)
return color:lighten_to(color.L + (1 - color.L) / n * i)
end
return self:variations(f, n)
end
-----------------------------------------------------------------------------
-- Creates n shades of this color and returns them as a table
--
-- @param n the number of shades
-- @return a table with n values containing the new colors
-----------------------------------------------------------------------------
function Color:shades(n)
local f = function(color, i, n)
return color:lighten_to(color.L - color.L / n * i)
end
return self:variations(f, n)
end
function Color:tint(r)
return self:lighten_to(self.L + (1 - self.L) * r)
end
function Color:shade(r)
return self:lighten_to(self.L - self.L * r)
end
Color_mt.__tostring = Color.to_rgb

View File

@@ -49,36 +49,20 @@ local CSS_COLORS = {
steelblue = M.escape("fg", "#4682B4"),
}
--- @param attr "fg"|"bg"
--- @param hl string
--- @return string?
M.retrieve = function(attr, hl)
assert(type(hl) == "string")
assert(attr == "bg" or attr == "fg")
local gui = vim.fn.has("termguicolors") > 0 and vim.o.termguicolors
local family = gui and "gui" or "cterm"
local pattern = gui and "^#[%l%d]+" or "^[%d]+$"
local code =
vim.fn.synIDattr(vim.fn.synIDtrans(vim.fn.hlID(hl)), attr, family) --[[@as string]]
if string.find(code, pattern) then
return code
end
return nil
end
--- @param text string the text content to be rendered
--- @param name string the ANSI color name or RGB color codes
--- @param hl string? the highlighting group name
--- @return string
M.render = function(text, name, hl)
local strings = require("gentags.commons.strings")
local str = require("gentags.commons.str")
local fgfmt = nil
local fgcode = strings.not_empty(hl) and M.retrieve("fg", hl --[[@as string]])
local hlcodes = str.not_empty(hl)
and require("gentags.commons.api").get_hl(hl --[[@as string]])
or nil
if type(fgcode) == "string" then
fgfmt = M.escape("fg", fgcode)
local fgcode = type(hlcodes) == "table" and hlcodes.fg or nil
if type(fgcode) == "number" then
fgfmt = M.escape("fg", string.format("#%06x", fgcode))
elseif CSS_COLORS[name] then
fgfmt = CSS_COLORS[name]
else
@@ -86,10 +70,9 @@ M.render = function(text, name, hl)
end
local fmt = nil
local bgcode = strings.not_empty(hl) and M.retrieve("bg", hl --[[@as string]])
or nil
if type(bgcode) == "string" then
local bgcolor = M.escape("bg", bgcode)
local bgcode = type(hlcodes) == "table" and hlcodes.bg or nil
if type(bgcode) == "number" then
local bgcolor = M.escape("bg", string.format("#%06x", bgcode))
fmt = string.format("%s;%s", fgfmt, bgcolor)
else
fmt = fgfmt

View File

@@ -0,0 +1,502 @@
local M = {}
-- FileLineReader {
--- @class commons.FileLineReader
--- @field filename string file name.
--- @field handler integer file handle.
--- @field filesize integer file size in bytes.
--- @field offset integer current read position.
--- @field batchsize integer chunk size for each read operation running internally.
--- @field buffer string? internal data buffer.
local FileLineReader = {}
--- @param filename string
--- @param batchsize integer?
--- @return commons.FileLineReader?
function FileLineReader:open(filename, batchsize)
local uv = require("gentags.commons.uv")
local handler = uv.fs_open(filename, "r", 438) --[[@as integer]]
if type(handler) ~= "number" then
error(
string.format(
"|commons.fileio - FileLineReader:open| failed to fs_open file: %s",
vim.inspect(filename)
)
)
return nil
end
local fstat = uv.fs_fstat(handler) --[[@as table]]
if type(fstat) ~= "table" then
error(
string.format(
"|commons.fileio - FileLineReader:open| failed to fs_fstat file: %s",
vim.inspect(filename)
)
)
uv.fs_close(handler)
return nil
end
local o = {
filename = filename,
handler = handler,
filesize = fstat.size,
offset = 0,
batchsize = batchsize or 4096,
buffer = nil,
}
setmetatable(o, self)
self.__index = self
return o
end
--- @private
--- @return integer
function FileLineReader:_read_chunk()
local uv = require("gentags.commons.uv")
local chunksize = (self.filesize >= self.offset + self.batchsize) and self.batchsize
or (self.filesize - self.offset)
if chunksize <= 0 then
return 0
end
local data, --[[@as string?]]
read_err,
read_name =
uv.fs_read(self.handler, chunksize, self.offset)
if read_err then
error(
string.format(
"|commons.fileio - FileLineReader:_read_chunk| failed to fs_read file: %s, read_error:%s, read_name:%s",
vim.inspect(self.filename),
vim.inspect(read_err),
vim.inspect(read_name)
)
)
return -1
end
-- append to buffer
self.buffer = self.buffer and (self.buffer .. data) or data --[[@as string]]
self.offset = self.offset + #data
return #data
end
--- @return boolean
function FileLineReader:has_next()
self:_read_chunk()
return self.buffer ~= nil and string.len(self.buffer) > 0
end
--- @return string?
function FileLineReader:next()
--- @return string?
local function impl()
local str = require("gentags.commons.str")
if self.buffer == nil then
return nil
end
self.buffer = self.buffer:gsub("\r\n", "\n")
local nextpos = str.find(self.buffer, "\n")
if nextpos then
local line = self.buffer:sub(1, nextpos - 1)
self.buffer = self.buffer:sub(nextpos + 1)
return line
else
return nil
end
end
repeat
local nextline = impl()
if nextline then
return nextline
end
until self:_read_chunk() <= 0
local nextline = impl()
if nextline then
return nextline
else
local buf = self.buffer
self.buffer = nil
return buf
end
end
-- Close the file reader.
function FileLineReader:close()
local uv = require("gentags.commons.uv")
if self.handler then
uv.fs_close(self.handler)
self.handler = nil
end
end
M.FileLineReader = FileLineReader
-- FileLineReader }
-- CachedFileReader {
--- @class commons.CachedFileReader
--- @field filename string
--- @field cache string?
local CachedFileReader = {}
--- @param filename string
--- @return commons.CachedFileReader
function CachedFileReader:open(filename)
local o = {
filename = filename,
cache = nil,
}
setmetatable(o, self)
self.__index = self
return o
end
--- @param opts {trim:boolean?}?
--- @return string?
function CachedFileReader:read(opts)
opts = opts or {}
opts.trim = type(opts.trim) == "boolean" and opts.trim or false
if self.cache == nil then
self.cache = M.readfile(self.filename)
end
if self.cache == nil then
return self.cache
end
return opts.trim and vim.trim(self.cache) or self.cache
end
--- @return string?
function CachedFileReader:reset()
local saved = self.cache
self.cache = nil
return saved
end
M.CachedFileReader = CachedFileReader
-- CachedFileReader }
--- @param filename string
--- @param opts {trim:boolean?}?
--- @return string?
M.readfile = function(filename, opts)
opts = opts or { trim = false }
opts.trim = type(opts.trim) == "boolean" and opts.trim or false
local f = io.open(filename, "r")
if f == nil then
return nil
end
local content = f:read("*a")
f:close()
return opts.trim and vim.trim(content) or content
end
--- @param filename string
--- @param on_complete fun(data:string?):any
--- @param opts {trim:boolean?}?
M.asyncreadfile = function(filename, on_complete, opts)
local uv = require("gentags.commons.uv")
opts = opts or { trim = false }
opts.trim = type(opts.trim) == "boolean" and opts.trim or false
local open_result, open_err = uv.fs_open(filename, "r", 438, function(open_complete_err, fd)
if open_complete_err then
error(
string.format(
"failed to complete open(r) file %s: %s",
vim.inspect(filename),
vim.inspect(open_complete_err)
)
)
return
end
uv.fs_fstat(fd --[[@as integer]], function(fstat_err, stat)
if fstat_err then
error(
string.format(
"failed to fstat file %s: %s",
vim.inspect(filename),
vim.inspect(fstat_err)
)
)
return
end
if not stat then
error(
string.format(
"failed to fstat file %s (empty stat): %s",
vim.inspect(filename),
vim.inspect(fstat_err)
)
)
return
end
uv.fs_read(fd --[[@as integer]], stat.size, 0, function(read_err, data)
if read_err then
error(
string.format(
"failed to read file %s: %s",
vim.inspect(filename),
vim.inspect(read_err)
)
)
return
end
uv.fs_close(fd --[[@as integer]], function(close_err)
on_complete((opts.trim and type(data) == "string") and vim.trim(data) or data)
if close_err then
error(
string.format(
"failed to close file %s: %s",
vim.inspect(filename),
vim.inspect(close_err)
)
)
end
end)
end)
end)
end)
assert(
open_result ~= nil,
string.format(
"failed to open(read) file: %s, error: %s",
vim.inspect(filename),
vim.inspect(open_err)
)
)
end
--- @param filename string
--- @return string[]|nil
M.readlines = function(filename)
local ok, reader = pcall(M.FileLineReader.open, M.FileLineReader, filename) --[[@as commons.FileLineReader]]
if not ok or reader == nil then
return nil
end
local results = {}
while reader:has_next() do
table.insert(results, reader:next())
end
reader:close()
return results
end
--- @param filename string
--- @param opts {on_line:fun(line:string):any,on_complete:fun(bytes:integer):any,on_error:fun(err:string?):any,batchsize:integer?}
M.asyncreadlines = function(filename, opts)
assert(type(opts) == "table")
assert(type(opts.on_line) == "function")
---@diagnostic disable-next-line: undefined-field
local batchsize = opts.batchsize or 4096
local function _handle_error(err, msg)
---@diagnostic disable-next-line: undefined-field
if type(opts.on_error) == "function" then
---@diagnostic disable-next-line: undefined-field
opts.on_error(err)
else
error(
string.format(
"failed to async read file(%s): %s, error: %s",
vim.inspect(msg),
vim.inspect(filename),
vim.inspect(err)
)
)
end
end
local uv = require("gentags.commons.uv")
local open_result, open_err = uv.fs_open(filename, "r", 438, function(open_complete_err, fd)
if open_complete_err then
_handle_error(open_complete_err, "fs_open complete")
return
end
local fstat_result, fstat_err = uv.fs_fstat(
fd --[[@as integer]],
function(fstat_complete_err, stat)
if fstat_complete_err then
_handle_error(fstat_complete_err, "fs_fstat complete")
return
end
if stat == nil then
_handle_error("stat is nil", "fs_fstat complete")
return
end
local fsize = stat.size
local offset = 0
local buffer = nil
local function _process(buf, fn_line_processor)
local str = require("gentags.commons.str")
local i = 1
while i <= #buf do
local newline_pos = str.find(buf, "\n", i)
if not newline_pos then
break
end
local line = buf:sub(i, newline_pos - 1)
fn_line_processor(line)
i = newline_pos + 1
end
return i
end
local function _chunk_read()
local read_result, read_err = uv.fs_read(
fd --[[@as integer]],
batchsize,
offset,
function(read_complete_err, data)
if read_complete_err then
_handle_error(read_complete_err, "fs_read complete")
return
end
if data then
offset = offset + #data
buffer = buffer and (buffer .. data) or data --[[@as string]]
buffer = buffer:gsub("\r\n", "\n")
local pos = _process(buffer, opts.on_line)
-- truncate the processed lines if still exists any
buffer = pos <= #buffer and buffer:sub(pos, #buffer) or nil
else
-- no more data
-- if buffer still has not been processed
if buffer then
local pos = _process(buffer, opts.on_line)
buffer = pos <= #buffer and buffer:sub(pos, #buffer) or nil
-- process all the left buffer till the end of file
if buffer then
opts.on_line(buffer)
end
end
-- close file
local close_result, close_err = uv.fs_close(
fd --[[@as integer]],
function(close_complete_err)
if close_complete_err then
_handle_error(close_complete_err, "fs_close complete")
end
---@diagnostic disable-next-line: undefined-field
if type(opts.on_complete) == "function" then
---@diagnostic disable-next-line: undefined-field
opts.on_complete(fsize)
end
end
)
if close_result == nil then
_handle_error(close_err, "fs_close")
end
end
end
)
if read_result == nil then
_handle_error(read_err, "fs_read")
end
end
_chunk_read()
end
)
if fstat_result == nil then
_handle_error(fstat_err, "fs_fstat")
end
end)
if open_result == nil then
_handle_error(open_err, "fs_open")
end
end
-- AsyncFileLineReader }
--- @param filename string file name.
--- @param content string file content.
--- @return integer returns `0` if success, returns `-1` if failed.
M.writefile = function(filename, content)
local f = io.open(filename, "w")
if not f then
return -1
end
f:write(content)
f:close()
return 0
end
--- @param filename string file name.
--- @param content string file content.
--- @param on_complete fun(bytes:integer?):any callback on write complete.
--- 1. `bytes`: written data bytes.
M.asyncwritefile = function(filename, content, on_complete)
local uv = require("gentags.commons.uv")
uv.fs_open(filename, "w", 438, function(open_err, fd)
if open_err then
error(
string.format("failed to open(w) file %s: %s", vim.inspect(filename), vim.inspect(open_err))
)
return
end
---@diagnostic disable-next-line: param-type-mismatch
uv.fs_write(fd, content, nil, function(write_err, bytes)
if write_err then
error(
string.format(
"failed to write file %s: %s",
vim.inspect(filename),
vim.inspect(write_err)
)
)
return
end
---@diagnostic disable-next-line: param-type-mismatch
uv.fs_close(fd, function(close_err)
if close_err then
error(
string.format(
"failed to close(w) file %s: %s",
vim.inspect(filename),
vim.inspect(close_err)
)
)
return
end
if type(on_complete) == "function" then
on_complete(bytes)
end
end)
end)
end)
end
--- @param filename string file name.
--- @param lines string[] content lines.
--- @return integer returns `0` if success, returns `-1` if failed.
M.writelines = function(filename, lines)
local f = io.open(filename, "w")
if not f then
return -1
end
assert(type(lines) == "table")
for _, line in ipairs(lines) do
assert(type(line) == "string")
f:write(line .. "\n")
end
f:close()
return 0
end
return M

View File

@@ -1,328 +0,0 @@
local M = {}
-- FileLineReader {
--- @class commons.FileLineReader
--- @field filename string file name.
--- @field handler integer file handle.
--- @field filesize integer file size in bytes.
--- @field offset integer current read position.
--- @field batchsize integer chunk size for each read operation running internally.
--- @field buffer string? internal data buffer.
local FileLineReader = {}
--- @param filename string
--- @param batchsize integer?
--- @return commons.FileLineReader?
function FileLineReader:open(filename, batchsize)
local uv = require("gentags.commons.uv")
local handler = uv.fs_open(filename, "r", 438) --[[@as integer]]
if type(handler) ~= "number" then
error(
string.format(
"|commons.fileios - FileLineReader:open| failed to fs_open file: %s",
vim.inspect(filename)
)
)
return nil
end
local fstat = uv.fs_fstat(handler) --[[@as table]]
if type(fstat) ~= "table" then
error(
string.format(
"|commons.fileios - FileLineReader:open| failed to fs_fstat file: %s",
vim.inspect(filename)
)
)
uv.fs_close(handler)
return nil
end
local o = {
filename = filename,
handler = handler,
filesize = fstat.size,
offset = 0,
batchsize = batchsize or 4096,
buffer = nil,
}
setmetatable(o, self)
self.__index = self
return o
end
--- @private
--- @return integer
function FileLineReader:_read_chunk()
local uv = require("gentags.commons.uv")
local chunksize = (self.filesize >= self.offset + self.batchsize)
and self.batchsize
or (self.filesize - self.offset)
if chunksize <= 0 then
return 0
end
local data, --[[@as string?]]
read_err,
read_name =
uv.fs_read(self.handler, chunksize, self.offset)
if read_err then
error(
string.format(
"|commons.fileios - FileLineReader:_read_chunk| failed to fs_read file: %s, read_error:%s, read_name:%s",
vim.inspect(self.filename),
vim.inspect(read_err),
vim.inspect(read_name)
)
)
return -1
end
-- append to buffer
self.buffer = self.buffer and (self.buffer .. data) or data --[[@as string]]
self.offset = self.offset + #data
return #data
end
--- @return boolean
function FileLineReader:has_next()
self:_read_chunk()
return self.buffer ~= nil and string.len(self.buffer) > 0
end
--- @return string?
function FileLineReader:next()
--- @return string?
local function impl()
local strings = require("gentags.commons.strings")
if self.buffer == nil then
return nil
end
self.buffer = self.buffer:gsub("\r\n", "\n")
local nextpos = strings.find(self.buffer, "\n")
if nextpos then
local line = self.buffer:sub(1, nextpos - 1)
self.buffer = self.buffer:sub(nextpos + 1)
return line
else
return nil
end
end
repeat
local nextline = impl()
if nextline then
return nextline
end
until self:_read_chunk() <= 0
local nextline = impl()
if nextline then
return nextline
else
local buf = self.buffer
self.buffer = nil
return buf
end
end
-- Close the file reader.
function FileLineReader:close()
local uv = require("gentags.commons.uv")
if self.handler then
uv.fs_close(self.handler)
self.handler = nil
end
end
M.FileLineReader = FileLineReader
-- FileLineReader }
--- @param filename string
--- @param opts {trim:boolean?}?
--- @return string?
M.readfile = function(filename, opts)
opts = opts or { trim = false }
opts.trim = type(opts.trim) == "boolean" and opts.trim or false
local f = io.open(filename, "r")
if f == nil then
return nil
end
local content = f:read("*a")
f:close()
return opts.trim and vim.trim(content) or content
end
--- @param filename string file name.
--- @param on_complete fun(data:string?):nil callback on read complete.
--- 1. `data`: the file content.
--- @param opts {trim:boolean?}? options:
--- 1. `trim`: whether to trim whitespaces around text content, by default `false`.
M.asyncreadfile = function(filename, on_complete, opts)
local uv = require("gentags.commons.uv")
opts = opts or { trim = false }
opts.trim = type(opts.trim) == "boolean" and opts.trim or false
uv.fs_open(filename, "r", 438, function(open_err, fd)
if open_err then
error(
string.format(
"failed to open(r) file %s: %s",
vim.inspect(filename),
vim.inspect(open_err)
)
)
return
end
uv.fs_fstat(
---@diagnostic disable-next-line: param-type-mismatch
fd,
function(fstat_err, stat)
if fstat_err then
error(
string.format(
"failed to fstat file %s: %s",
vim.inspect(filename),
vim.inspect(fstat_err)
)
)
return
end
if not stat then
error(
string.format(
"failed to fstat file %s (empty stat): %s",
vim.inspect(filename),
vim.inspect(fstat_err)
)
)
return
end
---@diagnostic disable-next-line: param-type-mismatch
uv.fs_read(fd, stat.size, 0, function(read_err, data)
if read_err then
error(
string.format(
"failed to read file %s: %s",
vim.inspect(filename),
vim.inspect(read_err)
)
)
return
end
---@diagnostic disable-next-line: param-type-mismatch
uv.fs_close(fd, function(close_err)
on_complete(
(opts.trim and type(data) == "string") and vim.trim(data) or data
)
if close_err then
error(
string.format(
"failed to close file %s: %s",
vim.inspect(filename),
vim.inspect(close_err)
)
)
end
end)
end)
end
)
end)
end
--- @param filename string
--- @return string[]|nil
M.readlines = function(filename)
local reader = M.FileLineReader:open(filename) --[[@as commons.FileLineReader]]
if not reader then
return nil
end
local results = {}
while reader:has_next() do
table.insert(results, reader:next())
end
reader:close()
return results
end
--- @param filename string file name.
--- @param content string file content.
--- @return integer returns `0` if success, returns `-1` if failed.
M.writefile = function(filename, content)
local f = io.open(filename, "w")
if not f then
return -1
end
f:write(content)
f:close()
return 0
end
--- @param filename string file name.
--- @param content string file content.
--- @param on_complete fun(bytes:integer?):any callback on write complete.
--- 1. `bytes`: written data bytes.
M.asyncwritefile = function(filename, content, on_complete)
local uv = require("gentags.commons.uv")
uv.fs_open(filename, "w", 438, function(open_err, fd)
if open_err then
error(
string.format(
"failed to open(w) file %s: %s",
vim.inspect(filename),
vim.inspect(open_err)
)
)
return
end
---@diagnostic disable-next-line: param-type-mismatch
uv.fs_write(fd, content, nil, function(write_err, bytes)
if write_err then
error(
string.format(
"failed to write file %s: %s",
vim.inspect(filename),
vim.inspect(write_err)
)
)
return
end
---@diagnostic disable-next-line: param-type-mismatch
uv.fs_close(fd, function(close_err)
if close_err then
error(
string.format(
"failed to close(w) file %s: %s",
vim.inspect(filename),
vim.inspect(close_err)
)
)
return
end
if type(on_complete) == "function" then
on_complete(bytes)
end
end)
end)
end)
end
--- @param filename string file name.
--- @param lines string[] content lines.
--- @return integer returns `0` if success, returns `-1` if failed.
M.writelines = function(filename, lines)
local f = io.open(filename, "w")
if not f then
return -1
end
assert(type(lines) == "table")
for _, line in ipairs(lines) do
assert(type(line) == "string")
f:write(line .. "\n")
end
f:close()
return 0
end
return M

View File

@@ -50,8 +50,7 @@ function Formatter:new(fmt, opts)
assert(type(fmt) == "string")
opts = opts or { datefmt = "%Y-%m-%d %H:%M:%S", msecsfmt = "%06d" }
opts.datefmt = type(opts.datefmt) == "string" and opts.datefmt
or "%Y-%m-%d %H:%M:%S"
opts.datefmt = type(opts.datefmt) == "string" and opts.datefmt or "%Y-%m-%d %H:%M:%S"
opts.msecsfmt = type(opts.msecsfmt) == "string" and opts.msecsfmt or "%06d"
local o = {
@@ -80,7 +79,7 @@ local FORMATTING_TAGS = {
--- @param meta table<string,any>
--- @return string
function Formatter:format(meta)
local strings = require("gentags.commons.strings")
local str = require("gentags.commons.str")
local n = string.len(self.fmt)
@@ -95,10 +94,7 @@ function Formatter:format(meta)
return false
end
return strings.startswith(
string.sub(self.fmt, idx, endpos),
FORMATTING_TAGS[tag]
)
return str.startswith(string.sub(self.fmt, idx, endpos), FORMATTING_TAGS[tag])
end
return impl
end
@@ -195,8 +191,7 @@ function ConsoleHandler:write(meta)
local msg_lines = vim.split(meta.MESSAGE, "\n", { plain = true })
for _, line in ipairs(msg_lines) do
local chunks = {}
local line_meta =
vim.tbl_deep_extend("force", vim.deepcopy(meta), { MESSAGE = line })
local line_meta = vim.tbl_deep_extend("force", vim.deepcopy(meta), { MESSAGE = line })
local record = self.formatter:format(line_meta)
table.insert(chunks, {
record,
@@ -230,9 +225,8 @@ function FileHandler:new(filepath, filemode, formatter)
assert(filemode == "a" or filemode == "w" or filemode == nil)
if formatter == nil then
formatter = Formatter:new(
"%(asctime)s,%(msecs)d [%(filename)s:%(lineno)d] %(levelname)s: %(message)s"
)
formatter =
Formatter:new("%(asctime)s,%(msecs)d [%(filename)s:%(lineno)d] %(levelname)s: %(message)s")
end
filemode = filemode ~= nil and string.lower(filemode) or "a"
@@ -240,10 +234,7 @@ function FileHandler:new(filepath, filemode, formatter)
if filemode == "w" then
filehandle = io.open(filepath, "w")
assert(
filehandle ~= nil,
string.format("failed to open file:%s", vim.inspect(filepath))
)
assert(filehandle ~= nil, string.format("failed to open file:%s", vim.inspect(filepath)))
end
local o = {
@@ -513,8 +504,7 @@ M.setup = function(opts)
local SEPARATOR = IS_WINDOWS and "\\" or "/"
local filepath = string.format(
"%s%s",
type(conf.file_log_dir) == "string" and (conf.file_log_dir .. SEPARATOR)
or "",
type(conf.file_log_dir) == "string" and (conf.file_log_dir .. SEPARATOR) or "",
conf.file_log_name
)
local file_handler = FileHandler:new(filepath, conf.file_log_mode or "a")
@@ -532,7 +522,7 @@ M.has = function(name)
end
--- @param name string
--- @return commons.logging.Logger?
--- @return commons.logging.Logger
M.get = function(name)
assert(type(name) == "string")
return NAMESPACE[name]
@@ -541,10 +531,7 @@ end
--- @param logger commons.logging.Logger
M.add = function(logger)
assert(type(logger) == "table")
assert(
(type(logger.name) == "string" and string.len(logger.name) > 0)
or logger.name ~= nil
)
assert((type(logger.name) == "string" and string.len(logger.name) > 0) or logger.name ~= nil)
assert(NAMESPACE[logger.name] == nil)
NAMESPACE[logger.name] = logger
end

View File

@@ -0,0 +1,286 @@
---@mod micro-async
---@alias micro-async.SelectOpts { prompt: string?, format_item: nil|fun(item: any): string, kind: string? }
---@alias micro-async.InputOpts { prompt: string?, default: string?, completion: string?, highlight: fun(text: string) }
---@class micro-async.Cancellable
---@field cancel fun(self: micro-async.Cancellable)
---@field is_cancelled fun(self: micro-async.Cancellable): boolean
---@class micro-async.Task: micro-async.Cancellable
---@field thread thread
---@field resume fun(self: micro-async.Task, ...: any):micro-async.Cancellable?
local yield = coroutine.yield
local resume = coroutine.resume
local running = coroutine.running
---@type table<thread, micro-async.Task>
---@private
local handles = setmetatable({}, {
__mode = "k",
})
---@private
local function is_cancellable(task)
return type(task) == "table"
and vim.is_callable(task.cancel)
and vim.is_callable(task.is_cancelled)
end
---@param fn fun(...): ...
---@return micro-async.Task
---@private
local function new_task(fn)
local thread = coroutine.create(fn)
local cancelled = false
local task = {}
local current = nil
function task:cancel()
if not cancelled then
cancelled = true
if current and not current:is_cancelled() then
current:cancel()
end
end
end
function task:resume(...)
if not cancelled then
local ok, rv = resume(thread, ...)
if not ok then
self:cancel()
error(rv)
end
if is_cancellable(rv) then
current = rv
end
end
end
handles[thread] = task
return task
end
local Async = {}
---@text Create a callback function that resumes the current or specified coroutine when called.
---
---@param co thread | nil The thread to resume, defaults to the running one.
---@return fun(args:...)
function Async.callback(co)
co = co or running()
return function(...)
if co and handles[co] then
handles[co]:resume(...)
end
end
end
---@text Create a callback function that resumes the current or specified coroutine when called,
---and is wrapped in `vim.schedule` to ensure the API is safe to call.
---
---@param co thread | nil The thread to resume, defaults to the running one.
---@return fun(args:...)
function Async.scheduled_callback(co)
co = co or running()
return function(...)
handles[co]:resume(...)
end
end
---@text Create an async function that can be called from a synchronous context.
---Cannot return values as it is non-blocking.
---
---@param fn fun(...):...
---@return fun(...): micro-async.Task
function Async.void(fn)
local task = new_task(fn)
return function(...)
task:resume(...)
return task
end
end
---@text Run a function asynchronously and call the callback with the result.
---
---@param fn fun(...):...
---@param cb fun(...)
---@param ... any
---@return micro-async.Task
function Async.run(fn, cb, ...)
local task = new_task(function(...)
cb(fn(...))
end)
task:resume(...)
return task
end
---@text Run an async function syncrhonously and return the result.
---@text WARNING: This will completely block Neovim's main thread!
---
---@param fn fun(...):...
---@param timeout_ms integer?
---@param ... any
---@return boolean
---@return any ...
function Async.block_on(fn, timeout_ms, ...)
local done, result = false, nil
Async.run(fn, function(...)
result, done = { ... }, true
end, ...)
vim.wait(timeout_ms or 1000, function()
return done
end)
return done, result and unpack(result)
end
---@text Wrap a callback-style function to be async. Add an additional `callback` parameter at the
---end of function, to yield value on its callback. And the `argc` parameter should be parameters
---count + 1 (with an additional `callback` parameter).
---
---@param fn fun(...): ...any
---@param argc integer
---@return fun(...): ...
function Async.wrap(fn, argc)
return function(...)
local args = { ... }
args[argc] = Async.callback()
return yield(fn(unpack(args)))
end
end
---@text Wrap a callback-style function to be async, with the callback wrapped in `vim.schedule_wrap`
---to ensure it is safe to call the nvim API.
---
---@param fn fun(...): ...any
---@param argc integer
---@return fun(...): ...
function Async.scheduled_wrap(fn, argc)
return function(...)
local args = { ... }
args[argc] = Async.scheduled_callback()
return yield(fn(unpack(args)))
end
end
---@text Yields to the Neovim scheduler
---
---@async
function Async.schedule()
return yield(vim.schedule(Async.callback()))
end
---@text Yields the current task, resuming when the specified timeout has elapsed.
---
---@async
---@param timeout integer
function Async.defer(timeout)
yield({
---@type uv_timer_t
timer = vim.defer_fn(Async.callback(), timeout),
cancel = function(self)
if not self.timer:is_closing() then
if self.timer:is_active() then
self.timer:stop()
end
self.timer:close()
end
end,
is_cancelled = function(self)
return self.timer:is_closing()
end,
})
end
---@text Wrapper that creates and queues a work request, yields, and resumes the current task with the results.
---
---@async
---@param fn fun(...):...
---@param ... ...uv.aliases.threadargs
---@return ...uv.aliases.threadargs
function Async.work(fn, ...)
local uv = require("gentags.commons.micro-async.uv")
return uv.queue_work(uv.new_work(fn), ...)
end
---@text Async vim.system
---
---@async
---@param cmd string[] Command to run
---@param opts table Options to pass to `vim.system`
Async.system = function(cmd, opts)
return yield(vim.system(cmd, opts, Async.callback()))
end
---@text Join multiple async functions and call the callback with the results.
---@param ... fun():...
function Async.join(...)
local thunks = { ... }
local remaining = #thunks
local results = {}
local wrapped = function()
for i, thunk in ipairs(thunks) do
results[i] = { thunk() }
remaining = remaining - 1
if remaining == 0 then
return unpack(results)
end
end
end
return wrapped()
end
---@module "commons.micro-async.lsp"
---@private
Async.lsp = nil
---@module "commons.micro-async.uv"
---@private
Async.uv = nil
---@private
Async.ui = {}
---@async
---@param items any[]
---@param opts micro-async.SelectOpts
---@return any|nil, integer|nil
Async.ui.select = function(items, opts)
vim.ui.select(items, opts, Async.callback())
local win = vim.api.nvim_get_current_win()
local cancelled = false
return yield({
cancel = function()
vim.api.nvim_win_close(win, true)
cancelled = true
end,
is_cancelled = function()
return cancelled
end,
})
end
---@async
---@param opts micro-async.InputOpts
---@return string|nil
Async.ui.input = function(opts)
return yield(vim.ui.input(opts, Async.scheduled_callback()))
end
setmetatable(Async, {
__index = function(_, k)
local ok, mod = pcall(require, "commons.micro-async." .. k)
if ok then
return mod
end
end,
})
return Async

View File

@@ -0,0 +1,59 @@
---@diagnostic disable: undefined-doc-name
---@mod micro-async.lsp
local wrap = require("gentags.commons.micro-async").wrap
---Async wrapper for LSP requests
local lsp = {}
---Async wrapper around `vim.lsp.buf_request`.
---@type async fun(bufnr: integer, method: string, params: table?): error: lsp.ResponseError?, result: table, context: lsp.HandlerContext, config: table?
lsp.buf_request = wrap(vim.lsp.buf_request, 4)
---Async wrapper around `vim.lsp.buf_request_all`.
---@type async fun(bufnr: integer, method: string, params: table?): table<integer, { error: lsp.ResponseError, result: table }>
lsp.buf_request_all = wrap(vim.lsp.buf_request_all, 4)
lsp.request = {}
lsp.request.references = function(buf, params)
return lsp.buf_request(buf, "textDocument/references", params)
end
lsp.request.definition = function(buf, params)
return lsp.buf_request(buf, "textDocument/definition", params)
end
lsp.request.type_definition = function(buf, params)
return lsp.buf_request(buf, "textDocument/typeDefinition", params)
end
lsp.request.implementation = function(buf, params)
return lsp.buf_request(buf, "textDocument/implementation", params)
end
lsp.request.rename = function(buf, params)
return lsp.buf_request(buf, "textDocument/rename", params)
end
lsp.request.signature_help = function(buf, params)
return lsp.buf_request(buf, "textDocument/signatureHelp", params)
end
lsp.request.document_symbols = function(buf, params)
return lsp.buf_request(buf, "textDocument/documentSymbol", params)
end
lsp.request.hover = function(buf, params)
return lsp.buf_request(buf, "textDocument/hover", params)
end
lsp.request.inlay_hint = function(buf, params)
return lsp.buf_request(buf, "textDocument/inlayHint", params)
end
lsp.request.code_actions = function(buf, params)
return lsp.buf_request(buf, "textDocument/codeAction", params)
end
return lsp

View File

@@ -0,0 +1,124 @@
---@mod micro-async.uv
local a = require("gentags.commons.micro-async")
local yield, callback, scheduled_wrap = a.yield, a.callback, a.scheduled_wrap
local luv = vim.uv or vim.loop
---Async vim.uv wrapper
local uv = {}
---@type async fun(path: string, entries: integer): err: string?, dir: luv_dir_t
uv.fs_opendir = function(path, entries)
---@diagnostic disable-next-line: param-type-mismatch
return yield(luv.fs_opendir(path, callback(), entries))
end
---@type async fun(dir: luv_dir_t): err: string?, entries: uv.aliases.fs_readdir_entries[]
uv.fs_readdir = scheduled_wrap(luv.fs_readdir, 2)
---@type async fun(path: string): err: string?, path: string?
uv.fs_realpath = scheduled_wrap(luv.fs_realpath, 2)
---@type async fun(path: string): err: string?, path: string?
uv.fs_readlink = scheduled_wrap(luv.fs_readlink, 2)
---@type async fun(path: string, mode: integer): err: string?, permissions: boolean?
uv.fs_access = scheduled_wrap(luv.fs_access, 3)
---@type async fun(fd: integer, size: integer, offset: integer?): err: string?, data: string?
uv.fs_read = scheduled_wrap(luv.fs_read, 4)
---@type async fun(path: string, new_path: string): err: string?, success: boolean?
uv.fs_rename = scheduled_wrap(luv.fs_rename, 2)
---@type async fun(path: string, flags: integer | uv.aliases.fs_access_flags, mode: integer): err: string?, fs: integer?
uv.fs_open = scheduled_wrap(luv.fs_open, 4)
---@type async fun(template: string): err: string?, fd: integer?, path: string?
uv.fs_mkstemp = scheduled_wrap(luv.fs_mkstemp, 2)
---@type async fun(template: string): err: string?, path: string?
uv.fs_mkdtemp = scheduled_wrap(luv.fs_mkdtemp, 2)
---@type async fun(path: string): err: string?, success: boolean?
uv.fs_rmdir = scheduled_wrap(luv.fs_rmdir, 2)
---@type async fun(path: string, mode: integer): err: string?, success: boolean?
uv.fs_mkdir = scheduled_wrap(luv.fs_mkdir, 3)
---@type async fun(path: string, mode: integer): err: string?, success: boolean?
uv.fs_chmod = scheduled_wrap(luv.fs_chmod, 3)
---@type async fun(path: string, uid: integer, gid: integer): err: string?, success: boolean?
uv.fs_chown = scheduled_wrap(luv.fs_chown, 4)
---@type async fun(fd: integer, mode: integer): err: string?, success: boolean?
uv.fs_fchmod = scheduled_wrap(luv.fs_fchmod, 3)
---@type async fun(fd: integer, uid: integer, gid: integer): err: string?, success: boolean?
uv.fs_fchown = scheduled_wrap(luv.fs_fchown, 4)
---@type async fun(address: uv.aliases.getsockname_rtn)
uv.getnameinfo = scheduled_wrap(luv.getnameinfo, 2)
---@type async fun(path: string, new_path: string, flags: uv.aliases.fs_copyfile_flags): err: string?, success: boolean?
uv.fs_copyfile = scheduled_wrap(luv.fs_copyfile, 4)
---@type async fun(fd: integer, atime: integer, mtime: integer): err: string?, success: boolean?
uv.fs_futime = scheduled_wrap(luv.fs_futime, 4)
---@type async fun(fd: integer, offset: integer): err: string?, success: boolean?
uv.fs_ftruncate = scheduled_wrap(luv.fs_ftruncate, 3)
---@type async fun(path: string, new_path: string): err: string?, success: boolean?
uv.fs_link = scheduled_wrap(luv.fs_link, 3)
---@type async fun(path: string): err: string?, success: boolean?
uv.fs_unlink = scheduled_wrap(luv.fs_unlink, 2)
---@type async fun(fd: integer, data: string | string[], offset: integer?): err: string?, success: boolean?
uv.fs_write = scheduled_wrap(luv.fs_write, 4)
---@type async fun(path: string, newpath: string, flags: integer | uv.aliases.fs_symlink_flags): err: string?, success: boolean?
uv.fs_symlink = scheduled_wrap(luv.fs_symlink, 3)
---@type async fun(path: string, atime: integer, mtime: integer): err: string?, success: boolean?
uv.fs_lutime = scheduled_wrap(luv.fs_lutime, 4)
---@type async fun(path: string): err: string?, stat: uv.aliases.fs_stat_table
uv.fs_stat = scheduled_wrap(luv.fs_stat, 2)
---@type async fun(path: string): err: string?, stat: uv_fs_t?
uv.fs_scandir = scheduled_wrap(luv.fs_scandir, 2)
---@type async fun(path: string): err: string?, stat: uv.aliases.fs_statfs_stats
uv.fs_statfs = scheduled_wrap(luv.fs_statfs, 2)
---@type async fun(fd: integer): err: string?, success: boolean?
uv.fs_fsync = scheduled_wrap(luv.fs_fsync, 2)
---@type async fun(out_fd: integer, in_fd: integer, in_offset: integer, size: integer): err: string?, bytes: integer?
uv.fs_sendfile = scheduled_wrap(luv.fs_sendfile, 4)
---@type async fun(fd: integer): err: string?, success: boolean?
uv.fs_fdatasync = scheduled_wrap(luv.fs_fdatasync, 2)
---@type async fun(fd: integer): err: string?, success: boolean?
uv.fs_close = scheduled_wrap(luv.fs_close, 2)
---@type async fun(path: string, atime: integer, mtime: integer): err: string?, success: boolean?
uv.fs_utime = scheduled_wrap(luv.fs_utime, 4)
---@type async fun(fn: fun(...:uv.aliases.threadargs):...: uv.aliases.threadargs): luv_work_ctx_t
uv.new_work = function(fn)
return luv.new_work(fn, callback())
end
---@type async fun(work: luv_work_ctx_t, ...:uv.aliases.threadargs): ...: uv.aliases.threadargs
uv.queue_work = function(work, ...)
return yield(luv.queue_work(work, ...))
end
return uv

View File

@@ -4,46 +4,79 @@ local M = {}
M.INT32_MAX = 2147483647
M.INT32_MIN = -2147483648
M._RELATIVE_PRECISION = 1e-09
M._ABSOLUTE_PRECISION = 0.0
-- https://docs.python.org/3/library/math.html#math.isclose
--
--- @param a number?
--- @param b number?
--- @param rel_tol number?
--- @param abs_tol number?
--- @return boolean
M.eq = function(a, b)
return type(a) == "number" and type(b) == "number" and a == b
M.eq = function(a, b, rel_tol, abs_tol)
if type(a) ~= "number" or type(b) ~= "number" then
return false
end
if a == b then
return true
end
rel_tol = rel_tol or M._RELATIVE_PRECISION
abs_tol = abs_tol or M._ABSOLUTE_PRECISION
return math.abs(a - b) <= math.max(rel_tol * math.max(math.abs(a), math.abs(b)), abs_tol)
end
--- @param a number?
--- @param b number?
--- @param rel_tol number?
--- @param abs_tol number?
--- @return boolean
M.ne = function(a, b)
return not M.eq(a, b)
M.ne = function(a, b, rel_tol, abs_tol)
return not M.eq(a, b, rel_tol, abs_tol)
end
--- @param a number?
--- @param b number?
--- @param rel_tol number?
--- @param abs_tol number?
--- @return boolean
M.gt = function(a, b)
return type(a) == "number" and type(b) == "number" and a > b
M.gt = function(a, b, rel_tol, abs_tol)
if type(a) ~= "number" or type(b) ~= "number" then
return false
end
return M.ne(a, b, rel_tol, abs_tol) and a > b
end
--- @param a number?
--- @param b number?
--- @param rel_tol number?
--- @param abs_tol number?
--- @return boolean
M.ge = function(a, b)
return M.gt(a, b) or M.eq(a, b)
M.ge = function(a, b, rel_tol, abs_tol)
return M.gt(a, b, rel_tol, abs_tol) or M.eq(a, b, rel_tol, abs_tol)
end
--- @param a number?
--- @param b number?
--- @param rel_tol number?
--- @param abs_tol number?
--- @return boolean
M.lt = function(a, b)
return type(a) == "number" and type(b) == "number" and a < b
M.lt = function(a, b, rel_tol, abs_tol)
if type(a) ~= "number" or type(b) ~= "number" then
return false
end
return M.ne(a, b, rel_tol, abs_tol) and a < b
end
--- @param a number?
--- @param b number?
--- @param rel_tol number?
--- @param abs_tol number?
--- @return boolean
M.le = function(a, b)
return M.lt(a, b) or M.eq(a, b)
M.le = function(a, b, rel_tol, abs_tol)
return M.lt(a, b, rel_tol, abs_tol) or M.eq(a, b, rel_tol, abs_tol)
end
--- @param value number
@@ -83,10 +116,7 @@ end
M.max = function(f, a, ...)
assert(
type(f) == "function",
string.format(
"first param 'f' must be unary-function returns number value:%s",
vim.inspect(f)
)
string.format("first param 'f' must be unary-function returns number value:%s", vim.inspect(f))
)
local maximal_item = a
local maximal_value = f(a)
@@ -107,10 +137,7 @@ end
M.min = function(f, a, ...)
assert(
type(f) == "function",
string.format(
"first param 'f' must be unary-function returns number value:%s",
vim.inspect(f)
)
string.format("first param 'f' must be unary-function returns number value:%s", vim.inspect(f))
)
local minimal_item = a
local minimal_value = f(a)
@@ -129,15 +156,8 @@ end
--- @return number
M.random = function(m, n)
local rand_result, rand_err = require("gentags.commons.uv").random(4)
if rand_result == nil then
if m == nil and n == nil then
return math.random()
elseif m ~= nil and n == nil then
return math.random(m)
else
return math.random(m --[[@as integer]], n --[[@as integer]])
end
end
assert(rand_result ~= nil, rand_err)
local bytes = {
string.byte(rand_result --[[@as string]], 1, -1),
}

View File

@@ -63,8 +63,7 @@ end
M._normalize_slash = function(p, opts)
assert(type(p) == "string")
opts = opts or { double_backslash = false }
opts.double_backslash = type(opts.double_backslash) == "boolean"
and opts.double_backslash
opts.double_backslash = type(opts.double_backslash) == "boolean" and opts.double_backslash
or false
-- '\\\\' => '\\'
@@ -126,8 +125,7 @@ end
M.normalize = function(p, opts)
assert(type(p) == "string")
opts = opts or { double_backslash = false, expand = false, resolve = false }
opts.double_backslash = type(opts.double_backslash) == "boolean"
and opts.double_backslash
opts.double_backslash = type(opts.double_backslash) == "boolean" and opts.double_backslash
or false
opts.expand = type(opts.expand) == "boolean" and opts.expand or false
opts.resolve = type(opts.resolve) == "boolean" and opts.resolve or false
@@ -162,7 +160,7 @@ M.normalize = function(p, opts)
-- )
end
return result
return M._normalize_slash(result, opts)
end
--- @param ... any
@@ -211,8 +209,8 @@ end
M.parent = function(p)
p = p or vim.fn.getcwd()
local strings = require("gentags.commons.strings")
if strings.endswith(p, "/") or strings.endswith(p, "\\") then
local str = require("gentags.commons.str")
if str.endswith(p, "/") or str.endswith(p, "\\") then
p = string.sub(p, 1, #p - 1)
end

View File

@@ -0,0 +1,13 @@
local M = {}
local uv = vim.uv or vim.loop
local os_name = uv.os_uname().sysname
local os_name_valid = type(os_name) == "string" and string.len(os_name) > 0
M.OS_NAME = os_name
M.IS_WINDOWS = os_name_valid and os_name:gmatch("Windows") ~= nil
M.IS_MAC = os_name_valid and os_name:match("Darwin") ~= nil
M.IS_BSD = vim.fn.has("bsd") > 0
M.IS_LINUX = os_name_valid and os_name:match("Linux") ~= nil
return M

View File

@@ -0,0 +1,376 @@
---@diagnostic disable: inject-field, undefined-field
local vim = vim
local PackedValue = {}
PackedValue.__index = PackedValue
function PackedValue.new(...)
local values = vim.F.pack_len(...)
local tbl = { _values = values }
return setmetatable(tbl, PackedValue)
end
function PackedValue.pcall(self, f)
local ok_and_value = function(ok, ...)
return ok, PackedValue.new(...)
end
return ok_and_value(pcall(f, self:unpack()))
end
function PackedValue.unpack(self)
return vim.F.unpack_len(self._values)
end
function PackedValue.first(self)
local first = self:unpack()
return first
end
--- @class Promise
local Promise = {}
Promise.__index = Promise
local PromiseStatus =
{ Pending = "pending", Fulfilled = "fulfilled", Rejected = "rejected" }
local is_promise = function(v)
return getmetatable(v) == Promise
end
local new_empty_userdata = function()
return newproxy(true)
end
local new_pending = function(on_fullfilled, on_rejected)
vim.validate({
on_fullfilled = { on_fullfilled, "function", true },
on_rejected = { on_rejected, "function", true },
})
local tbl = {
_status = PromiseStatus.Pending,
_queued = {},
_value = nil,
_on_fullfilled = on_fullfilled,
_on_rejected = on_rejected,
_handled = false,
}
local self = setmetatable(tbl, Promise)
local userdata = new_empty_userdata()
self._unhandled_detector = setmetatable(
{ [self] = userdata },
{ __mode = "k" }
)
getmetatable(userdata).__gc = function()
if self._status ~= PromiseStatus.Rejected or self._handled then
return
end
self._handled = true
vim.schedule(function()
local values = vim.inspect(
{ self._value:unpack() },
{ newline = "", indent = "" }
)
error("unhandled promise rejection: " .. values, 0)
end)
end
return self
end
--- Equivalents to JavaScript's Promise.new.
--- @param executor fun(resolve:fun(...:any),reject:fun(...:any))
--- @return Promise
function Promise.new(executor)
vim.validate({ executor = { executor, "function" } })
local self = new_pending()
local resolve = function(...)
local first = ...
if is_promise(first) then
first
:next(function(...)
self:_resolve(...)
end)
:catch(function(...)
self:_reject(...)
end)
return
end
self:_resolve(...)
end
local reject = function(...)
self:_reject(...)
end
executor(resolve, reject)
return self
end
--- Returns a fulfilled promise.
--- But if the first argument is promise, returns the promise.
--- @param ... any: one promise or non-promises
--- @return Promise
function Promise.resolve(...)
local first = ...
if is_promise(first) then
return first
end
local value = PackedValue.new(...)
return Promise.new(function(resolve, _)
resolve(value:unpack())
end)
end
--- Returns a rejected promise.
--- But if the first argument is promise, returns the promise.
--- @param ... any: one promise or non-promises
--- @return Promise
function Promise.reject(...)
local first = ...
if is_promise(first) then
return first
end
local value = PackedValue.new(...)
return Promise.new(function(_, reject)
reject(value:unpack())
end)
end
function Promise._resolve(self, ...)
if self._status == PromiseStatus.Rejected then
return
end
self._status = PromiseStatus.Fulfilled
self._value = PackedValue.new(...)
for _ = 1, #self._queued do
local promise = table.remove(self._queued, 1)
promise:_start_resolve(self._value)
end
end
function Promise._start_resolve(self, value)
if not self._on_fullfilled then
return vim.schedule(function()
self:_resolve(value:unpack())
end)
end
local ok, result = value:pcall(self._on_fullfilled)
if not ok then
return vim.schedule(function()
self:_reject(result:unpack())
end)
end
local first = result:first()
if not is_promise(first) then
return vim.schedule(function()
self:_resolve(result:unpack())
end)
end
first
:next(function(...)
self:_resolve(...)
end)
:catch(function(...)
self:_reject(...)
end)
end
function Promise._reject(self, ...)
if self._status == PromiseStatus.Fulfilled then
return
end
self._status = PromiseStatus.Rejected
self._value = PackedValue.new(...)
self._handled = self._handled or #self._queued > 0
for _ = 1, #self._queued do
local promise = table.remove(self._queued, 1)
promise:_start_reject(self._value)
end
end
function Promise._start_reject(self, value)
if not self._on_rejected then
return vim.schedule(function()
self:_reject(value:unpack())
end)
end
local ok, result = value:pcall(self._on_rejected)
local first = result:first()
if ok and not is_promise(first) then
return vim.schedule(function()
self:_resolve(result:unpack())
end)
end
if not is_promise(first) then
return vim.schedule(function()
self:_reject(result:unpack())
end)
end
first
:next(function(...)
self:_resolve(...)
end)
:catch(function(...)
self:_reject(...)
end)
end
--- Equivalents to JavaScript's Promise.then.
--- @param on_fullfilled (fun(...:any):any)?: A callback on fullfilled.
--- @param on_rejected (fun(...:any):any)?: A callback on rejected.
--- @return Promise
function Promise.next(self, on_fullfilled, on_rejected)
vim.validate({
on_fullfilled = { on_fullfilled, "function", true },
on_rejected = { on_rejected, "function", true },
})
local promise = new_pending(on_fullfilled, on_rejected)
table.insert(self._queued, promise)
vim.schedule(function()
if self._status == PromiseStatus.Fulfilled then
return self:_resolve(self._value:unpack())
end
if self._status == PromiseStatus.Rejected then
return self:_reject(self._value:unpack())
end
end)
return promise
end
--- Equivalents to JavaScript's Promise.catch.
--- @param on_rejected (fun(...:any):any)?: A callback on rejected.
--- @return Promise
function Promise.catch(self, on_rejected)
return self:next(nil, on_rejected)
end
--- Equivalents to JavaScript's Promise.finally.
--- @param on_finally fun()
--- @return Promise
function Promise.finally(self, on_finally)
vim.validate({ on_finally = { on_finally, "function", true } })
return self
:next(function(...)
on_finally()
return ...
end)
:catch(function(...)
on_finally()
return Promise.reject(...)
end)
end
--- Equivalents to JavaScript's Promise.all.
--- Even if multiple value are resolved, results include only the first value.
--- @param list any[]: promise or non-promise values
--- @return Promise
function Promise.all(list)
vim.validate({ list = { list, "table" } })
return Promise.new(function(resolve, reject)
local remain = #list
if remain == 0 then
return resolve({})
end
local results = {}
for i, e in ipairs(list) do
Promise.resolve(e)
:next(function(...)
local first = ...
results[i] = first
if remain == 1 then
return resolve(results)
end
remain = remain - 1
end)
:catch(function(...)
reject(...)
end)
end
end)
end
--- Equivalents to JavaScript's Promise.race.
--- @param list any[]: promise or non-promise values
--- @return Promise
function Promise.race(list)
vim.validate({ list = { list, "table" } })
return Promise.new(function(resolve, reject)
for _, e in ipairs(list) do
Promise.resolve(e)
:next(function(...)
resolve(...)
end)
:catch(function(...)
reject(...)
end)
end
end)
end
--- Equivalents to JavaScript's Promise.any.
--- Even if multiple value are rejected, errors include only the first value.
--- @param list any[]: promise or non-promise values
--- @return Promise
function Promise.any(list)
vim.validate({ list = { list, "table" } })
return Promise.new(function(resolve, reject)
local remain = #list
if remain == 0 then
return reject({})
end
local errs = {}
for i, e in ipairs(list) do
Promise.resolve(e)
:next(function(...)
resolve(...)
end)
:catch(function(...)
local first = ...
errs[i] = first
if remain == 1 then
return reject(errs)
end
remain = remain - 1
end)
end
end)
end
--- Equivalents to JavaScript's Promise.allSettled.
--- Even if multiple value are resolved/rejected, value/reason is only the first value.
--- @param list any[]: promise or non-promise values
--- @return Promise
function Promise.all_settled(list)
vim.validate({ list = { list, "table" } })
return Promise.new(function(resolve)
local remain = #list
if remain == 0 then
return resolve({})
end
local results = {}
for i, e in ipairs(list) do
Promise.resolve(e)
:next(function(...)
local first = ...
results[i] = { status = PromiseStatus.Fulfilled, value = first }
end)
:catch(function(...)
local first = ...
results[i] = { status = PromiseStatus.Rejected, reason = first }
end)
:finally(function()
if remain == 1 then
return resolve(results)
end
remain = remain - 1
end)
end
end)
end
return Promise

View File

@@ -0,0 +1,39 @@
local helper = require("vusted.helper")
local plugin_name = helper.get_module_root(...)
helper.root = helper.find_plugin_root(plugin_name)
function helper.before_each() end
function helper.after_each()
helper.cleanup_loaded_modules(plugin_name)
vim.cmd([[lua collectgarbage("collect")]])
end
function helper.on_finished()
local finished = false
return setmetatable({
wait = function()
local ok = vim.wait(1000, function()
return finished
end, 10, false)
if not ok then
error("wait timeout")
end
end,
}, {
__call = function()
finished = true
end,
})
end
function helper.wait(promise)
local on_finished = helper.on_finished()
promise:finally(function()
on_finished()
end)
on_finished:wait()
end
return helper

View File

@@ -133,10 +133,7 @@ function _RingBufferIterator:has_next()
if self.index <= 0 or self.index > self.ringbuf.size then
return false
end
if
self.index ~= self.initial_index
and self.ringbuf:_inc(self.index) == self.initial_index
then
if self.index ~= self.initial_index and self.ringbuf:_inc(self.index) == self.initial_index then
return false
end
@@ -186,10 +183,7 @@ function _RingBufferRIterator:has_next()
if self.index <= 0 or self.index > self.ringbuf.size then
return false
end
if
self.index ~= self.initial_index
and self.ringbuf:_dec(self.index) == self.initial_index
then
if self.index ~= self.initial_index and self.ringbuf:_dec(self.index) == self.initial_index then
return false
end

View File

@@ -1,5 +1,14 @@
local NVIM_VERSION_0_10 = false
do
NVIM_VERSION_0_10 = require("gentags.commons.version").ge({ 0, 10 })
end
local M = {}
M.system = (NVIM_VERSION_0_10 and vim.is_callable(vim.system)) and vim.system
or require("gentags.commons._system").run
--- @alias commons.SpawnLineProcessor fun(line:string):any
--- @alias commons.SpawnOpts {on_stdout:commons.SpawnLineProcessor, on_stderr:commons.SpawnLineProcessor, [string]:any}
--- @alias commons.SpawnOnExit fun(completed:vim.SystemCompleted):nil
@@ -18,11 +27,11 @@ M.run = function(cmd, opts, on_exit)
--- @param fn_line_processor commons.SpawnLineProcessor
--- @return integer
local function _process(buffer, fn_line_processor)
local strings = require("gentags.commons.strings")
local str = require("gentags.commons.str")
local i = 1
while i <= #buffer do
local newline_pos = strings.find(buffer, "\n", i)
local newline_pos = str.find(buffer, "\n", i)
if not newline_pos then
break
end
@@ -54,10 +63,8 @@ M.run = function(cmd, opts, on_exit)
stdout_buffer = stdout_buffer and (stdout_buffer .. data) or data
-- search buffer and process each line
local i = _process(stdout_buffer, opts.on_stdout)
-- truncate the printed lines if found any
stdout_buffer = i <= #stdout_buffer
and stdout_buffer:sub(i, #stdout_buffer)
or nil
-- truncate the processed lines if still exists any
stdout_buffer = i <= #stdout_buffer and stdout_buffer:sub(i, #stdout_buffer) or nil
elseif stdout_buffer then
-- foreach the data_buffer and find every line
local i = _process(stdout_buffer, opts.on_stdout)
@@ -88,9 +95,7 @@ M.run = function(cmd, opts, on_exit)
if data then
stderr_buffer = stderr_buffer and (stderr_buffer .. data) or data
local i = _process(stderr_buffer, opts.on_stderr)
stderr_buffer = i <= #stderr_buffer
and stderr_buffer:sub(i, #stderr_buffer)
or nil
stderr_buffer = i <= #stderr_buffer and stderr_buffer:sub(i, #stderr_buffer) or nil
elseif stderr_buffer then
local i = _process(stderr_buffer, opts.on_stderr)
if i <= #stderr_buffer then
@@ -101,10 +106,7 @@ M.run = function(cmd, opts, on_exit)
end
end
local _system = vim.is_callable(vim.system) and vim.system
or require("gentags.commons._system").run
return _system(cmd, {
return M.system(cmd, {
cwd = opts.cwd,
env = opts.env,
clear_env = opts.clear_env,

View File

@@ -142,8 +142,7 @@ M.startswith = function(s, t, opts)
assert(type(t) == "string")
opts = opts or { ignorecase = false }
opts.ignorecase = type(opts.ignorecase) == "boolean" and opts.ignorecase
or false
opts.ignorecase = type(opts.ignorecase) == "boolean" and opts.ignorecase or false
if opts.ignorecase then
return string.len(s) >= string.len(t) and s:sub(1, #t):lower() == t:lower()
@@ -161,17 +160,43 @@ M.endswith = function(s, t, opts)
assert(type(t) == "string")
opts = opts or { ignorecase = false }
opts.ignorecase = type(opts.ignorecase) == "boolean" and opts.ignorecase
or false
opts.ignorecase = type(opts.ignorecase) == "boolean" and opts.ignorecase or false
if opts.ignorecase then
return string.len(s) >= string.len(t)
and s:sub(#s - #t + 1):lower() == t:lower()
return string.len(s) >= string.len(t) and s:sub(#s - #t + 1):lower() == t:lower()
else
return string.len(s) >= string.len(t) and s:sub(#s - #t + 1) == t
end
end
--- @param s string
--- @param p string
--- @param r string
--- @return string, integer
M.replace = function(s, p, r)
assert(type(s) == "string")
assert(type(p) == "string")
assert(type(r) == "string")
local sn = string.len(s)
local pn = string.len(p)
local pos = 1
local matched = 0
local result = s
while pos <= sn do
pos = M.find(result, p, pos) --[[@as integer]]
if type(pos) ~= "number" then
break
end
result = string.sub(result, 1, pos - 1) .. r .. string.sub(result, pos + pn)
pos = pos + pn
matched = matched + 1
end
return result, matched
end
--- @param c string
--- @return boolean
M.isspace = function(c)
@@ -239,7 +264,7 @@ M.setchar = function(s, pos, ch)
assert(string.len(ch) == 1)
local n = string.len(s)
pos = require("gentags.commons.tables").list_index(pos, n)
pos = require("gentags.commons.tbl").list_index(pos, n)
local buffer = ""
if pos > 1 then

View File

@@ -1,91 +0,0 @@
local M = {}
--- @param t any?
--- @return boolean
M.tbl_empty = function(t)
return type(t) ~= "table" or vim.tbl_isempty(t)
end
--- @param t any?
--- @return boolean
M.tbl_not_empty = function(t)
return type(t) == "table" and not vim.tbl_isempty(t)
end
--- @param t any?
--- @param ... any
--- @return any
M.tbl_get = function(t, ...)
local e = t --[[@as table]]
for _, k in ipairs({ ... }) do
if M.tbl_not_empty(e) and e[k] ~= nil then
e = e[k]
else
return nil
end
end
return e
end
--- @param t any[]
--- @param v any
--- @param compare (fun(a:any, b:any):boolean)|nil
--- @return boolean
M.tbl_contains = function(t, v, compare)
assert(type(t) == "table")
for k, item in pairs(t) do
if type(compare) == "function" then
if compare(item, v) then
return true
end
else
if item == v then
return true
end
end
end
return false
end
--- @param l any?
--- @return boolean
M.list_empty = function(l)
return type(l) ~= "table" or #l == 0
end
--- @param l any?
--- @return boolean
M.list_not_empty = function(l)
return type(l) == "table" and #l > 0
end
--- @param i integer
--- @param n integer
--- @return integer
M.list_index = function(i, n)
assert(n > 0)
assert((i >= 1 and i <= n) or (i <= -1 and i >= -n))
return i > 0 and i or (n + i + 1)
end
--- @param l any[]
--- @param v any
--- @param compare (fun(a:any, b:any):boolean)|nil
--- @return boolean
M.list_contains = function(l, v, compare)
assert(type(l) == "table")
for _, item in ipairs(l) do
if type(compare) == "function" then
if compare(item, v) then
return true
end
else
if item == v then
return true
end
end
end
return false
end
return M

730
lua/gentags/commons/tbl.lua Normal file
View File

@@ -0,0 +1,730 @@
local M = {}
--- @param t any?
--- @return boolean
M.tbl_empty = function(t)
return type(t) ~= "table" or vim.tbl_isempty(t)
end
--- @param t any?
--- @return boolean
M.tbl_not_empty = function(t)
return type(t) == "table" and not vim.tbl_isempty(t)
end
--- @param t any?
--- @param ... any
--- @return any
M.tbl_get = function(t, ...)
local e = t --[[@as table]]
for _, k in ipairs({ ... }) do
if type(e) == "table" and e[k] ~= nil then
e = e[k]
else
return nil
end
end
return e
end
--- @param t any[]
--- @param v any
--- @param compare (fun(a:any, b:any):boolean)|nil
--- @return boolean
M.tbl_contains = function(t, v, compare)
assert(type(t) == "table")
for k, item in pairs(t) do
if type(compare) == "function" then
if compare(item, v) then
return true
end
else
if item == v then
return true
end
end
end
return false
end
--- @param l any?
--- @return boolean
M.list_empty = function(l)
return type(l) ~= "table" or #l == 0
end
--- @param l any?
--- @return boolean
M.list_not_empty = function(l)
return type(l) == "table" and #l > 0
end
--- @param i integer
--- @param n integer
--- @return integer
M.list_index = function(i, n)
assert(n >= 0)
return i > 0 and i or (n + i + 1)
end
--- @param l any[]
--- @param v any
--- @param compare (fun(a:any, b:any):boolean)|nil
--- @return boolean
M.list_contains = function(l, v, compare)
assert(type(l) == "table")
for _, item in ipairs(l) do
if type(compare) == "function" then
if compare(item, v) then
return true
end
else
if item == v then
return true
end
end
end
return false
end
--- @class commons.List
--- @field _data any[]
local List = {}
--- @param l any[]
--- @return commons.List
function List:move(l)
assert(type(l) == "table")
local o = { _data = l }
setmetatable(o, self)
self.__index = self
return o
end
--- @param l any[]
--- @return commons.List
function List:copy(l)
assert(type(l) == "table")
local new_l = {}
for i, v in ipairs(l) do
table.insert(new_l, v)
end
return List:move(new_l)
end
--- @param ... any
--- @return commons.List
function List:of(...)
return List:move({ ... })
end
--- @return any[]
function List:data()
return self._data
end
--- @return integer
function List:length()
return #self._data
end
--- @return boolean
function List:empty()
return #self._data == 0
end
--- @param index integer
--- @return any
function List:at(index)
local normalized_index = M.list_index(index, self:length())
return self._data[normalized_index]
end
--- @return any
function List:first()
return self:at(1)
end
--- @return any
function List:last()
return self:at(self:length())
end
--- @param other commons.List
--- @return commons.List
function List:concat(other)
assert(M.is_list(other))
local l = {}
for i, v in ipairs(self._data) do
table.insert(l, v)
end
for i, v in ipairs(other._data) do
table.insert(l, v)
end
return List:move(l)
end
--- @param separator string?
--- @return string
function List:join(separator)
separator = separator or " "
return table.concat(self._data, separator)
end
--- @param f fun(value:any, index:integer):boolean
--- @return boolean
function List:every(f)
assert(type(f) == "function")
for i, v in ipairs(self._data) do
if not f(v, i) then
return false
end
end
return true
end
--- @param f fun(value:any, index:integer):boolean
--- @return boolean
function List:some(f)
assert(type(f) == "function")
for i, v in ipairs(self._data) do
if f(v, i) then
return true
end
end
return false
end
--- @param f fun(value:any, index:integer):boolean
--- @return boolean
function List:none(f)
assert(type(f) == "function")
for i, v in ipairs(self._data) do
if f(v, i) then
return false
end
end
return true
end
--- @param f fun(value:any, index:integer):boolean
--- @return commons.List
function List:filter(f)
assert(type(f) == "function")
local l = {}
for i, v in ipairs(self._data) do
if f(v, i) then
table.insert(l, v)
end
end
return List:move(l)
end
--- @param f fun(value:any, index:integer):boolean
--- @return any?, integer
function List:find(f)
assert(type(f) == "function")
for i, v in ipairs(self._data) do
if f(v, i) then
return v, i
end
end
return nil, -1
end
--- @param f fun(value:any, index:integer):boolean
--- @return any?, integer
function List:findLast(f)
assert(type(f) == "function")
local n = self:length()
for i = n, 1, -1 do
local v = self._data[i]
if f(v, i) then
return v, i
end
end
return nil, -1
end
--- @param value any
--- @param start integer?
--- @param comparator (fun(a:any,b:any):boolean)|nil
--- @return integer?
function List:indexOf(value, start, comparator)
assert(type(comparator) == "function" or comparator == nil)
start = start or 1
local n = self:length()
for i = start, n do
local v = self._data[i]
if type(comparator) == "function" then
if comparator(v, value) then
return i
end
else
if v == value then
return i
end
end
end
return -1
end
--- @param value any
--- @param rstart integer?
--- @param comparator (fun(a:any,b:any):boolean)|nil
--- @return integer?
function List:lastIndexOf(value, rstart, comparator)
assert(type(comparator) == "function" or comparator == nil)
local n = self:length()
rstart = rstart or n
for i = rstart, 1, -1 do
local v = self._data[i]
if type(comparator) == "function" then
if comparator(v, value) then
return i
end
else
if v == value then
return i
end
end
end
return -1
end
--- @param f fun(value:any, index:integer):nil
function List:forEach(f)
assert(type(f) == "function")
for i, v in ipairs(self._data) do
f(v, i)
end
end
--- @param value any
--- @param start integer?
--- @param comparator (fun(a:any,b:any):boolean)|nil
--- @return boolean
function List:includes(value, start, comparator)
return self:indexOf(value, start, comparator) >= 1
end
--- @param f fun(value:any,index:integer):any
--- @return commons.List
function List:map(f)
assert(type(f) == "function")
local l = {}
for i, v in ipairs(self._data) do
table.insert(l, f(v, i))
end
return List:move(l)
end
--- @return any?, boolean
function List:pop()
if self:empty() then
return nil, false
end
return table.remove(self._data, self:length()), true
end
--- @param ... any
function List:push(...)
for i, v in ipairs({ ... }) do
table.insert(self._data, v)
end
end
--- @return any?, boolean
function List:shift()
if self:empty() then
return nil, false
end
return table.remove(self._data, 1), true
end
--- @param ... any
function List:unshift(...)
for i, v in ipairs({ ... }) do
table.insert(self._data, 1, v)
end
end
--- @param f fun(accumulator:any,value:any,index:integer):any
--- @param initialValue any?
--- @return any
function List:reduce(f, initialValue)
assert(type(f) == "function")
if self:empty() then
return initialValue
end
local startIndex = initialValue and 1 or 2
local accumulator = initialValue or self._data[1]
local n = self:length()
local i = startIndex
while i <= n do
accumulator = f(accumulator, self._data[i], i)
i = i + 1
end
return accumulator
end
--- @param f fun(accumulator:any,value:any,index:integer):any
--- @param initialValue any?
--- @return any
function List:reduceRight(f, initialValue)
assert(type(f) == "function")
if self:empty() then
return initialValue
end
local n = self:length()
local startIndex = initialValue and n or self:length() - 1
local accumulator = initialValue or self._data[n]
local i = startIndex
while i >= 1 do
accumulator = f(accumulator, self._data[i], i)
i = i - 1
end
return accumulator
end
--- @return commons.List
function List:reverse()
if self:empty() then
return List:move({})
end
local l = {}
local i = self:length()
while i >= 1 do
table.insert(l, self._data[i])
i = i - 1
end
return List:move(l)
end
--- @param startIndex integer?
--- @param endIndex integer?
--- @return commons.List
function List:slice(startIndex, endIndex)
assert(type(startIndex) == "number" or startIndex == nil)
assert(type(endIndex) == "number" or endIndex == nil)
local n = self:length()
startIndex = startIndex or 1
endIndex = endIndex or n
local l = {}
for i = startIndex, endIndex do
if i >= 1 and i <= n then
table.insert(l, self._data[i])
end
end
return List:move(l)
end
--- @param comparator (fun(a:any,b:any):boolean)|nil
--- @return commons.List
function List:sort(comparator)
local l = {}
for i, v in ipairs(self._data) do
table.insert(l, v)
end
table.sort(l, comparator)
return List:move(l)
end
M.List = List
--- @param o any
--- @return boolean
M.is_list = function(o)
return type(o) == "table" and o.__index == List and getmetatable(o) == List
end
--- @class commons.HashMap
--- @field _data table
local HashMap = {}
--- @param t table
--- @return commons.HashMap
function HashMap:move(t)
assert(type(t) == "table")
local o = { _data = t }
setmetatable(o, self)
self.__index = self
return o
end
--- @param t table
--- @return commons.HashMap
function HashMap:copy(t)
assert(type(t) == "table")
local new_t = {}
for k, v in pairs(t) do
new_t[k] = v
end
return HashMap:move(new_t)
end
--- @param ... {[1]:any,[2]:any}
--- @return commons.HashMap
function HashMap:of(...)
local t = {}
local s = 0
for i, v in ipairs({ ... }) do
t[v[1]] = v[2]
s = s + 1
end
local o = { _data = t }
setmetatable(o, self)
self.__index = self
return o
end
--- @return table
function HashMap:data()
return self._data
end
--- @return integer
function HashMap:size()
local s = 0
for _, _ in pairs(self._data) do
s = s + 1
end
return s
end
--- @return boolean
function HashMap:empty()
return next(self._data) == nil
end
--- @param key any
--- @param value any
function HashMap:set(key, value)
self._data[key] = value
end
--- @param key any
--- @return any?
function HashMap:unset(key)
local old = self._data[key]
self._data[key] = nil
return old
end
--- @param ... any
--- @return any
function HashMap:get(...)
return M.tbl_get(self._data, ...)
end
--- @param key any
--- @return boolean
function HashMap:hasKey(key)
return self._data[key] ~= nil
end
--- @param value any
--- @param comparator (fun(a:any, b:any):boolean)|nil
--- @return boolean
function HashMap:hasValue(value, comparator)
for k, v in pairs(self._data) do
if type(comparator) == "function" and comparator(v, value) then
return true
elseif v == value then
return true
end
end
return false
end
--- @param other commons.HashMap
--- @return commons.HashMap
function HashMap:merge(other)
assert(M.is_hashmap(other))
local t = {}
for k, v in pairs(self._data) do
t[k] = v
end
for k, v in pairs(other._data) do
t[k] = v
end
return HashMap:move(t)
end
--- @param f fun(key:any, value:any):boolean
--- @return boolean
function HashMap:every(f)
assert(type(f) == "function")
for k, v in pairs(self._data) do
if not f(k, v) then
return false
end
end
return true
end
--- @param f fun(key:any, value:any):boolean
--- @return boolean
function HashMap:some(f)
assert(type(f) == "function")
for k, v in pairs(self._data) do
if f(k, v) then
return true
end
end
return false
end
--- @param f fun(key:any, value:any):boolean
--- @return boolean
function HashMap:none(f)
assert(type(f) == "function")
for k, v in pairs(self._data) do
if f(k, v) then
return false
end
end
return true
end
--- @param f fun(key:any, value:any):boolean
--- @return commons.HashMap
function HashMap:filter(f)
assert(type(f) == "function")
local t = {}
for k, v in pairs(self._data) do
if f(k, v) then
t[k] = v
end
end
return HashMap:move(t)
end
--- @param f fun(key:any, value:any):boolean
--- @return any, any
function HashMap:find(f)
assert(type(f) == "function")
for k, v in pairs(self._data) do
if f(k, v) then
return k, v
end
end
return nil, nil
end
--- @param f fun(key:any,value:any):nil
function HashMap:forEach(f)
assert(type(f) == "function")
for k, v in pairs(self._data) do
f(k, v)
end
end
--- @param iterator any?
--- @return any, any
function HashMap:next(iterator)
return next(self._data, iterator)
end
--- @return commons.HashMap
function HashMap:invert()
local t = {}
for k, v in pairs(self._data) do
t[v] = k
end
return HashMap:move(t)
end
--- @param f fun(key:any, value:any):any
--- @return commons.HashMap
function HashMap:mapKeys(f)
assert(type(f) == "function")
local t = {}
for k, v in pairs(self._data) do
t[f(k, v)] = v
end
return HashMap:move(t)
end
--- @param f fun(key:any, value:any):any
--- @return commons.HashMap
function HashMap:mapValues(f)
assert(type(f) == "function")
local t = {}
for k, v in pairs(self._data) do
t[k] = f(k, v)
end
return HashMap:move(t)
end
--- @return any[]
function HashMap:keys()
local keys = {}
for k, _ in pairs(self._data) do
table.insert(keys, k)
end
return keys
end
--- @return any[]
function HashMap:values()
local values = {}
for _, v in pairs(self._data) do
table.insert(values, v)
end
return values
end
--- @return {[1]:any,[2]:any}[]
function HashMap:entries()
local p = {}
for k, v in pairs(self._data) do
table.insert(p, { k, v })
end
return p
end
--- @param f fun(accumulator:any,key:any,value:any):any
--- @param initialValue any
--- @return any
function HashMap:reduce(f, initialValue)
assert(type(f) == "function")
if self:empty() then
return initialValue
end
local accumulator = initialValue
for k, v in pairs(self._data) do
accumulator = f(accumulator, k, v)
end
return accumulator
end
M.HashMap = HashMap
--- @param o any?
--- @return boolean
M.is_hashmap = function(o)
return type(o) == "table" and o.__index == HashMap and getmetatable(o) == HashMap
end
return M

View File

@@ -0,0 +1,82 @@
local M = {}
M.HAS_VIM_VERSION = vim.is_callable(vim.version)
M.HAS_VIM_VERSION_EQ = M.HAS_VIM_VERSION
and type(vim.version) == "table"
and vim.is_callable(vim.version.eq)
M.HAS_VIM_VERSION_GT = M.HAS_VIM_VERSION
and type(vim.version) == "table"
and vim.is_callable(vim.version.gt)
M.HAS_VIM_VERSION_GE = M.HAS_VIM_VERSION
and type(vim.version) == "table"
and vim.is_callable(vim.version.ge)
M.HAS_VIM_VERSION_LT = M.HAS_VIM_VERSION
and type(vim.version) == "table"
and vim.is_callable(vim.version.lt)
M.HAS_VIM_VERSION_LE = M.HAS_VIM_VERSION
and type(vim.version) == "table"
and vim.is_callable(vim.version.le)
--- @param l integer[]
--- @return string
M.to_string = function(l)
assert(type(l) == "table")
local builder = {}
for _, v in ipairs(l) do
table.insert(builder, tostring(v))
end
return table.concat(builder, ".")
end
--- @param s string
--- @return integer[]
M.to_list = function(s)
assert(type(s) == "string")
local splits = vim.split(s, ".", { plain = true })
local result = {}
for _, v in ipairs(splits) do
table.insert(result, tonumber(v))
end
return result
end
--- @param ver string|integer[]
--- @return boolean
M.lt = function(ver)
if M.HAS_VIM_VERSION and M.HAS_VIM_VERSION_LT then
if type(ver) == "string" then
ver = M.to_list(ver)
end
return vim.version.lt(vim.version(), ver)
else
if type(ver) == "table" then
ver = M.to_string(ver)
end
ver = "nvim-" .. ver
return vim.fn.has(ver) < 1
end
end
--- @param ver string|integer[]
--- @return boolean
M.ge = function(ver)
if M.HAS_VIM_VERSION and M.HAS_VIM_VERSION_EQ and M.HAS_VIM_VERSION_GT then
if type(ver) == "string" then
ver = M.to_list(ver)
end
return vim.version.gt(vim.version(), ver) or vim.version.eq(vim.version(), ver)
elseif M.HAS_VIM_VERSION and M.HAS_VIM_VERSION_GE then
if type(ver) == "string" then
ver = M.to_list(ver)
end
return vim.version.ge(vim.version(), ver)
else
if type(ver) == "table" then
ver = M.to_string(ver)
end
ver = "nvim-" .. ver
return vim.fn.has(ver) > 0
end
end
return M

View File

@@ -1 +1 @@
5.1.0
11.0.1

View File

@@ -10,70 +10,78 @@ local Defaults = {
ctags = {
["--tag-relative=never"] = true,
-- exclude logs
["--exclude=*.log"] = true,
-- Recommended Options:
-- exclude vcs
["--exclude=*.git"] = true,
["--exclude=*.svg"] = true,
["--exclude=*.hg"] = true,
-- exclude nodejs
["--exclude=node_modules"] = true,
-- exclude tags/cscope
["--exclude=*tags*"] = true,
["--exclude=*cscope.*"] = true,
-- exclude python
["--exclude=*.pyc"] = true,
-- exclude jvm class
["--exclude=*.class"] = true,
-- exclude VS project generated
["--exclude=*.pdb"] = true,
["--exclude=*.sln"] = true,
["--exclude=*.csproj"] = true,
["--exclude=*.csproj.user"] = true,
-- exclude blobs
["--exclude=*.exe"] = true,
["--exclude=*.dll"] = true,
["--exclude=*.mp3"] = true,
["--exclude=*.ogg"] = true,
["--exclude=*.flac"] = true,
["--exclude=*.swp"] = true,
["--exclude=*.swo"] = true,
["--exclude=*.bmp"] = true,
["--exclude=*.gif"] = true,
["--exclude=*.ico"] = true,
["--exclude=*.jpg"] = true,
["--exclude=*.png"] = true,
["--exclude=*.rar"] = true,
["--exclude=*.zip"] = true,
["--exclude=*.tar"] = true,
["--exclude=*.tar.gz"] = true,
["--exclude=*.tar.xz"] = true,
["--exclude=*.tar.bz2"] = true,
["--exclude=*.pdf"] = true,
["--exclude=*.doc"] = true,
["--exclude=*.docx"] = true,
["--exclude=*.ppt"] = true,
["--exclude=*.pptx"] = true,
-- -- exclude logs
-- ["--exclude=*.log"] = true,
--
-- -- exclude vcs
-- ["--exclude=*.git"] = true,
-- ["--exclude=*.svg"] = true,
-- ["--exclude=*.hg"] = true,
--
-- -- exclude nodejs
-- ["--exclude=node_modules"] = true,
--
-- -- exclude tags/cscope
-- ["--exclude=*tags*"] = true,
-- ["--exclude=*cscope.*"] = true,
--
-- -- exclude python
-- ["--exclude=*.pyc"] = true,
--
-- -- exclude jvm class
-- ["--exclude=*.class"] = true,
--
-- -- exclude VS project generated
-- ["--exclude=*.pdb"] = true,
-- ["--exclude=*.sln"] = true,
-- ["--exclude=*.csproj"] = true,
-- ["--exclude=*.csproj.user"] = true,
--
-- -- exclude blobs
-- ["--exclude=*.exe"] = true,
-- ["--exclude=*.dll"] = true,
-- ["--exclude=*.mp3"] = true,
-- ["--exclude=*.ogg"] = true,
-- ["--exclude=*.flac"] = true,
-- ["--exclude=*.swp"] = true,
-- ["--exclude=*.swo"] = true,
-- ["--exclude=*.bmp"] = true,
-- ["--exclude=*.gif"] = true,
-- ["--exclude=*.ico"] = true,
-- ["--exclude=*.jpg"] = true,
-- ["--exclude=*.png"] = true,
-- ["--exclude=*.rar"] = true,
-- ["--exclude=*.zip"] = true,
-- ["--exclude=*.tar"] = true,
-- ["--exclude=*.tar.gz"] = true,
-- ["--exclude=*.tar.xz"] = true,
-- ["--exclude=*.tar.bz2"] = true,
-- ["--exclude=*.pdf"] = true,
-- ["--exclude=*.doc"] = true,
-- ["--exclude=*.docx"] = true,
-- ["--exclude=*.ppt"] = true,
-- ["--exclude=*.pptx"] = true,
},
-- workspace detection
workspace = { [".git"] = true, [".svn"] = true },
-- excluded filetypes
disabled_filetypes = { ["neo-tree"] = true, ["NvimTree"] = true },
workspace = { [".git"] = true, [".svn"] = true, [".hg"] = true },
-- excluded workspace
disabled_workspaces = {},
-- excluded files
disabled_files = {},
-- excluded filetypes
disabled_filetypes = {
-- Recommended Options:
-- ["neo-tree"] = true,
-- ["NvimTree"] = true,
},
-- excluded filenames
disabled_filenames = {},
-- cache directory
-- For *NIX: `~/.cache/nvim/gentags.nvim`.

View File

@@ -1,7 +1,7 @@
local tbl = require("gentags.commons.tbl")
local str = require("gentags.commons.str")
local logging = require("gentags.commons.logging")
local spawn = require("gentags.commons.spawn")
local tables = require("gentags.commons.tables")
local strings = require("gentags.commons.strings")
local uv = require("gentags.commons.uv")
local configs = require("gentags.configs")
@@ -22,10 +22,10 @@ local TAGS_INITED_MAP = {}
--- @param ctx gentags.Context
M.load = function(ctx)
local logger = logging.get("gentags") --[[@as commons.logging.Logger]]
local logger = logging.get("gentags")
logger:debug("|load| ctx:%s", vim.inspect(ctx))
if strings.empty(ctx.tags_file) then
if str.empty(ctx.tags_file) then
return
end
if TAGS_LOADED_MAP[ctx.tags_file] then
@@ -43,11 +43,11 @@ end
--- @param on_exit (fun():nil)|nil
--- @return table?
M._write = function(ctx, on_exit)
local logger = logging.get("gentags") --[[@as commons.logging.Logger]]
local logger = logging.get("gentags")
logger:debug("|_write| ctx:%s", vim.inspect(ctx))
-- no tags name
if strings.empty(ctx.tags_file) then
if str.empty(ctx.tags_file) then
return nil
end
-- tags name already exist, e.g. already running ctags for this tags
@@ -56,7 +56,7 @@ M._write = function(ctx, on_exit)
end
local tmpfile = vim.fn.tempname() --[[@as string]]
if strings.empty(tmpfile) then
if str.empty(tmpfile) then
return nil
end
@@ -123,10 +123,10 @@ M._write = function(ctx, on_exit)
end
local cfg = configs.get()
local opts_map = vim.deepcopy(tables.tbl_get(cfg, "ctags") or {})
local opts_table = vim.deepcopy(tbl.tbl_get(cfg, "ctags") or {})
local opts = {}
for o, v in pairs(opts_map) do
if type(o) == "string" and string.len(o) > 0 and v then
for o, v in ipairs(opts_table) do
if str.not_empty(o) and v then
table.insert(opts, o)
end
end
@@ -134,7 +134,7 @@ M._write = function(ctx, on_exit)
local cwd = nil
if ctx.mode == "workspace" then
logger:ensure(
strings.not_empty(ctx.workspace),
str.not_empty(ctx.workspace),
"ctx.workspace cannot be empty: %s",
vim.inspect(ctx)
)
@@ -149,7 +149,7 @@ M._write = function(ctx, on_exit)
if ctx.mode == "singlefile" then
-- only generate tags for target source file
logger:ensure(
strings.not_empty(ctx.filename),
str.not_empty(ctx.filename),
"ctx.filename cannot be empty: %s",
vim.inspect(ctx)
)
@@ -181,13 +181,13 @@ end
--- @param on_exit (fun():nil)|nil
--- @return table?
M._append = function(ctx, on_exit)
local logger = logging.get("gentags") --[[@as commons.logging.Logger]]
local logger = logging.get("gentags")
logger:debug("|_append| ctx:%s", vim.inspect(ctx))
if strings.empty(ctx.filename) then
if str.empty(ctx.filename) then
return nil
end
if strings.empty(ctx.tags_file) then
if str.empty(ctx.tags_file) then
return nil
end
if TAGS_LOCKING_MAP[ctx.tags_file] then
@@ -231,7 +231,13 @@ M._append = function(ctx, on_exit)
end
local cfg = configs.get()
local opts = vim.deepcopy(tables.tbl_get(cfg, "ctags") or {})
local opts_table = vim.deepcopy(tbl.tbl_get(cfg, "ctags") or {})
local opts = {}
for o, v in pairs(opts_table) do
if str.not_empty(o) and v then
table.insert(opts, o)
end
end
-- append mode
table.insert(opts, "--append=yes")
@@ -264,7 +270,7 @@ M._append = function(ctx, on_exit)
end
M.init = function(ctx)
if strings.empty(ctx.tags_file) then
if str.empty(ctx.tags_file) then
return
end
if TAGS_INITED_MAP[ctx.tags_file] then
@@ -281,27 +287,27 @@ M.update = function(ctx)
if
ctx.mode == "singlefile"
or (
strings.not_empty(ctx.tags_file)
str.not_empty(ctx.tags_file)
and vim.fn.filereadable(ctx.tags_file) <= 0
)
then
-- if working in singlefile mode, or in workspace mode but the output tags file not exist
-- go back to generate tags for whole workspace
local logger = logging.get("gentags") --[[@as commons.logging.Logger]]
local logger = logging.get("gentags")
logger:debug("|update| go back to init, ctx:%s", vim.inspect(ctx))
vim.schedule(function()
M.init(ctx)
end)
else
local logger = logging.get("gentags") --[[@as commons.logging.Logger]]
local logger = logging.get("gentags")
logger:ensure(
ctx.mode == "workspace",
"ctx.mode must be 'workspace': %s",
vim.inspect(ctx)
)
if strings.empty(ctx.workspace) then
if str.empty(ctx.workspace) then
return
end
@@ -339,7 +345,7 @@ end
--- @param ctx gentags.Context
--- @return gentags.StatusInfo
M.status = function(ctx)
local running = tables.tbl_not_empty(JOBS_MAP)
local running = tbl.tbl_not_empty(JOBS_MAP)
local jobs = 0
for pid, system_obj in pairs(JOBS_MAP) do
jobs = jobs + 1

View File

@@ -1,22 +1,24 @@
local tbl = require("gentags.commons.tbl")
local str = require("gentags.commons.str")
local path = require("gentags.commons.path")
local logging = require("gentags.commons.logging")
local strings = require("gentags.commons.strings")
local paths = require("gentags.commons.paths")
local tables = require("gentags.commons.tables")
local configs = require("gentags.configs")
local utils = require("gentags.utils")
local M = {}
-- A tool module has these APIs: load/init/update/terminate
-- A tool module has these APIs: load/init/update/terminate/status
--
--- @alias gentags.Context {workspace:string?,filename:string?,filetype:string?,tags_file:string?,tags_handle:string?,mode:"workspace"|"singlefile"}
--
--- @alias gentags.LoadMethod fun(ctx:gentags.Context):nil
--- @alias gentags.InitMethod fun(ctx:gentags.Context):nil
--- @alias gentags.UpdateMethod fun(ctx:gentags.Context):nil
--- @alias gentags.TerminateMethod fun(ctx:gentags.Context):nil
--- @alias gentags.StatusInfo {running:boolean,jobs:integer}
--- @alias gentags.StatusMethod fun(ctx:gentags.Context):gentags.StatusInfo
--
--- @alias gentags.Tool {load:gentags.LoadMethod,init:gentags.InitMethod,update:gentags.UpdateMethod,terminate:gentags.TerminateMethod,status:gentags.StatusMethod}
--- @type table<string, gentags.Tool>
local TOOLS_MAP = {
@@ -37,18 +39,14 @@ end
--- @return gentags.Context
M.get_context = function()
local cfg = configs.get()
local logger = logging.get("gentags") --[[@as commons.logging.Logger]]
local logger = logging.get("gentags")
local filename = utils.get_filename()
local filetype = utils.get_filetype()
local filedir = nil
if
strings.not_empty(filename)
and not tables.list_contains(cfg.exclude_filetypes or {}, filetype)
then
filedir = paths.parent(filename)
if str.not_empty(filename) then
filedir = path.parent(filename)
end
local workspace = utils.get_workspace(filedir)
@@ -61,18 +59,15 @@ M.get_context = function()
local tags_handle = nil
local tags_file = nil
if strings.not_empty(workspace) then
if str.not_empty(workspace) then
tags_handle = utils.get_tags_handle(workspace --[[@as string]])
tags_file = utils.get_tags_file(tags_handle --[[@as string]])
elseif
strings.not_empty(filename)
and not tables.list_contains(cfg.exclude_filetypes or {}, filetype)
then
elseif str.not_empty(filename) then
tags_handle = utils.get_tags_handle(filename)
tags_file = utils.get_tags_file(tags_handle)
end
local mode = strings.not_empty(workspace) and "workspace" or "singlefile"
local mode = str.not_empty(workspace) and "workspace" or "singlefile"
return {
workspace = workspace,
@@ -84,54 +79,81 @@ M.get_context = function()
}
end
--- @return boolean
M.enabled = function()
local logger = logging.get("gentags")
local cfg = configs.get()
local filename = utils.get_filename()
local filetype = utils.get_filetype()
if cfg.disabled_filetypes[filetype] then
return false
end
if cfg.disabled_filenames[filename] then
return false
end
local filedir = nil
if str.not_empty(filename) then
filedir = path.parent(filename)
end
local workspace = utils.get_workspace(filedir)
if cfg.disabled_workspaces[workspace] then
return false
end
return true
end
M.load = function()
vim.schedule(function()
local ok, ctx_or_err = pcall(M.get_context)
local logger = logging.get("gentags") --[[@as commons.logging.Logger]]
logger:ensure(ok, "failed to get context:%s", vim.inspect(ctx_or_err))
local logger = logging.get("gentags")
local ok, ctx = pcall(M.get_context)
logger:ensure(ok, "failed to get context:%s", vim.inspect(ctx))
local tool = get_tool()
local ok2, err2 = pcall(tool.load, ctx_or_err)
local ok2, err2 = pcall(tool.load, ctx)
logger:ensure(ok2, "failed to load:%s", vim.inspect(err2))
end)
end
M.init = function()
vim.schedule(function()
local ok, ctx_or_err = pcall(M.get_context)
local logger = logging.get("gentags") --[[@as commons.logging.Logger]]
logger:ensure(ok, "failed to get context:%s", vim.inspect(ctx_or_err))
local logger = logging.get("gentags")
local ok, ctx = pcall(M.get_context)
logger:ensure(ok, "failed to get context:%s", vim.inspect(ctx))
local tool = get_tool()
local ok2, err2 = pcall(tool.init, ctx_or_err)
local ok2, err2 = pcall(tool.init, ctx)
logger:ensure(ok2, "failed to init:%s", vim.inspect(err2))
end)
end
M.update = function()
vim.schedule(function()
local ok, ctx_or_err = pcall(M.get_context)
local logger = logging.get("gentags") --[[@as commons.logging.Logger]]
logger:ensure(ok, "failed to get context:%s", vim.inspect(ctx_or_err))
local logger = logging.get("gentags")
local ok, ctx = pcall(M.get_context)
logger:ensure(ok, "failed to get context:%s", vim.inspect(ctx))
local tool = get_tool()
local ok2, err2 = pcall(tool.update, ctx_or_err)
local ok2, err2 = pcall(tool.update, ctx)
logger:ensure(ok2, "failed to update:%s", vim.inspect(err2))
end)
end
M.terminate = function()
local ok, ctx_or_err = pcall(M.get_context)
local logger = logging.get("gentags") --[[@as commons.logging.Logger]]
logger:ensure(ok, "failed to get context:%s", vim.inspect(ctx_or_err))
local logger = logging.get("gentags")
local ok, ctx = pcall(M.get_context)
logger:ensure(ok, "failed to get context:%s", vim.inspect(ctx))
local tool = get_tool()
local ok2, err2 = pcall(tool.update, ctx_or_err)
local ok2, err2 = pcall(tool.update, ctx)
logger:ensure(ok2, "failed to terminate:%s", vim.inspect(err2))
end
M.status = function()
local ok, ctx_or_err = pcall(M.get_context)
local logger = logging.get("gentags") --[[@as commons.logging.Logger]]
logger:ensure(ok, "failed to get context:%s", vim.inspect(ctx_or_err))
local logger = logging.get("gentags")
local ok, ctx = pcall(M.get_context)
logger:ensure(ok, "failed to get context:%s", vim.inspect(ctx))
local tool = get_tool()
local ok2, err2 = pcall(tool.status, ctx_or_err)
local ok2, err2 = pcall(tool.status, ctx)
logger:ensure(ok2, "failed to get status:%s", vim.inspect(err2))
end

View File

@@ -1,7 +1,7 @@
local logging = require("gentags.commons.logging")
local paths = require("gentags.commons.paths")
local strings = require("gentags.commons.strings")
local str = require("gentags.commons.str")
local path = require("gentags.commons.path")
local uv = require("gentags.commons.uv")
local logging = require("gentags.commons.logging")
local configs = require("gentags.configs")
@@ -17,14 +17,14 @@ M.get_workspace = function(cwd)
-- logger:debug("|get_workspace| 0-cwd:%s", vim.inspect(cwd))
for pattern, value in pairs(configs.get().workspace) do
if value then
local target = paths.join(cwd, pattern)
local target = path.join(cwd, pattern)
-- logger:debug(
-- "|get_workspace| 1-cwd:%s, target:%s",
-- vim.inspect(cwd),
-- vim.inspect(target)
-- )
target =
paths.normalize(target, { double_backslash = true, expand = true })
path.normalize(target, { double_backslash = true, expand = true })
local stat_result, stat_err = uv.fs_stat(target)
-- logger:debug(
-- "|get_workspace| 2-cwd:%s, target:%s, stat result:%s, stat err:%s",
@@ -38,13 +38,13 @@ M.get_workspace = function(cwd)
end
end
end
local parent = paths.parent(cwd)
local parent = path.parent(cwd)
-- logger:debug(
-- "|get_workspace| 3-cwd:%s, parent:%s",
-- vim.inspect(cwd),
-- vim.inspect(parent)
-- )
if strings.blank(parent) then
if str.blank(parent) then
break
end
cwd = parent
@@ -55,7 +55,7 @@ end
--- @return string
M.get_filename = function()
local bufnr = vim.api.nvim_get_current_buf()
local filename = paths.normalize(
local filename = path.normalize(
vim.api.nvim_buf_get_name(bufnr),
{ double_backslash = true, expand = true }
)
@@ -70,44 +70,48 @@ end
--- @param filepath string?
--- @return string?
M.get_tags_handle = function(filepath)
if strings.empty(filepath) then
if str.empty(filepath) then
return nil
end
while
strings.not_empty(filepath)
and strings.endswith(filepath --[[@as string]], "/")
or strings.endswith(filepath --[[@as string]], "\\")
str.not_empty(filepath)
and (
str.endswith(filepath --[[@as string]], "/")
or str.endswith(filepath --[[@as string]], "\\")
)
do
filepath = string.sub(filepath --[[@as string]], 1, #filepath - 1)
end
while
strings.not_empty(filepath)
and strings.startswith(filepath --[[@as string]], "/")
or strings.startswith(filepath --[[@as string]], "\\")
str.not_empty(filepath)
and (
str.startswith(filepath --[[@as string]], "/")
or str.startswith(filepath --[[@as string]], "\\")
)
do
filepath = string.sub(filepath --[[@as string]], 2)
end
filepath = paths.normalize(
filepath = path.normalize(
filepath --[[@as string]],
{ double_backslash = true, expand = true }
)
filepath = filepath:gsub("/", "%-"):gsub(" ", "%-"):gsub(":", "%-")
while strings.startswith(filepath, "-") do
while str.startswith(filepath, "-") do
filepath = string.sub(filepath, 2)
end
while strings.endswith(filepath, "-") do
while str.endswith(filepath, "-") do
filepath = string.sub(filepath, 1, #filepath - 1)
end
local cache_dir = configs.get().cache_dir
return paths.join(cache_dir, filepath)
return path.join(cache_dir, filepath)
end
--- @param tags_handle string?
--- @return string?
M.get_tags_file = function(tags_handle)
if strings.empty(tags_handle) then
if str.empty(tags_handle) then
return nil
end
return tags_handle .. "-tags"

13
selene.toml Normal file
View File

@@ -0,0 +1,13 @@
std = "vim"
exclude = [
"lua/gentags/commons/*.lua",
"lua/gentags/commons/*/*.lua",
"lua/gentags/commons/*/*/*.lua",
]
[lints]
mixed_table = "allow"
unused_variable = "allow"
incorrect_standard_library_use = "allow"

19
vim.toml Normal file
View File

@@ -0,0 +1,19 @@
[selene]
base = "lua51"
[vim]
any = true
[it]
any = true
[describe]
any = true
[before_each]
any = true
[assert]
any = true