mirror of
https://github.com/chenasraf/nvim-treesitter.git
synced 2026-05-18 01:39:00 +00:00
refactor: rewrite installation using jobs and async
Replace sync variants with callback support
This commit is contained in:
committed by
Christian Clason
parent
5aa2984a02
commit
cde679e435
@@ -59,9 +59,6 @@ require'nvim-treesitter'.setup {
|
||||
-- List of parsers to ignore installing
|
||||
ignore_install = { },
|
||||
|
||||
-- Install parsers synchronously (only applied to `ensure_install`)
|
||||
sync_install = false,
|
||||
|
||||
-- Automatically install missing parsers when entering buffer
|
||||
auto_install = false,
|
||||
|
||||
|
||||
2
TODO.md
2
TODO.md
@@ -7,7 +7,6 @@ This document lists the planned and finished changes in this rewrite towards [Nv
|
||||
- [ ] **`query_predicates.lua`:** upstream/remove
|
||||
- [ ] **`parsers.lua`:** modularize?
|
||||
- [ ] **`parsers.lua`:** assign tiers
|
||||
- [ ] **`install.lua`:** fix messages, add sync support (@lewis6991)
|
||||
- [ ] **`install.lua`:** simplify compilation:
|
||||
- hardcode one compiler + args per platform
|
||||
- provide `install.compile_command` for overriding (function that takes files, ...?)
|
||||
@@ -32,5 +31,6 @@ This document lists the planned and finished changes in this rewrite towards [Nv
|
||||
- [X] install parsers to standard directory by default
|
||||
- [X] remove bundled queries from runtimepath; copy on parser install
|
||||
- [X] general refactor and cleanup
|
||||
- [X] rewrite installation using async module (drop support for sync; use callback instead)
|
||||
- [X] switch to upstream injection format
|
||||
- [X] remove locals from highlighting (cf. https://github.com/nvim-treesitter/nvim-treesitter/issues/3944#issuecomment-1458782497)
|
||||
|
||||
@@ -38,16 +38,13 @@ To install supported parsers and queries, put this in your `init.lua` file:
|
||||
|
||||
>lua
|
||||
require'nvim-treesitter.configs'.setup {
|
||||
-- A directory to install the parsers into.
|
||||
-- A directory to install the parsers and queries to.
|
||||
-- Defaults to the `stdpath('data')/site` dir.
|
||||
install_dir = "/some/path/to/store/parsers",
|
||||
|
||||
-- A list of parser names, or "core", "stable", "community", "unstable"
|
||||
ensure_install = { "core", "rust" },
|
||||
|
||||
-- Install parsers synchronously (only applied to `ensure_installed`)
|
||||
sync_install = false,
|
||||
|
||||
-- Automatically install missing parsers when entering buffer
|
||||
auto_install = false,
|
||||
|
||||
@@ -66,10 +63,6 @@ COMMANDS *nvim-treesitter-commands*
|
||||
Install one or more treesitter parsers.
|
||||
You can use |:TSInstall| `all` to install all parsers. Use |:TSInstall!| to
|
||||
force the reinstallation of already installed parsers.
|
||||
*:TSInstallSync*
|
||||
:TSInstallSync {language} ... ~
|
||||
|
||||
Perform the |:TSInstall| operation synchronously.
|
||||
|
||||
*:TSInstallInfo*
|
||||
:TSInstallInfo ~
|
||||
@@ -83,11 +76,6 @@ Update the installed parser for one more {language} or all installed parsers
|
||||
if {language} is omitted. The specified parser is installed if it is not already
|
||||
installed.
|
||||
|
||||
*:TSUpdateSync*
|
||||
:TSUpdateSync {language} ... ~
|
||||
|
||||
Perform the |:TSUpdate| operation synchronously.
|
||||
|
||||
*:TSUninstall*
|
||||
:TSUninstall {language} ... ~
|
||||
|
||||
|
||||
110
lua/nvim-treesitter/async.lua
Normal file
110
lua/nvim-treesitter/async.lua
Normal file
@@ -0,0 +1,110 @@
|
||||
local co = coroutine
|
||||
|
||||
local M = {}
|
||||
|
||||
---Executes a future with a callback when it is done
|
||||
--- @param func function
|
||||
--- @param callback function
|
||||
--- @param ... unknown
|
||||
local function execute(func, callback, ...)
|
||||
local thread = co.create(func)
|
||||
|
||||
local function step(...)
|
||||
local ret = { co.resume(thread, ...) }
|
||||
--- @type boolean, any
|
||||
local stat, nargs_or_err = unpack(ret)
|
||||
|
||||
if not stat then
|
||||
error(
|
||||
string.format(
|
||||
'The coroutine failed with this message: %s\n%s',
|
||||
nargs_or_err,
|
||||
debug.traceback(thread)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
if co.status(thread) == 'dead' then
|
||||
if callback then
|
||||
callback(unpack(ret, 3, table.maxn(ret)))
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
--- @type function, any[]
|
||||
local fn, args = ret[3], { unpack(ret, 4, table.maxn(ret)) }
|
||||
args[nargs_or_err] = step
|
||||
fn(unpack(args, 1, nargs_or_err))
|
||||
end
|
||||
|
||||
step(...)
|
||||
end
|
||||
|
||||
--- Creates an async function with a callback style function.
|
||||
--- @generic F: function
|
||||
--- @param func F
|
||||
--- @param argc integer
|
||||
--- @return F
|
||||
function M.wrap(func, argc)
|
||||
--- @param ... unknown
|
||||
--- @return unknown
|
||||
return function(...)
|
||||
return co.yield(argc, func, ...)
|
||||
end
|
||||
end
|
||||
|
||||
---Use this to create a function which executes in an async context but
|
||||
---called from a non-async context. Inherently this cannot return anything
|
||||
---since it is non-blocking
|
||||
--- @generic F: function
|
||||
--- @param func async F
|
||||
--- @param nargs? integer
|
||||
--- @return F
|
||||
function M.sync(func, nargs)
|
||||
nargs = nargs or 0
|
||||
return function(...)
|
||||
local callback = select(nargs + 1, ...)
|
||||
execute(func, callback, unpack({ ... }, 1, nargs))
|
||||
end
|
||||
end
|
||||
|
||||
--- @param n integer max number of concurrent jobs
|
||||
--- @param interrupt_check? function
|
||||
--- @param thunks function[]
|
||||
--- @return any
|
||||
function M.join(n, interrupt_check, thunks)
|
||||
return co.yield(1, function(finish)
|
||||
if #thunks == 0 then
|
||||
return finish()
|
||||
end
|
||||
|
||||
local remaining = { select(n + 1, unpack(thunks)) }
|
||||
local to_go = #thunks
|
||||
|
||||
local ret = {} --- @type any[]
|
||||
|
||||
local function cb(...)
|
||||
ret[#ret + 1] = { ... }
|
||||
to_go = to_go - 1
|
||||
if to_go == 0 then
|
||||
finish(ret)
|
||||
elseif not interrupt_check or not interrupt_check() then
|
||||
if #remaining > 0 then
|
||||
local next_task = table.remove(remaining)
|
||||
next_task(cb)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, math.min(n, #thunks) do
|
||||
thunks[i](cb)
|
||||
end
|
||||
end, 1)
|
||||
end
|
||||
|
||||
---An async function that when called will yield to the Neovim scheduler to be
|
||||
---able to call the API.
|
||||
--- @type fun()
|
||||
M.main = M.wrap(vim.schedule, 1)
|
||||
|
||||
return M
|
||||
@@ -1,7 +1,6 @@
|
||||
local M = {}
|
||||
|
||||
---@class TSConfig
|
||||
---@field sync_install boolean
|
||||
---@field auto_install boolean
|
||||
---@field ensure_install string[]
|
||||
---@field ignore_install string[]
|
||||
@@ -9,7 +8,6 @@ local M = {}
|
||||
|
||||
---@type TSConfig
|
||||
local config = {
|
||||
sync_install = false,
|
||||
auto_install = false,
|
||||
ensure_install = {},
|
||||
ignore_install = {},
|
||||
@@ -38,7 +36,11 @@ function M.setup(user_data)
|
||||
and not vim.list_contains(M.installed_parsers(), lang)
|
||||
and not vim.list_contains(config.ignore_install, lang)
|
||||
then
|
||||
require('nvim-treesitter.install').install(lang)
|
||||
require('nvim-treesitter.install').install(lang, nil, function()
|
||||
-- Need to pcall since 'FileType' can be triggered multiple times
|
||||
-- per buffer
|
||||
pcall(vim.treesitter.start, args.buf, lang)
|
||||
end)
|
||||
end
|
||||
end,
|
||||
})
|
||||
@@ -48,9 +50,7 @@ function M.setup(user_data)
|
||||
local to_install = M.norm_languages(config.ensure_install, { ignored = true, installed = true })
|
||||
|
||||
if #to_install > 0 then
|
||||
require('nvim-treesitter.install').install(to_install, {
|
||||
with_sync = config.sync_install,
|
||||
})
|
||||
require('nvim-treesitter.install').install(to_install)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -63,9 +63,10 @@ function M.get_install_dir(dir_name)
|
||||
local dir = vim.fs.joinpath(config.install_dir, dir_name)
|
||||
|
||||
if not vim.loop.fs_stat(dir) then
|
||||
local ok, error = pcall(vim.fn.mkdir, dir, 'p', '0755')
|
||||
local ok, err = pcall(vim.fn.mkdir, dir, 'p', '0755')
|
||||
if not ok then
|
||||
vim.notify(error, vim.log.levels.ERROR)
|
||||
local log = require('nvim-treesitter.log')
|
||||
log.error(err --[[@as string]])
|
||||
end
|
||||
end
|
||||
return dir
|
||||
@@ -105,35 +106,37 @@ function M.norm_languages(languages, skip)
|
||||
end
|
||||
languages = parsers.get_available()
|
||||
end
|
||||
--TODO(clason): skip and warn on unavailable parser
|
||||
|
||||
for i, tier in ipairs(parsers.tiers) do
|
||||
if vim.list_contains(languages, tier) then
|
||||
languages = vim.iter.filter(function(l)
|
||||
return l ~= tier
|
||||
end, languages)
|
||||
end, languages) --[[@as string[] ]]
|
||||
vim.list_extend(languages, parsers.get_available(i))
|
||||
end
|
||||
end
|
||||
|
||||
--TODO(clason): support skipping tiers
|
||||
if skip and skip.ignored then
|
||||
local ignored = config.ignore_install
|
||||
languages = vim.iter.filter(function(v)
|
||||
return not vim.list_contains(ignored, v)
|
||||
end, languages)
|
||||
end, languages) --[[@as string[] ]]
|
||||
end
|
||||
|
||||
if skip and skip.installed then
|
||||
local installed = M.installed_parsers()
|
||||
languages = vim.iter.filter(function(v)
|
||||
return not vim.list_contains(installed, v)
|
||||
end, languages)
|
||||
end, languages) --[[@as string[] ]]
|
||||
end
|
||||
|
||||
if skip and skip.missing then
|
||||
local installed = M.installed_parsers()
|
||||
languages = vim.iter.filter(function(v)
|
||||
return vim.list_contains(installed, v)
|
||||
end, languages)
|
||||
end, languages) --[[@as string[] ]]
|
||||
end
|
||||
|
||||
return languages
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
local shell = require('nvim-treesitter.shell_cmds')
|
||||
local install = require('nvim-treesitter.install')
|
||||
local config = require('nvim-treesitter.config')
|
||||
local util = require('nvim-treesitter.util')
|
||||
local tsq = vim.treesitter.query
|
||||
|
||||
local M = {}
|
||||
@@ -62,7 +62,7 @@ local function install_health()
|
||||
vim.health.ok('`git` executable found.')
|
||||
end
|
||||
|
||||
local cc = shell.select_executable(install.compilers)
|
||||
local cc = install.select_executable(install.compilers)
|
||||
if not cc then
|
||||
vim.health.error('`cc` executable not found.', {
|
||||
'Check that any of '
|
||||
@@ -118,6 +118,7 @@ local function query_status(lang, query_group)
|
||||
end
|
||||
|
||||
function M.check()
|
||||
--- @type {[1]: string, [2]: string, [3]: string}[]
|
||||
local error_collection = {}
|
||||
-- Installation dependency checks
|
||||
install_health()
|
||||
@@ -144,22 +145,19 @@ function M.check()
|
||||
if #error_collection > 0 then
|
||||
vim.health.start('The following errors have been detected:')
|
||||
for _, p in ipairs(error_collection) do
|
||||
local lang, type, err = unpack(p)
|
||||
local lang, type, err = p[1], p[2], p[3]
|
||||
local lines = {}
|
||||
table.insert(lines, lang .. '(' .. type .. '): ' .. err)
|
||||
local files = tsq.get_files(lang, type)
|
||||
if #files > 0 then
|
||||
table.insert(lines, lang .. '(' .. type .. ') is concatenated from the following files:')
|
||||
for _, file in ipairs(files) do
|
||||
local fd = io.open(file, 'r')
|
||||
if fd then
|
||||
local ok, file_err = pcall(tsq.parse, lang, fd:read('*a'))
|
||||
if ok then
|
||||
table.insert(lines, '| [OK]:"' .. file .. '"')
|
||||
else
|
||||
table.insert(lines, '| [ERR]:"' .. file .. '", failed to load: ' .. file_err)
|
||||
end
|
||||
fd:close()
|
||||
local query = util.read_file(file)
|
||||
local ok, file_err = pcall(tsq.parse, lang, query)
|
||||
if ok then
|
||||
table.insert(lines, '| [OK]:"' .. file .. '"')
|
||||
else
|
||||
table.insert(lines, '| [ERR]:"' .. file .. '", failed to load: ' .. file_err)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
122
lua/nvim-treesitter/job.lua
Normal file
122
lua/nvim-treesitter/job.lua
Normal file
@@ -0,0 +1,122 @@
|
||||
-- Interface with Neovim job control and provide a simple job sequencing structure
|
||||
local uv = vim.loop
|
||||
local a = require('nvim-treesitter.async')
|
||||
local log = require('nvim-treesitter.log')
|
||||
|
||||
local M = { JobResult = {}, Opts = {} }
|
||||
|
||||
--- @class JobResult
|
||||
--- @field exit_code integer
|
||||
--- @field signal integer | string
|
||||
--- @field stdout string[]
|
||||
--- @field stderr string[]
|
||||
|
||||
--- @class JobOpts
|
||||
--- @field cwd string
|
||||
--- @field timeout integer
|
||||
--- @field env string[]
|
||||
--- @field on_stderr fun(_: string)
|
||||
--- @field on_stdout fun(_: string)
|
||||
|
||||
--- Wrapper for vim.loop.spawn. Takes a command, options, and callback just like
|
||||
--- vim.loop.spawn, but ensures that all output from the command has been
|
||||
--- flushed before calling the callback.
|
||||
--- @param cmd string
|
||||
--- @param options uv.aliases.spawn_options
|
||||
--- @param callback fun(exit_code: integer, signal: integer|string)
|
||||
local function spawn(cmd, options, callback)
|
||||
local handle --- @type uv_process_t?
|
||||
local timer --- @type uv_timer_t
|
||||
log.trace('running job: (cwd=%s) %s %s', options.cwd, cmd, table.concat(options.args, ' '))
|
||||
handle = uv.spawn(cmd, options, function(exit_code, signal)
|
||||
---@cast handle -nil
|
||||
|
||||
handle:close()
|
||||
if timer then
|
||||
timer:stop()
|
||||
timer:close()
|
||||
end
|
||||
|
||||
callback(exit_code, signal)
|
||||
end)
|
||||
|
||||
--- @type integer?
|
||||
--- @diagnostic disable-next-line:undefined-field
|
||||
local timeout = options.timeout
|
||||
|
||||
if timeout then
|
||||
timer = assert(uv.new_timer())
|
||||
timer:start(timeout, 0, function()
|
||||
timer:stop()
|
||||
timer:close()
|
||||
if handle and handle:is_active() then
|
||||
log.warn('Killing %s due to timeout!', cmd)
|
||||
handle:kill('sigint')
|
||||
handle:close()
|
||||
for _, pipe in
|
||||
pairs(options.stdio --[[@as uv_pipe_t[] ]])
|
||||
do
|
||||
pipe:close()
|
||||
end
|
||||
callback(-9999, 'sigint')
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
--- Main exposed function for the jobs module. Takes a task and options and
|
||||
--- returns an async function that will run the task with the given opts via
|
||||
--- vim.loop.spawn
|
||||
--- @param task string[]
|
||||
--- @param opts JobOpts
|
||||
--- @param callback fun(_: JobResult)
|
||||
--- @type fun(task: string|string[], opts: JobOpts): JobResult
|
||||
M.run = a.wrap(function(task, opts, callback)
|
||||
local stdout_data = {}
|
||||
local stderr_data = {}
|
||||
|
||||
local stdout = assert(uv.new_pipe(false))
|
||||
local stderr = assert(uv.new_pipe(false))
|
||||
|
||||
spawn(task[1], {
|
||||
args = { unpack(task, 2) },
|
||||
stdio = { nil, stdout, stderr },
|
||||
cwd = opts.cwd,
|
||||
timeout = opts.timeout and 1000 * opts.timeout or nil,
|
||||
env = opts.env,
|
||||
hide = true,
|
||||
}, function(exit_code, signal)
|
||||
callback({
|
||||
exit_code = exit_code,
|
||||
signal = signal,
|
||||
stdout = stdout_data,
|
||||
stderr = stderr_data,
|
||||
})
|
||||
end)
|
||||
|
||||
for kind, pipe in pairs({ stdout = stdout, stderr = stderr }) do
|
||||
pipe:read_start(function(err, data)
|
||||
if kind == 'stderr' and opts.on_stderr and data then
|
||||
opts.on_stderr(data)
|
||||
end
|
||||
if kind == 'stdout' and opts.on_stdout and data then
|
||||
opts.on_stdout(data)
|
||||
end
|
||||
if data then
|
||||
log.trace('%s -> %s', kind, data)
|
||||
end
|
||||
if err then
|
||||
log.error(err)
|
||||
end
|
||||
if data ~= nil then
|
||||
local output = kind == 'stdout' and stdout_data or stderr_data
|
||||
table.insert(output, vim.trim(data))
|
||||
else
|
||||
pipe:read_stop()
|
||||
pipe:close()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end, 3)
|
||||
|
||||
return M
|
||||
133
lua/nvim-treesitter/log.lua
Normal file
133
lua/nvim-treesitter/log.lua
Normal file
@@ -0,0 +1,133 @@
|
||||
local api = vim.api
|
||||
|
||||
-- TODO(lewis6991): write these out to a file
|
||||
local messages = {} --- @type {[1]: string, [2]: string?, [3]: string}[]
|
||||
|
||||
local sev_to_hl = {
|
||||
trace = 'DiagnosticHint',
|
||||
debug = 'Normal',
|
||||
info = 'MoreMsg',
|
||||
warn = 'WarningMsg',
|
||||
error = 'ErrorMsg',
|
||||
}
|
||||
|
||||
---@param ctx string?
|
||||
---@param m string
|
||||
---@param ... any
|
||||
local function trace(ctx, m, ...)
|
||||
messages[#messages + 1] = { 'trace', ctx, string.format(m, ...) }
|
||||
end
|
||||
|
||||
---@param ctx string?
|
||||
---@param m string
|
||||
---@param ... any
|
||||
local function debug(ctx, m, ...)
|
||||
messages[#messages + 1] = { 'debug', ctx, string.format(m, ...) }
|
||||
end
|
||||
|
||||
---@param ctx string?
|
||||
---@return string
|
||||
local function mkpfx(ctx)
|
||||
return ctx and string.format('[nvim-treesitter/%s]', ctx) or '[nvim-treesitter]'
|
||||
end
|
||||
|
||||
---@param ctx string?
|
||||
---@param m string
|
||||
---@param ... any
|
||||
local function info(ctx, m, ...)
|
||||
local m1 = string.format(m, ...)
|
||||
messages[#messages + 1] = { 'info', ctx, m1 }
|
||||
api.nvim_echo({ { mkpfx(ctx) .. ': ' .. m1, sev_to_hl.info } }, true, {})
|
||||
end
|
||||
|
||||
---@param ctx string?
|
||||
---@param m string
|
||||
---@param ... any
|
||||
local function warn(ctx, m, ...)
|
||||
local m1 = string.format(m, ...)
|
||||
messages[#messages + 1] = { 'warn', ctx, m1 }
|
||||
api.nvim_echo({ { mkpfx(ctx) .. ' warning: ' .. m1, sev_to_hl.warn } }, true, {})
|
||||
end
|
||||
|
||||
---@param ctx string?
|
||||
---@param m string
|
||||
---@param ... any
|
||||
local function lerror(ctx, m, ...)
|
||||
local m1 = string.format(m, ...)
|
||||
messages[#messages + 1] = { 'error', ctx, m1 }
|
||||
error(mkpfx(ctx) .. ' error: ' .. m1)
|
||||
end
|
||||
|
||||
--- @class NTSLogModule
|
||||
--- @field trace fun(fmt: string, ...: any)
|
||||
--- @field debug fun(fmt: string, ...: any)
|
||||
--- @field info fun(fmt: string, ...: any)
|
||||
--- @field warn fun(fmt: string, ...: any)
|
||||
--- @field error fun(fmt: string, ...: any)
|
||||
local M = {}
|
||||
|
||||
--- @class Logger
|
||||
--- @field ctx? string
|
||||
local Logger = {}
|
||||
|
||||
M.Logger = Logger
|
||||
|
||||
--- @param ctx? string
|
||||
--- @return Logger
|
||||
function M.new(ctx)
|
||||
return setmetatable({ ctx = ctx }, { __index = Logger })
|
||||
end
|
||||
|
||||
---@param m string
|
||||
---@param ... any
|
||||
function Logger:trace(m, ...)
|
||||
trace(self.ctx, m, ...)
|
||||
end
|
||||
|
||||
---@param m string
|
||||
---@param ... any
|
||||
function Logger:debug(m, ...)
|
||||
debug(self.ctx, m, ...)
|
||||
end
|
||||
|
||||
---@param m string
|
||||
---@param ... any
|
||||
function Logger:info(m, ...)
|
||||
info(self.ctx, m, ...)
|
||||
end
|
||||
|
||||
---@param m string
|
||||
---@param ... any
|
||||
function Logger:warn(m, ...)
|
||||
warn(self.ctx, m, ...)
|
||||
end
|
||||
|
||||
---@param m string
|
||||
---@param ... any
|
||||
function Logger:error(m, ...)
|
||||
lerror(self.ctx, m, ...)
|
||||
end
|
||||
|
||||
local noctx_logger = M.new()
|
||||
|
||||
setmetatable(M, {
|
||||
__index = function(t, k)
|
||||
--- @diagnostic disable-next-line:no-unknown
|
||||
t[k] = function(...)
|
||||
return noctx_logger[k](noctx_logger, ...)
|
||||
end
|
||||
return t[k]
|
||||
end,
|
||||
})
|
||||
|
||||
function M.show()
|
||||
for _, l in ipairs(messages) do
|
||||
local sev, ctx, msg = l[1], l[2], l[3]
|
||||
local hl = sev_to_hl[sev]
|
||||
local text = ctx and string.format('%s(%s): %s', sev, ctx, msg)
|
||||
or string.format('%s: %s', sev, msg)
|
||||
api.nvim_echo({ { text, hl } }, false, {})
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -2818,17 +2818,18 @@ M.configs = {
|
||||
---@param tier integer? only get parsers of specified tier
|
||||
---@return string[]
|
||||
function M.get_available(tier)
|
||||
--- @type string[]
|
||||
local parsers = vim.tbl_keys(M.configs)
|
||||
table.sort(parsers)
|
||||
if tier then
|
||||
parsers = vim.iter.filter(function(p)
|
||||
return M.configs[p].tier == tier
|
||||
end, parsers)
|
||||
end, parsers) --[[@as string[] ]]
|
||||
end
|
||||
if vim.fn.executable('tree-sitter') == 0 or vim.fn.executable('node') == 0 then
|
||||
parsers = vim.iter.filter(function(p)
|
||||
return not M.configs[p].install_info.requires_generate_from_grammar
|
||||
end, parsers)
|
||||
end, parsers) --[[@as string[] ]]
|
||||
end
|
||||
return parsers
|
||||
end
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
local uv = vim.loop
|
||||
|
||||
local iswin = uv.os_uname().sysname == 'Windows_NT'
|
||||
|
||||
local M = {}
|
||||
|
||||
---@param executables string[]
|
||||
---@return string|nil
|
||||
function M.select_executable(executables)
|
||||
return vim.tbl_filter(function(c) ---@param c string
|
||||
return c ~= vim.NIL and vim.fn.executable(c) == 1
|
||||
end, executables)[1]
|
||||
end
|
||||
|
||||
-- Returns the compiler arguments based on the compiler and OS
|
||||
---@param repo InstallInfo
|
||||
---@param compiler string
|
||||
---@return string[]
|
||||
function M.select_compiler_args(repo, compiler)
|
||||
if compiler:find('cl$') or compiler:find('cl.exe$') then
|
||||
return {
|
||||
'/Fe:',
|
||||
'parser.so',
|
||||
'/Isrc',
|
||||
repo.files,
|
||||
'-Os',
|
||||
'/utf-8',
|
||||
'/LD',
|
||||
}
|
||||
elseif compiler:find('zig$') or compiler:find('zig.exe$') then
|
||||
return {
|
||||
'c++',
|
||||
'-o',
|
||||
'parser.so',
|
||||
repo.files,
|
||||
'-lc',
|
||||
'-Isrc',
|
||||
'-shared',
|
||||
'-Os',
|
||||
}
|
||||
else
|
||||
local args = {
|
||||
'-o',
|
||||
'parser.so',
|
||||
'-I./src',
|
||||
repo.files,
|
||||
'-Os',
|
||||
}
|
||||
if uv.os_uname().sysname == 'Darwin' then
|
||||
table.insert(args, '-bundle')
|
||||
else
|
||||
table.insert(args, '-shared')
|
||||
end
|
||||
if
|
||||
#vim.tbl_filter(function(file) ---@param file string
|
||||
local ext = vim.fn.fnamemodify(file, ':e')
|
||||
return ext == 'cc' or ext == 'cpp' or ext == 'cxx'
|
||||
end, repo.files) > 0
|
||||
then
|
||||
table.insert(args, '-lstdc++')
|
||||
end
|
||||
if not iswin then
|
||||
table.insert(args, '-fPIC')
|
||||
end
|
||||
return args
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns the compile command based on the OS and user options
|
||||
---@param repo InstallInfo
|
||||
---@param cc string
|
||||
---@param compile_location string
|
||||
---@return Command
|
||||
function M.select_compile_command(repo, cc, compile_location)
|
||||
local make = M.select_executable({ 'gmake', 'make' })
|
||||
if cc:find('cl$') or cc:find('cl.exe$') or not repo.use_makefile or iswin or not make then
|
||||
return {
|
||||
cmd = cc,
|
||||
info = 'Compiling...',
|
||||
err = 'Error during compilation',
|
||||
opts = {
|
||||
args = vim.tbl_flatten(M.select_compiler_args(repo, cc)),
|
||||
cwd = compile_location,
|
||||
},
|
||||
}
|
||||
else
|
||||
return {
|
||||
cmd = make,
|
||||
info = 'Compiling...',
|
||||
err = 'Error during compilation',
|
||||
opts = {
|
||||
args = {
|
||||
'--makefile=' .. M.get_package_path('scripts', 'compile_parsers.makefile'),
|
||||
'CC=' .. cc,
|
||||
},
|
||||
cwd = compile_location,
|
||||
},
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
---@param repo InstallInfo
|
||||
---@param project_name string
|
||||
---@param cache_dir string
|
||||
---@param revision string|nil
|
||||
---@param prefer_git boolean
|
||||
---@return table
|
||||
function M.select_download_commands(repo, project_name, cache_dir, revision, prefer_git)
|
||||
local can_use_tar = vim.fn.executable('tar') == 1 and vim.fn.executable('curl') == 1
|
||||
local is_github = repo.url:find('github.com', 1, true)
|
||||
local is_gitlab = repo.url:find('gitlab.com', 1, true)
|
||||
local project_dir = vim.fs.joinpath(cache_dir, project_name)
|
||||
|
||||
revision = revision or repo.branch or 'master'
|
||||
|
||||
if can_use_tar and (is_github or is_gitlab) and not prefer_git then
|
||||
local url = repo.url:gsub('.git$', '')
|
||||
|
||||
local dir_rev = revision
|
||||
if is_github and revision:find('^v%d') then
|
||||
dir_rev = revision:sub(2)
|
||||
end
|
||||
|
||||
local temp_dir = project_dir .. '-tmp'
|
||||
|
||||
return {
|
||||
{
|
||||
cmd = function()
|
||||
vim.fn.delete(temp_dir, 'rf')
|
||||
end,
|
||||
},
|
||||
{
|
||||
cmd = 'curl',
|
||||
info = 'Downloading ' .. project_name .. '...',
|
||||
err = 'Error during download, please verify your internet connection',
|
||||
opts = {
|
||||
args = {
|
||||
'--silent',
|
||||
'-L', -- follow redirects
|
||||
is_github and url .. '/archive/' .. revision .. '.tar.gz'
|
||||
or url
|
||||
.. '/-/archive/'
|
||||
.. revision
|
||||
.. '/'
|
||||
.. project_name
|
||||
.. '-'
|
||||
.. revision
|
||||
.. '.tar.gz',
|
||||
'--output',
|
||||
project_name .. '.tar.gz',
|
||||
},
|
||||
cwd = cache_dir,
|
||||
},
|
||||
},
|
||||
{
|
||||
cmd = function()
|
||||
--TODO(clason): use vim.fn.mkdir(temp_dir, 'p') in case stdpath('cache') is not created
|
||||
uv.fs_mkdir(temp_dir, 493)
|
||||
end,
|
||||
info = 'Creating temporary directory',
|
||||
err = 'Could not create ' .. project_name .. '-tmp',
|
||||
},
|
||||
{
|
||||
cmd = 'tar',
|
||||
info = 'Extracting ' .. project_name .. '...',
|
||||
err = 'Error during tarball extraction.',
|
||||
opts = {
|
||||
args = {
|
||||
'-xvzf',
|
||||
project_name .. '.tar.gz',
|
||||
'-C',
|
||||
project_name .. '-tmp',
|
||||
},
|
||||
cwd = cache_dir,
|
||||
},
|
||||
},
|
||||
{
|
||||
cmd = function()
|
||||
uv.fs_unlink(project_dir .. '.tar.gz')
|
||||
end,
|
||||
},
|
||||
{
|
||||
cmd = function()
|
||||
uv.fs_rename(
|
||||
vim.fs.joinpath(temp_dir, url:match('[^/]-$') .. '-' .. dir_rev),
|
||||
project_dir
|
||||
)
|
||||
end,
|
||||
},
|
||||
{
|
||||
cmd = function()
|
||||
vim.fn.delete(temp_dir, 'rf')
|
||||
end,
|
||||
},
|
||||
}
|
||||
else
|
||||
local git_dir = project_dir
|
||||
local clone_error = 'Error during download, please verify your internet connection'
|
||||
|
||||
return {
|
||||
{
|
||||
cmd = 'git',
|
||||
info = 'Downloading ' .. project_name .. '...',
|
||||
err = clone_error,
|
||||
opts = {
|
||||
args = {
|
||||
'clone',
|
||||
repo.url,
|
||||
project_name,
|
||||
},
|
||||
cwd = cache_dir,
|
||||
},
|
||||
},
|
||||
{
|
||||
cmd = 'git',
|
||||
info = 'Checking out locked revision',
|
||||
err = 'Error while checking out revision',
|
||||
opts = {
|
||||
args = {
|
||||
'checkout',
|
||||
revision,
|
||||
},
|
||||
cwd = git_dir,
|
||||
},
|
||||
},
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function M.get_package_path(...)
|
||||
return vim.fs.joinpath(vim.fn.fnamemodify(debug.getinfo(1, 'S').source:sub(2), ':p:h:h:h'), ...)
|
||||
end
|
||||
|
||||
--TODO(clason): only needed for iter_cmd_sync -> replace with uv.spawn?
|
||||
|
||||
-- Convert path for cmd.exe on Windows (needed when shellslash is set)
|
||||
---@param p string
|
||||
---@return string
|
||||
local function cmdpath(p)
|
||||
return vim.o.shellslash and p:gsub('/', '\\') or p
|
||||
end
|
||||
|
||||
---@param dir string
|
||||
---@param command string
|
||||
---@return string command
|
||||
function M.make_directory_change_for_command(dir, command)
|
||||
if iswin then
|
||||
if string.find(vim.o.shell, 'cmd') ~= nil then
|
||||
return string.format('pushd %s & %s & popd', cmdpath(dir), command)
|
||||
else
|
||||
return string.format('pushd %s ; %s ; popd', cmdpath(dir), command)
|
||||
end
|
||||
else
|
||||
return string.format('cd %s;\n %s', dir, command)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
28
lua/nvim-treesitter/util.lua
Normal file
28
lua/nvim-treesitter/util.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
local uv = vim.loop
|
||||
|
||||
local M = {}
|
||||
|
||||
--- @param filename string
|
||||
--- @return string
|
||||
function M.read_file(filename)
|
||||
local file = assert(io.open(filename, 'r'))
|
||||
local r = file:read('*a')
|
||||
file:close()
|
||||
return r
|
||||
end
|
||||
|
||||
--- @param filename string
|
||||
--- @param content string
|
||||
function M.write_file(filename, content)
|
||||
local file = assert(io.open(filename, 'w'))
|
||||
file:write(content)
|
||||
file:close()
|
||||
end
|
||||
|
||||
--- Recursively delete a directory
|
||||
--- @param name string
|
||||
function M.delete(name)
|
||||
vim.fs.rm(name, { recursive = true, force = true })
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -45,19 +45,6 @@ end, {
|
||||
desc = 'Install treesitter parsers from grammar',
|
||||
})
|
||||
|
||||
api.nvim_create_user_command('TSInstallSync', function(args)
|
||||
require('nvim-treesitter.install').install(args.fargs, {
|
||||
with_sync = true,
|
||||
force = args.bang,
|
||||
})
|
||||
end, {
|
||||
nargs = '+',
|
||||
bang = true,
|
||||
bar = true,
|
||||
complete = complete_available_parsers,
|
||||
desc = 'Install treesitter parsers synchronously',
|
||||
})
|
||||
|
||||
api.nvim_create_user_command('TSUpdate', function(args)
|
||||
require('nvim-treesitter.install').update(args.fargs)
|
||||
end, {
|
||||
@@ -67,15 +54,6 @@ end, {
|
||||
desc = 'Update installed treesitter parsers',
|
||||
})
|
||||
|
||||
api.nvim_create_user_command('TSUpdateSync', function(args)
|
||||
require('nvim-treesitter.install').update(args.fargs, { with_sync = true })
|
||||
end, {
|
||||
nargs = '*',
|
||||
bar = true,
|
||||
complete = complete_installed_parsers,
|
||||
desc = 'Update installed treesitter parsers synchronously',
|
||||
})
|
||||
|
||||
api.nvim_create_user_command('TSUninstall', function(args)
|
||||
require('nvim-treesitter.install').uninstall(args.fargs)
|
||||
end, {
|
||||
@@ -84,3 +62,9 @@ end, {
|
||||
complete = complete_installed_parsers,
|
||||
desc = 'Uninstall treesitter parsers',
|
||||
})
|
||||
|
||||
api.nvim_create_user_command('TSLog', function()
|
||||
require('nvim-treesitter.log').show()
|
||||
end, {
|
||||
desc = 'View log messages',
|
||||
})
|
||||
|
||||
@@ -65,9 +65,8 @@ for _, v in ipairs(sorted_parsers) do
|
||||
end
|
||||
generated_text = generated_text .. footnotes
|
||||
|
||||
local readme = assert(io.open('SUPPORTED_LANGUAGES.md', 'r'))
|
||||
local readme_text = readme:read('*a')
|
||||
readme:close()
|
||||
local readme = 'SUPPORTED_LANGUAGES.md'
|
||||
local readme_text = require('nvim-treesitter.util').read_file(readme)
|
||||
|
||||
local new_readme_text = string.gsub(
|
||||
readme_text,
|
||||
@@ -75,12 +74,10 @@ local new_readme_text = string.gsub(
|
||||
'<!--parserinfo-->\n' .. generated_text .. '<!--parserinfo-->'
|
||||
)
|
||||
|
||||
readme = assert(io.open('SUPPORTED_LANGUAGES.md', 'w'))
|
||||
readme:write(new_readme_text)
|
||||
readme:close()
|
||||
require('nvim-treesitter.util').write_file(readme, new_readme_text)
|
||||
|
||||
if string.find(readme_text, generated_text, 1, true) then
|
||||
print('README.md is up-to-date\n')
|
||||
print(readme .. ' is up-to-date\n')
|
||||
else
|
||||
print('New README.md was written\n')
|
||||
print('New ' .. readme .. ' was written\n')
|
||||
end
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
vim.opt.runtimepath:append('.')
|
||||
|
||||
-- Load previous lockfile
|
||||
local filename = require('nvim-treesitter.shell_cmds').get_package_path('lockfile.json')
|
||||
local file = assert(io.open(filename, 'r'))
|
||||
local lockfile = vim.json.decode(file:read('*a'))
|
||||
file:close()
|
||||
local filename = require('nvim-treesitter.install').get_package_path('lockfile.json')
|
||||
local lockfile = vim.json.decode(require('nvim-treesitter.util').read_file(filename))
|
||||
|
||||
---@type string?
|
||||
local skip_lang_string = os.getenv('SKIP_LOCKFILE_UPDATE_FOR_LANGS')
|
||||
@@ -47,6 +45,4 @@ end
|
||||
vim.print(lockfile)
|
||||
|
||||
-- write new lockfile
|
||||
file = assert(io.open(filename, 'w'))
|
||||
file:write(vim.json.encode(lockfile))
|
||||
file:close()
|
||||
require('nvim-treesitter.util').write_file(filename, vim.json.encode(lockfile))
|
||||
|
||||
Reference in New Issue
Block a user