Skip to content

Commit f45bd15

Browse files
committed
treesitter
1 parent 92e0f61 commit f45bd15

File tree

4 files changed

+123
-2
lines changed

4 files changed

+123
-2
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,10 @@ require("quicker").setup({
198198
-- Keep the cursor to the right of the filename and lnum columns
199199
constrain_cursor = true,
200200
highlight = {
201-
-- Use treesitter highlighting
201+
-- Attach parser to qf buffer, highlight text in real time as you edit.
202+
-- If this is true, other highlight options are ignored.
203+
attach_parser = true,
204+
-- Query treesitter highlight groups using string parser, only update after you save or refresh.
202205
treesitter = true,
203206
-- Use LSP semantic token highlighting
204207
lsp = true,

lua/quicker/config.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ end
154154
---@field soft_end? string
155155

156156
---@class (exact) quicker.HighlightConfig
157+
---@field attach_parser boolean
157158
---@field treesitter boolean
158159
---@field lsp boolean
159160
---@field load_buffers boolean

lua/quicker/display.lua

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ local function highlight_buffer_when_entered(qfbufnr, info)
267267
vim.b[qfbufnr].pending_highlight = nil
268268
info.start_idx = 1
269269
info.end_idx = vim.api.nvim_buf_line_count(qfbufnr)
270+
info.regions = {}
271+
info.empty_regions = {}
270272
schedule_highlights(info)
271273
end,
272274
})
@@ -339,7 +341,22 @@ add_qf_highlights = function(info)
339341
loaded = true
340342
end
341343

342-
if loaded then
344+
local ft = vim.filetype.match({ buf = item.bufnr })
345+
-- If attach_parser is true, we should not apply lsp highlight or query ts highlights
346+
-- from loaded buffers, as they will overwrite each other.
347+
if config.highlight.attach_parser and ft then
348+
info.regions[ft] = info.regions[ft] or {}
349+
info.empty_regions[ft] = info.empty_regions[ft] or {}
350+
local filename = vim.split(line, EM_QUAD, { plain = true })[1]
351+
table.insert(info.regions[ft], {
352+
{
353+
i - 1,
354+
filename:len() + EM_QUAD_LEN - 1, -- skip EM_QUAD
355+
i - 1,
356+
#line,
357+
},
358+
})
359+
elseif loaded then
343360
add_item_highlights_from_buf(qfbufnr, item, line, i)
344361
elseif config.highlight.treesitter then
345362
local filename = vim.split(line, EM_QUAD, { plain = true })[1]
@@ -383,6 +400,11 @@ add_qf_highlights = function(info)
383400
return
384401
end
385402
end
403+
if config.highlight.attach_parser then
404+
-- cleanup previous regions each time we call setqflist.
405+
require("quicker.treesitter").attach(qf_list.qfbufnr, info.empty_regions)
406+
require("quicker.treesitter").attach(qf_list.qfbufnr, info.regions)
407+
end
386408

387409
vim.api.nvim_buf_clear_namespace(qf_list.qfbufnr, ns, info.end_idx, -1)
388410
end
@@ -425,6 +447,8 @@ end
425447
---@field id integer
426448
---@field start_idx integer
427449
---@field end_idx integer
450+
---@field regions table<string, number[][][]>
451+
---@field empty_regions table<string, number[][][]>
428452
---@field winid integer
429453
---@field quickfix 1|0
430454
---@field force_bufload? boolean field injected by us to control if we're forcing a bufload for the syntax highlighting
@@ -588,6 +612,8 @@ function M.quickfixtextfunc(info)
588612

589613
-- If we just rendered the last item, add highlights
590614
if info.end_idx == #items then
615+
info.regions = {}
616+
info.empty_regions = {}
591617
schedule_highlights(info)
592618

593619
if qf_list.qfbufnr > 0 then

lua/quicker/treesitter.lua

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---@alias trouble.LangRegions table<string, number[][][]>
2+
3+
local M = {}
4+
5+
M.cache = {} ---@type table<number, table<string,{parser: vim.treesitter.LanguageTree, highlighter:vim.treesitter.highlighter, enabled:boolean}>>
6+
local ns = vim.api.nvim_create_namespace("quicker.treesitter")
7+
8+
local TSHighlighter = vim.treesitter.highlighter
9+
10+
local function wrap(name)
11+
return function(_, win, buf, ...)
12+
if not M.cache[buf] then
13+
return false
14+
end
15+
for _, hl in pairs(M.cache[buf] or {}) do
16+
if hl.enabled then
17+
TSHighlighter.active[buf] = hl.highlighter
18+
TSHighlighter[name](_, win, buf, ...)
19+
end
20+
end
21+
TSHighlighter.active[buf] = nil
22+
end
23+
end
24+
25+
M.did_setup = false
26+
function M.setup()
27+
if M.did_setup then
28+
return
29+
end
30+
M.did_setup = true
31+
32+
vim.api.nvim_set_decoration_provider(ns, {
33+
on_win = wrap("_on_win"),
34+
on_line = wrap("_on_line"),
35+
})
36+
37+
vim.api.nvim_create_autocmd("QuickFixCmdPost", {
38+
group = vim.api.nvim_create_augroup("quicker.treesitter.hl", { clear = true }),
39+
callback = function(ev)
40+
M.cache[ev.buf] = nil
41+
end,
42+
})
43+
end
44+
45+
---@param buf number
46+
---@param regions trouble.LangRegions
47+
function M.attach(buf, regions)
48+
M.setup()
49+
M.cache[buf] = M.cache[buf] or {}
50+
for lang in pairs(M.cache[buf]) do
51+
M.cache[buf][lang].enabled = regions[lang] ~= nil
52+
end
53+
54+
for lang in pairs(regions) do
55+
M._attach_lang(buf, lang, regions[lang])
56+
end
57+
end
58+
59+
---@param buf number
60+
---@param lang? string
61+
function M._attach_lang(buf, lang, regions)
62+
lang = lang or "markdown"
63+
lang = lang == "markdown" and "markdown_inline" or lang
64+
65+
M.cache[buf] = M.cache[buf] or {}
66+
67+
if not M.cache[buf][lang] then
68+
local ok, parser = pcall(vim.treesitter.get_parser, buf, lang)
69+
if not ok then
70+
return
71+
end
72+
parser:set_included_regions(vim.deepcopy(regions))
73+
M.cache[buf][lang] = {
74+
parser = parser,
75+
highlighter = TSHighlighter.new(parser),
76+
}
77+
end
78+
M.cache[buf][lang].enabled = true
79+
local parser = M.cache[buf][lang].parser
80+
81+
parser:set_included_regions(vim.deepcopy(regions))
82+
-- Run a full parse for all included regions. There are two reasons:
83+
-- 1. When we call `vim.treesitter.get_parser`, we have not set any
84+
-- injection ranges.
85+
-- 2. If this is not called, the highlighter will do incremental parsing,
86+
-- which means it only parses visible areas (the on_win and on_line callback),
87+
-- so if we modify the buffer, unvisited area's state get unsynced.
88+
pcall(parser.parse, parser, true)
89+
end
90+
91+
return M

0 commit comments

Comments
 (0)