diff --git a/lua/md-render/display_utils.lua b/lua/md-render/display_utils.lua index ef0971f..191b955 100644 --- a/lua/md-render/display_utils.lua +++ b/lua/md-render/display_utils.lua @@ -62,6 +62,40 @@ function M.reset_osc8_cache() _osc8_supported = nil end +-- Common info-string aliases that don't match a treesitter parser name directly. +-- nvim-treesitter "main" branch and bare Neovim register no defaults, so without +-- this table fenced blocks tagged ```sh / ```js / ... silently lose highlighting. +-- Users can add more via vim.treesitter.language.register() — that path is tried +-- first via vim.treesitter.language.get_lang(). +local LANG_ALIASES = { + sh = "bash", + shell = "bash", + shellscript = "bash", + zsh = "bash", + js = "javascript", + jsx = "javascript", + ts = "typescript", + py = "python", + rb = "ruby", + rs = "rust", + yml = "yaml", + md = "markdown", + ps1 = "powershell", +} + +--- Resolve a fenced-block info string to a treesitter parser name. +---@param name string +---@return string +local function resolve_lang(name) + local lower = name:lower() + local registered = vim.treesitter.language.get_lang(lower) + if registered and registered ~= lower then return registered end + return LANG_ALIASES[lower] or lower +end + +M._resolve_lang = resolve_lang +M._LANG_ALIASES = LANG_ALIASES + --- Apply treesitter syntax highlighting to code blocks ---@param buf integer ---@param ns integer @@ -82,13 +116,15 @@ function M.apply_treesitter_highlights(buf, ns, content) end local code_text = table.concat(code_lines, "\n") - local ok, parser = pcall(vim.treesitter.get_string_parser, code_text, block.language) + local lang = resolve_lang(block.language) + + local ok, parser = pcall(vim.treesitter.get_string_parser, code_text, lang) if not ok or not parser then goto continue end local trees = parser:parse() if not trees or #trees == 0 then goto continue end - local query = vim.treesitter.query.get(block.language, "highlights") + local query = vim.treesitter.query.get(lang, "highlights") if not query then goto continue end for id, node in query:iter_captures(trees[1]:root(), code_text) do @@ -117,7 +153,7 @@ function M.apply_treesitter_highlights(buf, ns, content) pcall(vim.api.nvim_buf_set_extmark, buf, ns, buf_sr, buf_sc, { end_row = buf_er, end_col = buf_ec, - hl_group = "@" .. name .. "." .. block.language, + hl_group = "@" .. name .. "." .. lang, priority = 4200, }) ::skip_capture:: diff --git a/tests/display_utils_test.lua b/tests/display_utils_test.lua new file mode 100644 index 0000000..bb240fc --- /dev/null +++ b/tests/display_utils_test.lua @@ -0,0 +1,76 @@ +-- display_utils tests +-- Run: nvim --headless -u NONE --noplugin -l tests/display_utils_test.lua + +package.path = vim.fn.getcwd() .. "/lua/?.lua;" .. vim.fn.getcwd() .. "/lua/?/init.lua;" .. package.path + +local display_utils = require "md-render.display_utils" + +local pass_count = 0 +local fail_count = 0 + +local function assert_eq(actual, expected, msg) + if actual == expected then + pass_count = pass_count + 1 + else + fail_count = fail_count + 1 + print("FAIL: " .. msg) + print(" expected: " .. vim.inspect(expected)) + print(" actual: " .. vim.inspect(actual)) + end +end + +local function test(name, fn) + local ok, err = pcall(fn) + if not ok then + fail_count = fail_count + 1 + print("ERROR: " .. name .. ": " .. tostring(err)) + end +end + +-- ============================================================================ +-- resolve_lang: map fenced info-string to treesitter parser name +-- ============================================================================ + +test("resolve_lang maps sh-family aliases to bash", function() + assert_eq(display_utils._resolve_lang "sh", "bash", "sh -> bash") + assert_eq(display_utils._resolve_lang "zsh", "bash", "zsh -> bash") + assert_eq(display_utils._resolve_lang "shell", "bash", "shell -> bash") + assert_eq(display_utils._resolve_lang "shellscript", "bash", "shellscript -> bash") +end) + +test("resolve_lang maps common short forms", function() + assert_eq(display_utils._resolve_lang "js", "javascript", "js -> javascript") + assert_eq(display_utils._resolve_lang "jsx", "javascript", "jsx -> javascript") + assert_eq(display_utils._resolve_lang "ts", "typescript", "ts -> typescript") + assert_eq(display_utils._resolve_lang "py", "python", "py -> python") + assert_eq(display_utils._resolve_lang "rb", "ruby", "rb -> ruby") + assert_eq(display_utils._resolve_lang "rs", "rust", "rs -> rust") + assert_eq(display_utils._resolve_lang "yml", "yaml", "yml -> yaml") + assert_eq(display_utils._resolve_lang "md", "markdown", "md -> markdown") + assert_eq(display_utils._resolve_lang "ps1", "powershell", "ps1 -> powershell") +end) + +test("resolve_lang is case-insensitive", function() + assert_eq(display_utils._resolve_lang "SH", "bash", "SH -> bash") + assert_eq(display_utils._resolve_lang "Bash", "bash", "Bash -> bash (passthrough)") +end) + +test("resolve_lang passes through names with no alias", function() + assert_eq(display_utils._resolve_lang "bash", "bash", "bash stays bash") + assert_eq(display_utils._resolve_lang "lua", "lua", "lua stays lua") + assert_eq(display_utils._resolve_lang "go", "go", "go stays go") + assert_eq(display_utils._resolve_lang "unknown_xyz", "unknown_xyz", "unknown stays unknown") +end) + +test("resolve_lang honors vim.treesitter.language.register", function() + -- Simulate a user-registered alias and confirm it wins over the literal name. + vim.treesitter.language.register("markdown", "custom_md_lang") + assert_eq( + display_utils._resolve_lang "custom_md_lang", + "markdown", + "registered alias custom_md_lang -> markdown" + ) +end) + +print(string.format("display_utils_test: %d passed, %d failed", pass_count, fail_count)) +if fail_count > 0 then os.exit(1) end