如何在Neovim的.ts文件中实现模板字符串的HTML/JSX语法高亮?
我目前能实现的最接近效果的方式是使用html标签模板字符串(即使文件中未定义html,该高亮也能生效)。
但这种方式无法处理如下场景:
<div class="max-w-md ${width}">
export function renderLoginForm(): string { const width = 'w-8' const html = String.raw return html` <div class="max-w-md p-6 ${width}"> something </div> ` }
不使用html标签时,:TSPlayground显示效果如下:
使用html标签时,template_string展开效果如下:
不使用html标签时:
可以看到这种方式效果不错,但当在模板字符串中使用插值(如"max-w-md mx-auto card p-6 ${width}")时就会失效:
我需要一个类似自定义jsx标签的方案,它能像html标签一样工作,同时支持模板字符串中的JS插值。
我正在尝试在Neovim编辑器的.ts文件中,将模板字符串高亮为HTML/JSX格式。
我希望Neovim的Tree-sitter能对.ts文件中普通的`…`模板字符串内的HTML/JSX进行语法高亮。我使用Lazy.nvim管理nvim-treesitter,尽管已安装typescript、tsx和html解析器,且编写了注入查询,但所有内容仍显示为@string,无法应用我需要的语法高亮。
export function foo(): string { return ` <div class="foo"> <Button onClick={() => alert("hi")}>Click me</Button> </div> `; }
我希望<div>/<Button>标签、属性和JSX表达式能像JSX/TSX/HTML一样被高亮(对应@tag、@attribute等捕获组),而非作为普通字符串处理,这样我就能在.ts文件中高亮模板代码/htmx内容。
安装解析器
:TSInstall typescript tsx html :TSUpdate
别名JSX→TSX
vim.treesitter.language.register("tsx", "jsx")
Lua中的注入查询(Lazy.nvim配置中的lua/plugins/treesitter.lua):
require("nvim-treesitter.configs").setup({ ensure_installed = {"typescript","tsx","html",…}, highlight = { enable = true }, }) vim.treesitter.query.set("typescript", "injections", [[ (template_string) @injection.content (#offset! @injection.content 0 1 0 -1) (#set! injection.language "jsx") ]]) vim.treesitter.query.set("tsx", "injections", [[ … ]])
备用after/queries配置
在~/.config/nvim/after/queries/typescript/injections.scm和~/.config/nvim/after/queries/tsx/injections.scm中添加相同的捕获规则、#set! injection.language "jsx"和#offset!。
调试操作
:InspectTree确认template_string节点存在。:TSHighlightCapturesUnderCursor仍只显示@string。
- Neovim v0.10.4
- Lazy.nvim插件管理器
- nvim-treesitter v0.11+
- 无TS或JSX相关的冲突语法插件
- 我遗漏或配置错误了什么,导致Tree-sitter无法在Lazy.nvim环境下,将TSX语法注入到无标签的模板字符串中?
- 我是否需要在其他时机或位置加载查询?
- Lazy.nvim在
vim.treesitter.query.set或after/queries/…方面有什么特殊注意事项吗? - 我应该将注入语言指定为"tsx"而非"jsx"吗?
任何示例配置片段或最小复现方案都将非常感谢。
提前致谢!
lua/plugins/treesitter.lua
-- lua/plugins/treesitter.lua local parser_config = require("nvim-treesitter.parsers").get_parser_configs() parser_config.gotmpl = { install_info = { url = "~/.config/nvim/tree-sitter-parsers/gotmpl", -- Path to the cloned parser files = { "src/parser.c" }, branch = "main", generate_requires_npm = false, requires_generate_from_grammar = false, }, filetype = "gotmpl", } return { "nvim-treesitter/nvim-treesitter", build = ":TSUpdate", dependencies = { "nvim-treesitter/playground", -- Add this plugin }, config = function() local config = require("nvim-treesitter.configs") config.setup({ ensure_installed = { "gotmpl", -- Go template parser "html", -- For embedded HTML "yaml", -- For embedded YAML "lua", -- Lua for configuration "javascript", -- JavaScript if needed "typescript", }, auto_install = true, highlight = { enable = true, additional_vim_regex_highlighting = false, }, indent = { enable = true }, playground = { enable = true, -- Enable Treesitter playground updatetime = 25, -- Debounced time for highlighting nodes in the playground persist_queries = false, -- Whether to save queries across sessions }, }) end, }
init.lua
-- init.lua local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" if not vim.loop.fs_stat(lazypath) then vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", "--branch=stable", -- latest stable release lazypath, }) end vim.opt.rtp:prepend(lazypath) vim.g.python3_host_prog = "/Users/<username>/.pyenv/shims/python" local opts = {} require("vim-options") require("lazy").setup("plugins")
lua/vim-options.lua
-- lua/vim-options.lua vim.cmd("set expandtab") vim.cmd("set tabstop=2") vim.cmd("set softtabstop=2") vim.cmd("set shiftwidth=2") vim.g.mapleader = " " vim.keymap.set("i", "jj", "<ESC>") vim.keymap.set("i", "kk", "<ESC>") -- vim.opt_local.formatoptions:remove({ "r", "o" }) vim.api.nvim_create_autocmd("FileType", { pattern = "*", callback = function() vim.opt_local.formatoptions:remove({ "r", "o" }) end, }) -- Define the function globally _G.resize_pane = function(direction, amount) local winnr = vim.api.nvim_get_current_win() local win_width = vim.api.nvim_win_get_width(winnr) local vim_width = vim.api.nvim_get_option("columns") if direction == "left" or direction == "right" then local edge_distance = direction == "left" and vim.api.nvim_win_get_position(winnr)[2] or vim_width - (vim.api.nvim_win_get_position(winnr)[2] + win_width) local resize_command = edge_distance > win_width / 2 and "vertical resize +" .. amount or "vertical resize -" .. amount vim.cmd(resize_command) else vim.cmd("resize " .. (direction == "up" and "-" or "+") .. amount) end end -- Key mappings vim.api.nvim_set_keymap("n", "<A-Left>", ":lua _G.resize_pane('left', 8)<CR>", { noremap = true, silent = true }) vim.api.nvim_set_keymap("n", "<A-Right>", ":lua _G.resize_pane('right', 8)<CR>", { noremap = true, silent = true }) vim.api.nvim_set_keymap("n", "<A-Up>", ":lua _G.resize_pane('down', 4)<CR>", { noremap = true, silent = true }) vim.api.nvim_set_keymap("n", "<A-Down>", ":lua _G.resize_pane('up', 4)<CR>", { noremap = true, silent = true }) --[[ -- Navigate vim panes better vim.keymap.set('n', '<c-k>', ':wincmd k<CR>') vim.keymap.set('n', '<c-j>', ':wincmd j<CR>') vim.keymap.set('n', '<c-h>', ':wincmd h<CR>') vim.keymap.set('n', '<c-l>', ':wincmd l<CR>') vim.keymap.set('n', '<leader>h', ':nohlsearch<CR>') ]] vim.wo.number = true vim.filetype.add({ extension = { tmpl = "gotmpl", -- Treat all `.tmpl` files as Go templates }, pattern = { [".*/templates/.*%.tpl"] = "helm", -- Helm-specific templates [".*/templates/.*%.ya?ml"] = "helm", ["helmfile.*%.ya?ml"] = "helm", }, })
内容的提问来源于stack exchange,提问作者currenthandle

