diff --git a/lua/nvim-treesitter/config.lua b/lua/nvim-treesitter/config.lua index 8150ad8a8..0e957309c 100644 --- a/lua/nvim-treesitter/config.lua +++ b/lua/nvim-treesitter/config.lua @@ -4,10 +4,12 @@ M.tiers = { 'stable', 'unstable', 'unmaintained', 'unsupported' } ---@class TSConfig ---@field install_dir string +---@field prefer_git boolean ---@type TSConfig local config = { install_dir = vim.fs.joinpath(vim.fn.stdpath('data'), 'site'), + prefer_git = false, } ---Setup call for users to override configuration configurations. @@ -18,10 +20,20 @@ function M.setup(user_data) user_data.install_dir = vim.fs.normalize(user_data.install_dir) vim.opt.runtimepath:prepend(user_data.install_dir) end + if user_data.prefer_git then + local log = require('nvim-treesitter.log') + log.warn('nvim-treesitter will use git to install parsers, which is slower and less stable.') + end config = vim.tbl_deep_extend('force', config, user_data) end end +---Returns whether git should be used to obtain the parsers +---@return boolean prefer_git +function M.is_git_preferred() + return config.prefer_git +end + -- Returns the install path for parsers, parser info, and queries. -- If the specified directory does not exist, it is created. ---@param dir_name string diff --git a/lua/nvim-treesitter/install.lua b/lua/nvim-treesitter/install.lua index 94d21d46a..3a5355e2d 100644 --- a/lua/nvim-treesitter/install.lua +++ b/lua/nvim-treesitter/install.lua @@ -188,22 +188,26 @@ local function do_generate(logger, repo, compile_location) end end +---@param tmp_dir string +---@param url string +---@param revision string +---@return string extraction_path +local function get_extraction_path(tmp_dir, url, revision) + local dir_rev = revision:find('^v%d') and revision:sub(2) or revision + local repo_project_name = url:match('[^/]-$') + return fs.joinpath(tmp_dir, repo_project_name .. '-' .. dir_rev) +end + ---@async ---@param logger Logger ---@param url string ---@param project_name string ---@param cache_dir string ---@param revision string ----@param output_dir string +---@param tmp_dir string ---@return string? err -local function do_download(logger, url, project_name, cache_dir, revision, output_dir) +local function curl_download(logger, url, project_name, cache_dir, revision, tmp_dir) local is_gitlab = url:find('gitlab.com', 1, true) - - local tmp = output_dir .. '-tmp' - - rmpath(tmp) - a.schedule() - url = url:gsub('.git$', '') local target = is_gitlab and string.format('%s/-/archive/%s/%s-%s.tar.gz', url, revision, project_name, revision) @@ -229,8 +233,8 @@ local function do_download(logger, url, project_name, cache_dir, revision, outpu end do -- Create tmp dir - logger:debug('Creating temporary directory: %s', tmp) - local err = mkpath(tmp) + logger:debug('Creating temporary directory: %s', tmp_dir) + local err = mkpath(tmp_dir) a.schedule() if err then return logger:error('Could not create %s-tmp: %s', project_name, err) @@ -257,11 +261,119 @@ local function do_download(logger, url, project_name, cache_dir, revision, outpu return logger:error('Could not remove tarball: %s', err) end end +end + +---@async +---@param logger Logger +---@param url string +---@param project_name string +---@param revision string +---@param tmp_dir string +---@return string? err +local function git_download(logger, url, project_name, revision, tmp_dir) + -- Set our temp directory to match what curl + tar would have given us + tmp_dir = get_extraction_path(tmp_dir, url, revision) + + do -- Create tmp dir + logger:debug('Creating temporary directory: %s', tmp_dir) + local err = mkpath(tmp_dir) + a.schedule() + if err then + return logger:error('Could not create %s-tmp: %s', project_name, err) + end + end + + do -- prepare git repo + local r = system({ + 'git', + '-C', + tmp_dir, + 'init', + }) + if r.code > 0 then + return logger:error('Failed to init git repo: %s', r.stderr) + end + end + + do -- Add remote repo + local r = system({ + 'git', + '-C', + tmp_dir, + 'remote', + 'add', + 'origin', + url, + }) + if r.code > 0 then + return logger:error('Failed to set git remote: %s', r.stderr) + end + end + + do -- Fetch specific commit + logger:info('Cloning %s...', project_name) + local r = system({ + 'git', + '-C', + tmp_dir, + 'fetch', + 'origin', + revision, + }) + if r.code > 0 then + return logger:error('Failed to fetch git revision: %s', r.stderr) + end + end + + do -- Reset to that commit + local r = system({ + 'git', + '-C', + tmp_dir, + 'reset', + '--hard', + 'FETCH_HEAD', + }) + if r.code > 0 then + return logger:error('Failed to set git head: %s', r.stderr) + end + end +end + +---@async +---@param logger Logger +---@param url string +---@param project_name string +---@param cache_dir string +---@param revision string +---@param output_dir string +---@return string? err +local function do_download(logger, url, project_name, cache_dir, revision, output_dir) + local tmp = output_dir .. '-tmp' + + rmpath(tmp) + a.schedule() + + if config.is_git_preferred() then + do -- Download with git + local err = git_download(logger, url, project_name, revision, tmp) + a.schedule() + if err then + return err + end + end + else + do -- Download with curl + local err = curl_download(logger, url, project_name, cache_dir, revision, tmp) + a.schedule() + if err then + return err + end + end + end do -- Move tmp dir to output dir - local dir_rev = revision:find('^v%d') and revision:sub(2) or revision - local repo_project_name = url:match('[^/]-$') - local extracted = fs.joinpath(tmp, repo_project_name .. '-' .. dir_rev) + local extracted = get_extraction_path(tmp, url, revision) logger:debug('Moving %s to %s/...', extracted, output_dir) local err = uv_rename(extracted, output_dir) a.schedule() diff --git a/scripts/install-parsers.lua b/scripts/install-parsers.lua index 2d083f3c3..ba8c3edb4 100755 --- a/scripts/install-parsers.lua +++ b/scripts/install-parsers.lua @@ -17,10 +17,12 @@ for i = 1, #_G.arg do end vim.opt.runtimepath:append('.') +local ts = require('nvim-treesitter') +ts.setup({ prefer_git = true }) ---@type async.Task -local task = update and require('nvim-treesitter').update('all', { summary = true }) - or require('nvim-treesitter').install( +local task = update and ts.update('all', { summary = true }) + or ts.install( #parsers > 0 and parsers or 'all', { force = true, summary = true, generate = generate, max_jobs = max_jobs } )