You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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标签时TSPlayground效果
使用html标签时,template_string展开效果如下:
使用html标签时template_string展开效果
不使用html标签时:
不使用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相关的冲突语法插件
问题
  1. 我遗漏或配置错误了什么,导致Tree-sitter无法在Lazy.nvim环境下,将TSX语法注入到无标签的模板字符串中?
  2. 我是否需要在其他时机或位置加载查询?
  3. Lazy.nvim在vim.treesitter.query.set或after/queries/…方面有什么特殊注意事项吗?
  4. 我应该将注入语言指定为"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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 01:29:51