From 7a22f74cd66937824671ca74df27e5bff15b49ad Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Sun, 11 May 2025 20:53:44 +0200 Subject: [PATCH] fix(treesitter): Allow tree-sitter grammar installation outside of orgmode folder --- lua/orgmode/health.lua | 27 ++--- lua/orgmode/utils/treesitter/install.lua | 134 ++++++++++++++--------- 2 files changed, 96 insertions(+), 65 deletions(-) diff --git a/lua/orgmode/health.lua b/lua/orgmode/health.lua index 066abf6f3..321032068 100644 --- a/lua/orgmode/health.lua +++ b/lua/orgmode/health.lua @@ -16,17 +16,25 @@ function M.check_has_treesitter() return h.error('Treesitter grammar is not installed. Run `:Org install_treesitter_grammar` to install it.') end - if not version_info.install_location then + if #version_info.parser_locations > 1 then local list = vim.tbl_map(function(parser) return ('- `%s`'):format(parser) - end, version_info.conflicting_parsers) - return h.error( - ('Installed org parser found in incorrect location. Run `:Org install_treesitter_grammar` to install it in correct location and remove the conflicting parsers from these locations:\n%s'):format( + end, version_info.parser_locations) + return h.warn( + ('Multiple org parsers found in these locations:\n%s\nDelete unused ones to avoid conflicts.'):format( table.concat(list, '\n') ) ) end + if not version_info.installed_in_orgmode_dir then + return h.ok( + ('Tree-sitter grammar is installed, but not by nvim-orgmode plugin. Any issues or version mismatch will need to be handled manually.\nIf you want nvim-orgmode to manage the parser installation (recommended), remove the installed parser at "%s" and restart Neovim.'):format( + version_info.parser_locations[1] + ) + ) + end + if version_info.outdated then return h.error('Treesitter grammar is out of date. Run `:Org install_treesitter_grammar` to update it.') end @@ -40,17 +48,6 @@ function M.check_has_treesitter() ) end - if #version_info.conflicting_parsers > 0 then - local list = vim.tbl_map(function(parser) - return ('- `%s`'):format(parser) - end, version_info.conflicting_parsers) - return h.warn( - ('Conflicting org parser(s) found in these locations:\n%s\nRemove them to avoid conflicts.'):format( - table.concat(list, '\n') - ) - ) - end - return h.ok(('Treesitter grammar installed (version %s)'):format(version_info.installed_version)) end diff --git a/lua/orgmode/utils/treesitter/install.lua b/lua/orgmode/utils/treesitter/install.lua index 7f479cf61..7cda8d2a9 100644 --- a/lua/orgmode/utils/treesitter/install.lua +++ b/lua/orgmode/utils/treesitter/install.lua @@ -9,15 +9,17 @@ local required_version = '2.0.0' function M.install() local version_info = M.get_version_info() + if not version_info.installed then return M.run('install') end - -- Parser found but in invalid location - if not version_info.install_location then - local result = M.run('install') - M.notify_conflicting_parsers(version_info.conflicting_parsers) - return result + if #version_info.parser_locations > 1 then + M.notify_conflicting_parsers(version_info.parser_locations) + end + + if not version_info.installed_in_orgmode_dir then + return false end if version_info.outdated then @@ -28,26 +30,22 @@ function M.install() return M.reinstall() end - M.notify_conflicting_parsers(version_info.conflicting_parsers) - return false end function M.notify_conflicting_parsers(conflicting_parsers) - if #conflicting_parsers > 0 then - local list = vim.tbl_map(function(parser) - return ('- `%s`'):format(parser) - end, conflicting_parsers) - utils.notify( - ('Conflicting org parser(s) found in these locations:\n%s\nRemove them to avoid conflicts.'):format( - table.concat(list, '\n') - ), - { - level = 'warn', - timeout = 5000, - } - ) - end + local list = vim.tbl_map(function(parser) + return ('- `%s`'):format(parser) + end, conflicting_parsers) + utils.notify( + ('Multiple org parsers found in these locations:\n%s\nDelete unused ones to avoid conflicts.'):format( + table.concat(list, '\n') + ), + { + level = 'warn', + timeout = 5000, + } + ) end function M.reinstall() @@ -57,13 +55,12 @@ end function M.get_version_info() local result = { installed = false, - correct_location = false, - install_location = nil, installed_version = nil, outdated = false, required_version = required_version, version_mismatch = false, - conflicting_parsers = {}, + parser_locations = {}, + installed_in_orgmode_dir = false, } if M.not_installed() then @@ -73,13 +70,8 @@ function M.get_version_info() result.installed = true local parser_locations = M.get_parser_locations() - result.conflicting_parsers = parser_locations.conflicting_parsers - - if not parser_locations.install_location then - return result - end - - result.install_location = parser_locations.install_location + result.parser_locations = parser_locations.parser_locations + result.installed_in_orgmode_dir = parser_locations.installed_in_orgmode_dir local installed_version = M.get_installed_version() result.installed_version = installed_version @@ -90,23 +82,26 @@ function M.get_version_info() end function M.get_parser_locations() - local installed_org_parsers = vim.tbl_map(function(item) - return vim.fn.fnamemodify(item, ':p') - end, vim.api.nvim_get_runtime_file('parser/org.so', true)) - local parser_path = M.get_parser_path() - local install_location = nil - local conflicting_parsers = {} - for _, parser in ipairs(installed_org_parsers) do - if vim.fs.normalize(parser) == vim.fs.normalize(parser_path) then - install_location = parser - else - table.insert(conflicting_parsers, parser) + local runtime_files = vim.api.nvim_get_runtime_file('parser/org.so', true) + local parser_locations = {} + local valid_paths = {} + for _, runtime_file in ipairs(runtime_files) do + local path = vim.fn.fnamemodify(runtime_file, ':p') + if not valid_paths[path] then + valid_paths[path] = path + table.insert(parser_locations, path) end end + local installed_in_orgmode_dir = false + + if #parser_locations == 1 and vim.fs.normalize(parser_locations[1]) == vim.fs.normalize(M.get_parser_path()) then + installed_in_orgmode_dir = true + end + return { - install_location = install_location, - conflicting_parsers = conflicting_parsers, + parser_locations = parser_locations, + installed_in_orgmode_dir = installed_in_orgmode_dir, } end @@ -198,6 +193,41 @@ function M.exe(cmd, opts) end) end +-- Returns the move command based on the OS +---@param from string +---@param to string +---@param cwd string +---@param is_win boolean +---@param shellslash boolean +function M.select_mv_cmd(from, to, cwd, is_win, shellslash) + if is_win then + local function cmdpath(p) + if shellslash then + local r = p:gsub('/', '\\') + return r + end + return p + end + + return { + cmd = 'cmd', + opts = { + args = { '/C', 'move', '/Y', cmdpath(from), cmdpath(to) }, + cwd = cwd, + }, + } + end + + return { + cmd = 'mv', + opts = { + args = { '-f', from, to }, + cwd = cwd, + }, + } +end + +-- Get path to the directory that holds the tree-sitter grammar. function M.get_path(url, type) local local_path = vim.fn.expand(url) local is_local_path = vim.fn.isdirectory(local_path) == 1 @@ -240,12 +270,14 @@ function M.run(type) end local compiler_args = M.select_compiler_args(compiler) - local path = nil + local ts_grammar_dir = nil local lock_file = M.get_lock_file() + local is_win = vim.fn.has('win32') == 1 + local shellslash = is_win and vim.opt.shellslash:get() or false return M.get_path(url, type) :next(function(directory) - path = directory + ts_grammar_dir = directory return M.exe(compiler, { args = compiler_args, cwd = directory, @@ -255,10 +287,12 @@ function M.run(type) if code ~= 0 then error('[orgmode] Failed to compile parser', 0) end - local source = vim.fs.joinpath(path, 'parser.so') - local copied, err = vim.uv.fs_copyfile(source, M.get_parser_path()) - if not copied then - error('[orgmode] Failed to copy generated tree-sitter parser to runtime folder: ' .. err, 0) + local move_cmd = M.select_mv_cmd('parser.so', M.get_parser_path(), ts_grammar_dir or '', is_win, shellslash) + return M.exe(move_cmd.cmd, move_cmd.opts) + end) + :next(function(code) + if code ~= 0 then + error('[orgmode] Failed to move generated tree-sitter parser to runtime folder', 0) end return utils.writefile(lock_file, vim.json.encode({ version = required_version })) end)