mirror of
https://github.com/nvim-treesitter/nvim-treesitter.git
synced 2026-07-12 00:10:04 -04:00
feat(languagetree): implement language tree
Allow the LanguageTree to be used as an option for highlighting. Co-authored-by: Santos Gallegos <stsewd@protonmail.com> Co-authored-by: Yazdani Kiyan <yazdani.kiyan@protonmail.com>
This commit is contained in:
parent
e9b6e2d311
commit
f7d92f663c
5 changed files with 193 additions and 13 deletions
|
|
@ -104,7 +104,6 @@ All modules are disabled by default,
|
||||||
so you'll need to activate them by putting this in your `init.vim` file:
|
so you'll need to activate them by putting this in your `init.vim` file:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
lua <<EOF
|
|
||||||
require'nvim-treesitter.configs'.setup {
|
require'nvim-treesitter.configs'.setup {
|
||||||
ensure_installed = "all", -- one of "all", "language", or a list of languages
|
ensure_installed = "all", -- one of "all", "language", or a list of languages
|
||||||
highlight = {
|
highlight = {
|
||||||
|
|
@ -112,7 +111,6 @@ require'nvim-treesitter.configs'.setup {
|
||||||
disable = { "c", "rust" }, -- list of language that will be disabled
|
disable = { "c", "rust" }, -- list of language that will be disabled
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
EOF
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Check [`:h nvim-treesitter-modules`](doc/nvim-treesitter.txt)
|
Check [`:h nvim-treesitter-modules`](doc/nvim-treesitter.txt)
|
||||||
|
|
@ -125,7 +123,6 @@ for a list of available modules and its options.
|
||||||
Consistent syntax highlighting.
|
Consistent syntax highlighting.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
lua <<EOF
|
|
||||||
require'nvim-treesitter.configs'.setup {
|
require'nvim-treesitter.configs'.setup {
|
||||||
highlight = {
|
highlight = {
|
||||||
enable = true,
|
enable = true,
|
||||||
|
|
@ -135,7 +132,6 @@ require'nvim-treesitter.configs'.setup {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
EOF
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Incremental selection
|
## Incremental selection
|
||||||
|
|
@ -143,7 +139,6 @@ EOF
|
||||||
Incremental selection based on the named nodes from the grammar.
|
Incremental selection based on the named nodes from the grammar.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
lua <<EOF
|
|
||||||
require'nvim-treesitter.configs'.setup {
|
require'nvim-treesitter.configs'.setup {
|
||||||
incremental_selection = {
|
incremental_selection = {
|
||||||
enable = true,
|
enable = true,
|
||||||
|
|
@ -155,7 +150,6 @@ require'nvim-treesitter.configs'.setup {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
EOF
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# External modules
|
# External modules
|
||||||
|
|
|
||||||
|
|
@ -81,14 +81,19 @@ function M.attach(bufnr, lang)
|
||||||
local parser = parsers.get_parser(bufnr, lang)
|
local parser = parsers.get_parser(bufnr, lang)
|
||||||
local config = configs.get_module('highlight')
|
local config = configs.get_module('highlight')
|
||||||
|
|
||||||
for k, v in pairs(config.custom_captures) do
|
if config.use_languagetree then
|
||||||
hlmap[k] = v
|
local ltree = require'nvim-treesitter.languagetree'
|
||||||
|
ltree.new(bufnr, lang)
|
||||||
|
else
|
||||||
|
for k, v in pairs(config.custom_captures) do
|
||||||
|
hlmap[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
local query = queries.get_query(lang, "highlights")
|
||||||
|
if not query then return end
|
||||||
|
|
||||||
|
M.highlighters[bufnr] = ts.highlighter.new(parser, query)
|
||||||
end
|
end
|
||||||
|
|
||||||
local query = queries.get_query(lang, "highlights")
|
|
||||||
if not query then return end
|
|
||||||
|
|
||||||
M.highlighters[bufnr] = ts.highlighter.new(parser, query)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.detach(bufnr)
|
function M.detach(bufnr)
|
||||||
|
|
|
||||||
156
lua/nvim-treesitter/languagetree.lua
Normal file
156
lua/nvim-treesitter/languagetree.lua
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
local parsers = require'nvim-treesitter.parsers'
|
||||||
|
local queries = require'nvim-treesitter.query'
|
||||||
|
local tsutils = require'nvim-treesitter.ts_utils'
|
||||||
|
local TSHighlighter = require'vim.treesitter.highlighter'
|
||||||
|
|
||||||
|
local ns = vim.api.nvim_create_namespace("treesitter/highlighter")
|
||||||
|
|
||||||
|
local LanguageTree = {}
|
||||||
|
LanguageTree.__index = LanguageTree
|
||||||
|
|
||||||
|
local trees = { }
|
||||||
|
|
||||||
|
function LanguageTree.new(bufnr, lang, not_root)
|
||||||
|
local buf
|
||||||
|
if not bufnr or bufnr == 0 then
|
||||||
|
buf = vim.api.nvim_get_current_buf()
|
||||||
|
else
|
||||||
|
buf = bufnr
|
||||||
|
end
|
||||||
|
|
||||||
|
local parser = parsers.get_parser(buf, parsers.ft_to_lang(lang))
|
||||||
|
if not parser then return end
|
||||||
|
|
||||||
|
local query = queries.get_query(lang, "highlights")
|
||||||
|
if not query then return end
|
||||||
|
|
||||||
|
local self = setmetatable(
|
||||||
|
{
|
||||||
|
highlighter = TSHighlighter.new(parser, query),
|
||||||
|
parser = parser,
|
||||||
|
children = {}
|
||||||
|
},
|
||||||
|
LanguageTree)
|
||||||
|
|
||||||
|
if not not_root then
|
||||||
|
trees[buf] = self
|
||||||
|
self.parser:register_cbs{
|
||||||
|
on_bytes = function() self:update() end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- First setup
|
||||||
|
self:update()
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function LanguageTree:add_child(lang, child)
|
||||||
|
if not vim.tbl_contains(self.children, child) then
|
||||||
|
table.insert(self.children, child)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function LanguageTree:remove_child(lang)
|
||||||
|
self.children[lang] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function LanguageTree:node_for_range(range)
|
||||||
|
for _, child in pairs(self.children) do
|
||||||
|
if child:contains(range) then
|
||||||
|
return child:node_for_range(range)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if self:contains(range) then
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function range_contains(source, dest)
|
||||||
|
local start_fits = source[1] < dest[1] or (source[1] == dest[1] and source[2] <= dest[2])
|
||||||
|
local end_fits = source[3] > dest[3] or (source[3] == dest[3] and source[4] >= dest[4])
|
||||||
|
|
||||||
|
return start_fits and end_fits
|
||||||
|
end
|
||||||
|
|
||||||
|
function LanguageTree:contains(range)
|
||||||
|
for _, source in pairs(self.parser:included_ranges()) do
|
||||||
|
if range_contains(source, range) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function LanguageTree:update()
|
||||||
|
local query = queries.get_query(self.parser.lang, "injections")
|
||||||
|
if not query then return end
|
||||||
|
|
||||||
|
local root = self.parser:parse():root()
|
||||||
|
local startl, _, stopl, _ = root:range()
|
||||||
|
|
||||||
|
local injections = {}
|
||||||
|
|
||||||
|
-- Find injections
|
||||||
|
for inj in queries.iter_prepared_matches(query, root, self.parser.bufnr, startl, stopl+1) do
|
||||||
|
local lang = inj.lang
|
||||||
|
|
||||||
|
if type(lang) ~= "string" then
|
||||||
|
lang = tsutils.get_node_text(lang.node, self.parser.bufnr)[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
if not lang or not inj.injection.node then
|
||||||
|
vim.api.nvim_err_writeln("Invalid match encountered")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if not injections[lang] then
|
||||||
|
injections[lang] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(injections[lang], inj.injection.node)
|
||||||
|
end
|
||||||
|
|
||||||
|
local seen = {}
|
||||||
|
|
||||||
|
-- Update each child accordingly
|
||||||
|
for lang, ranges in pairs(injections) do
|
||||||
|
if not self.children[lang] then
|
||||||
|
self.children[lang] = LanguageTree.new(self.parser.bufnr, lang, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.children[lang] then
|
||||||
|
self.children[lang].parser:set_included_ranges(ranges)
|
||||||
|
self.children[lang]:update()
|
||||||
|
seen[lang] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Clean up unused parsers
|
||||||
|
for lang, _ in pairs(self.children) do
|
||||||
|
if not seen[lang] then
|
||||||
|
self:remove_child(lang)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function LanguageTree._on_line(_, _win, buf, line)
|
||||||
|
local tree = trees[buf]
|
||||||
|
if not tree then return end
|
||||||
|
|
||||||
|
local line_len = #(vim.api.nvim_buf_get_lines(buf, line, line + 1, false)[1])
|
||||||
|
|
||||||
|
local matching = tree:node_for_range { line, 0, line, line_len } -- TODO proper search here
|
||||||
|
|
||||||
|
TSHighlighter._on_line("line", _win, buf, line, matching.highlighter)
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.api.nvim_set_decoration_provider(ns, {
|
||||||
|
on_buf = TSHighlighter._on_buf;
|
||||||
|
on_win = TSHighlighter._on_win;
|
||||||
|
on_line = LanguageTree._on_line;
|
||||||
|
})
|
||||||
|
|
||||||
|
return LanguageTree
|
||||||
19
queries/markdown/highlights.scm
Normal file
19
queries/markdown/highlights.scm
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
(atx_heading) @text.title
|
||||||
|
|
||||||
|
[
|
||||||
|
(code_span)
|
||||||
|
(fenced_code_block)
|
||||||
|
]@text.literal
|
||||||
|
|
||||||
|
(code_fence_content) @none
|
||||||
|
|
||||||
|
[
|
||||||
|
(link_text)
|
||||||
|
(image_description)
|
||||||
|
] @text.strong
|
||||||
|
|
||||||
|
[
|
||||||
|
(emphasis)
|
||||||
|
(strong_emphasis)
|
||||||
|
] @text.emphasis
|
||||||
|
(link_destination) @text.uri
|
||||||
6
queries/markdown/injections.scm
Normal file
6
queries/markdown/injections.scm
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
(fenced_code_block
|
||||||
|
(info_string) @lang
|
||||||
|
(code_fence_content) @injection)
|
||||||
|
|
||||||
|
((html_block) @injection
|
||||||
|
(#set! "lang" "html"))
|
||||||
Loading…
Add table
Add a link
Reference in a new issue