From fe1f5f57ade2820656e063bbc2565702f016f462 Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Sat, 1 Feb 2025 20:57:03 +0800 Subject: [PATCH 01/16] treesitter --- README.md | 5 ++- lua/quicker/config.lua | 1 + lua/quicker/display.lua | 32 +++++++++++++- lua/quicker/treesitter.lua | 91 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 lua/quicker/treesitter.lua diff --git a/README.md b/README.md index 9ceaf2a..75c75b6 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,10 @@ require("quicker").setup({ -- Keep the cursor to the right of the filename and lnum columns constrain_cursor = true, highlight = { - -- Use treesitter highlighting + -- Attach parser to qf buffer, highlight text in real time as you edit. + -- If this is true, other highlight options are ignored. + attach_parser = true, + -- Query treesitter highlight groups using string parser, only update after you save or refresh. treesitter = true, -- Use LSP semantic token highlighting lsp = true, diff --git a/lua/quicker/config.lua b/lua/quicker/config.lua index f60b978..a42c555 100644 --- a/lua/quicker/config.lua +++ b/lua/quicker/config.lua @@ -154,6 +154,7 @@ end ---@field soft_end? string ---@class (exact) quicker.HighlightConfig +---@field attach_parser boolean ---@field treesitter boolean ---@field lsp boolean ---@field load_buffers boolean diff --git a/lua/quicker/display.lua b/lua/quicker/display.lua index 5c551d3..4519a8f 100644 --- a/lua/quicker/display.lua +++ b/lua/quicker/display.lua @@ -5,7 +5,9 @@ local util = require("quicker.util") local M = {} -local EM_QUAD = " " +-- A EM_QUAD and a space, we include an extra space before the real text, because it +-- avoids strange cases such as no highlight when insert at start of qf text. +local EM_QUAD = "  " local EM_QUAD_LEN = EM_QUAD:len() M.EM_QUAD = EM_QUAD M.EM_QUAD_LEN = EM_QUAD_LEN @@ -267,6 +269,8 @@ local function highlight_buffer_when_entered(qfbufnr, info) vim.b[qfbufnr].pending_highlight = nil info.start_idx = 1 info.end_idx = vim.api.nvim_buf_line_count(qfbufnr) + info.regions = {} + info.empty_regions = {} schedule_highlights(info) end, }) @@ -339,7 +343,22 @@ add_qf_highlights = function(info) loaded = true end - if loaded then + local ft = vim.filetype.match({ buf = item.bufnr }) + -- If attach_parser is true, we should not apply lsp highlight or query ts highlights + -- from loaded buffers, as they will overwrite each other. + if config.highlight.attach_parser and ft then + info.regions[ft] = info.regions[ft] or {} + info.empty_regions[ft] = info.empty_regions[ft] or {} + local filename = vim.split(line, EM_QUAD, { plain = true })[1] + table.insert(info.regions[ft], { + { + i - 1, + filename:len() + EM_QUAD_LEN - 1, -- skip EM_QUAD + i - 1, + #line, + }, + }) + elseif loaded then add_item_highlights_from_buf(qfbufnr, item, line, i) elseif config.highlight.treesitter then local filename = vim.split(line, EM_QUAD, { plain = true })[1] @@ -383,6 +402,11 @@ add_qf_highlights = function(info) return end end + if config.highlight.attach_parser then + -- cleanup previous regions each time we call setqflist. + require("quicker.treesitter").attach(qf_list.qfbufnr, info.empty_regions) + require("quicker.treesitter").attach(qf_list.qfbufnr, info.regions) + end vim.api.nvim_buf_clear_namespace(qf_list.qfbufnr, ns, info.end_idx, -1) end @@ -425,6 +449,8 @@ end ---@field id integer ---@field start_idx integer ---@field end_idx integer +---@field regions table +---@field empty_regions table ---@field winid integer ---@field quickfix 1|0 ---@field force_bufload? boolean field injected by us to control if we're forcing a bufload for the syntax highlighting @@ -588,6 +614,8 @@ function M.quickfixtextfunc(info) -- If we just rendered the last item, add highlights if info.end_idx == #items then + info.regions = {} + info.empty_regions = {} schedule_highlights(info) if qf_list.qfbufnr > 0 then diff --git a/lua/quicker/treesitter.lua b/lua/quicker/treesitter.lua new file mode 100644 index 0000000..d0c7c8c --- /dev/null +++ b/lua/quicker/treesitter.lua @@ -0,0 +1,91 @@ +---@alias quicker.LangRegions table + +local M = {} + +M.cache = {} ---@type table> +local ns = vim.api.nvim_create_namespace("quicker.treesitter") + +local TSHighlighter = vim.treesitter.highlighter + +local function wrap(name) + return function(_, win, buf, ...) + if not M.cache[buf] then + return false + end + for _, hl in pairs(M.cache[buf] or {}) do + if hl.enabled then + TSHighlighter.active[buf] = hl.highlighter + TSHighlighter[name](_, win, buf, ...) + end + end + TSHighlighter.active[buf] = nil + end +end + +M.did_setup = false +function M.setup() + if M.did_setup then + return + end + M.did_setup = true + + vim.api.nvim_set_decoration_provider(ns, { + on_win = wrap("_on_win"), + on_line = wrap("_on_line"), + }) + + vim.api.nvim_create_autocmd("QuickFixCmdPost", { + group = vim.api.nvim_create_augroup("quicker.treesitter.hl", { clear = true }), + callback = function(ev) + M.cache[ev.buf] = nil + end, + }) +end + +---@param buf number +---@param regions quicker.LangRegions +function M.attach(buf, regions) + M.setup() + M.cache[buf] = M.cache[buf] or {} + for lang in pairs(M.cache[buf]) do + M.cache[buf][lang].enabled = regions[lang] ~= nil + end + + for lang in pairs(regions) do + M._attach_lang(buf, lang, regions[lang]) + end +end + +---@param buf number +---@param lang? string +function M._attach_lang(buf, lang, regions) + lang = lang or "markdown" + lang = lang == "markdown" and "markdown_inline" or lang + + M.cache[buf] = M.cache[buf] or {} + + if not M.cache[buf][lang] then + local ok, parser = pcall(vim.treesitter.get_parser, buf, lang) + if not ok then + return + end + parser:set_included_regions(vim.deepcopy(regions)) + M.cache[buf][lang] = { + parser = parser, + highlighter = TSHighlighter.new(parser), + } + end + M.cache[buf][lang].enabled = true + local parser = M.cache[buf][lang].parser + + parser:set_included_regions(vim.deepcopy(regions)) + -- Run a full parse for all included regions. There are two reasons: + -- 1. When we call `vim.treesitter.get_parser`, we have not set any + -- injection ranges. + -- 2. If this is not called, the highlighter will do incremental parsing, + -- which means it only parses visible areas (the on_win and on_line callback), + -- so if we modify the buffer, unvisited area's state get unsynced. + pcall(parser.parse, parser, true) +end + +return M From 290be6945fc0af09e3912e7d82c4310936445197 Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Tue, 4 Feb 2025 03:06:42 +0800 Subject: [PATCH 02/16] merge the same line --- lua/quicker/display.lua | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/lua/quicker/display.lua b/lua/quicker/display.lua index 4519a8f..3e067dc 100644 --- a/lua/quicker/display.lua +++ b/lua/quicker/display.lua @@ -350,14 +350,30 @@ add_qf_highlights = function(info) info.regions[ft] = info.regions[ft] or {} info.empty_regions[ft] = info.empty_regions[ft] or {} local filename = vim.split(line, EM_QUAD, { plain = true })[1] - table.insert(info.regions[ft], { - { - i - 1, - filename:len() + EM_QUAD_LEN - 1, -- skip EM_QUAD - i - 1, - #line, - }, - }) + + -- Note: we must include the new line character, when multiple lines are treated as one + -- range, without "\n" causes all the problems, for example: + -- + -- tests/tmp/expand_1.lua ┃ 2┃-- a comment + -- tests/tmp/expand_1.lua ┃ 3┃local a = 1 + -- + -- The parser will treat this as "-- a commentlocal a = 1". + local region = { + i - 1, + filename:len() + EM_QUAD_LEN - 1, -- skip EM_QUAD but include a space + i, + 0, + } + info.previous_item = info.previous_item or item + if info.previous_item.bufnr == item.bufnr and info.previous_item.lnum == item.lnum - 1 then + -- Merge current range with previous one, parse them together. + table.insert(info.regions[ft][#info.regions[ft]], region) + else + table.insert(info.regions[ft], { + region, + }) + end + info.previous_item = item elseif loaded then add_item_highlights_from_buf(qfbufnr, item, line, i) elseif config.highlight.treesitter then @@ -451,6 +467,7 @@ end ---@field end_idx integer ---@field regions table ---@field empty_regions table +---@field previous_item QuickFixItem ---@field winid integer ---@field quickfix 1|0 ---@field force_bufload? boolean field injected by us to control if we're forcing a bufload for the syntax highlighting @@ -496,7 +513,7 @@ function M.quickfixtextfunc(info) if user_data.header == "hard" then -- Header when expanded QF list local pieces = { - string.rep(b.strong_header, col_width + 1), + string.rep(b.strong_header, col_width + 2), b.strong_cross, string.rep(b.strong_header, lnum_width), } @@ -511,7 +528,7 @@ function M.quickfixtextfunc(info) elseif user_data.header == "soft" then -- Soft header when expanded QF list local pieces = { - string.rep(b.soft_header, col_width + 1), + string.rep(b.soft_header, col_width + 2), b.soft_cross, string.rep(b.soft_header, lnum_width), } From 3fe3881d1989bf5dbba9c7b8be792e036e4a5a0e Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Thu, 13 Feb 2025 09:33:35 +0800 Subject: [PATCH 03/16] fix: use BufWipeout instead of QuickFixCmdPost --- lua/quicker/treesitter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/quicker/treesitter.lua b/lua/quicker/treesitter.lua index d0c7c8c..bc98296 100644 --- a/lua/quicker/treesitter.lua +++ b/lua/quicker/treesitter.lua @@ -34,7 +34,7 @@ function M.setup() on_line = wrap("_on_line"), }) - vim.api.nvim_create_autocmd("QuickFixCmdPost", { + vim.api.nvim_create_autocmd("BufWipeout", { group = vim.api.nvim_create_augroup("quicker.treesitter.hl", { clear = true }), callback = function(ev) M.cache[ev.buf] = nil From c96d18404d3565f215ed9bfa739cfb7c1af000d4 Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Thu, 13 Feb 2025 09:59:57 +0800 Subject: [PATCH 04/16] fix: type anotations --- lua/quicker/treesitter.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/quicker/treesitter.lua b/lua/quicker/treesitter.lua index bc98296..985e793 100644 --- a/lua/quicker/treesitter.lua +++ b/lua/quicker/treesitter.lua @@ -1,4 +1,4 @@ ----@alias quicker.LangRegions table +---@alias quicker.LangRegions table local M = {} @@ -58,6 +58,7 @@ end ---@param buf number ---@param lang? string +---@param regions quicker.LangRegions function M._attach_lang(buf, lang, regions) lang = lang or "markdown" lang = lang == "markdown" and "markdown_inline" or lang From d80e9dbdd159e5f36fbe8d15f188cec42c0662d2 Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Thu, 13 Feb 2025 10:01:38 +0800 Subject: [PATCH 05/16] fix: make cache local --- lua/quicker/treesitter.lua | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lua/quicker/treesitter.lua b/lua/quicker/treesitter.lua index 985e793..ba7799b 100644 --- a/lua/quicker/treesitter.lua +++ b/lua/quicker/treesitter.lua @@ -2,17 +2,17 @@ local M = {} -M.cache = {} ---@type table> +local cache = {} ---@type table> local ns = vim.api.nvim_create_namespace("quicker.treesitter") local TSHighlighter = vim.treesitter.highlighter local function wrap(name) return function(_, win, buf, ...) - if not M.cache[buf] then + if not cache[buf] then return false end - for _, hl in pairs(M.cache[buf] or {}) do + for _, hl in pairs(cache[buf] or {}) do if hl.enabled then TSHighlighter.active[buf] = hl.highlighter TSHighlighter[name](_, win, buf, ...) @@ -37,7 +37,7 @@ function M.setup() vim.api.nvim_create_autocmd("BufWipeout", { group = vim.api.nvim_create_augroup("quicker.treesitter.hl", { clear = true }), callback = function(ev) - M.cache[ev.buf] = nil + cache[ev.buf] = nil end, }) end @@ -46,9 +46,9 @@ end ---@param regions quicker.LangRegions function M.attach(buf, regions) M.setup() - M.cache[buf] = M.cache[buf] or {} - for lang in pairs(M.cache[buf]) do - M.cache[buf][lang].enabled = regions[lang] ~= nil + cache[buf] = cache[buf] or {} + for lang in pairs(cache[buf]) do + cache[buf][lang].enabled = regions[lang] ~= nil end for lang in pairs(regions) do @@ -63,21 +63,21 @@ function M._attach_lang(buf, lang, regions) lang = lang or "markdown" lang = lang == "markdown" and "markdown_inline" or lang - M.cache[buf] = M.cache[buf] or {} + cache[buf] = cache[buf] or {} - if not M.cache[buf][lang] then + if not cache[buf][lang] then local ok, parser = pcall(vim.treesitter.get_parser, buf, lang) if not ok then return end parser:set_included_regions(vim.deepcopy(regions)) - M.cache[buf][lang] = { + cache[buf][lang] = { parser = parser, highlighter = TSHighlighter.new(parser), } end - M.cache[buf][lang].enabled = true - local parser = M.cache[buf][lang].parser + cache[buf][lang].enabled = true + local parser = cache[buf][lang].parser parser:set_included_regions(vim.deepcopy(regions)) -- Run a full parse for all included regions. There are two reasons: From 70af1977788458f9f73e9d969fa70a45402f26fe Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Thu, 13 Feb 2025 11:24:23 +0800 Subject: [PATCH 06/16] also load highlight from buffer --- README.md | 4 ++-- lua/quicker/display.lua | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 75c75b6..85a9d59 100644 --- a/README.md +++ b/README.md @@ -199,9 +199,9 @@ require("quicker").setup({ constrain_cursor = true, highlight = { -- Attach parser to qf buffer, highlight text in real time as you edit. - -- If this is true, other highlight options are ignored. attach_parser = true, - -- Query treesitter highlight groups using string parser, only update after you save or refresh. + -- Additionally query treesitter highlight groups using string parser, + -- those highlights only update when you save or refresh. treesitter = true, -- Use LSP semantic token highlighting lsp = true, diff --git a/lua/quicker/display.lua b/lua/quicker/display.lua index 3e067dc..a89c9b9 100644 --- a/lua/quicker/display.lua +++ b/lua/quicker/display.lua @@ -344,8 +344,6 @@ add_qf_highlights = function(info) end local ft = vim.filetype.match({ buf = item.bufnr }) - -- If attach_parser is true, we should not apply lsp highlight or query ts highlights - -- from loaded buffers, as they will overwrite each other. if config.highlight.attach_parser and ft then info.regions[ft] = info.regions[ft] or {} info.empty_regions[ft] = info.empty_regions[ft] or {} @@ -374,8 +372,6 @@ add_qf_highlights = function(info) }) end info.previous_item = item - elseif loaded then - add_item_highlights_from_buf(qfbufnr, item, line, i) elseif config.highlight.treesitter then local filename = vim.split(line, EM_QUAD, { plain = true })[1] local offset = filename:len() + EM_QUAD_LEN @@ -392,6 +388,9 @@ add_qf_highlights = function(info) }) end end + if loaded then + add_item_highlights_from_buf(qfbufnr, item, line, i) + end end local user_data = util.get_user_data(item) From 5e9ed34e306190523f96518fbb0f93b0171688ff Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sat, 15 Feb 2025 15:02:02 -0800 Subject: [PATCH 07/16] fix: EM_QUAD doesn't need a space --- lua/quicker/display.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lua/quicker/display.lua b/lua/quicker/display.lua index a89c9b9..92c18da 100644 --- a/lua/quicker/display.lua +++ b/lua/quicker/display.lua @@ -5,9 +5,7 @@ local util = require("quicker.util") local M = {} --- A EM_QUAD and a space, we include an extra space before the real text, because it --- avoids strange cases such as no highlight when insert at start of qf text. -local EM_QUAD = "  " +local EM_QUAD = " " local EM_QUAD_LEN = EM_QUAD:len() M.EM_QUAD = EM_QUAD M.EM_QUAD_LEN = EM_QUAD_LEN @@ -358,7 +356,7 @@ add_qf_highlights = function(info) -- The parser will treat this as "-- a commentlocal a = 1". local region = { i - 1, - filename:len() + EM_QUAD_LEN - 1, -- skip EM_QUAD but include a space + filename:len() + EM_QUAD_LEN, i, 0, } @@ -512,7 +510,7 @@ function M.quickfixtextfunc(info) if user_data.header == "hard" then -- Header when expanded QF list local pieces = { - string.rep(b.strong_header, col_width + 2), + string.rep(b.strong_header, col_width + 1), b.strong_cross, string.rep(b.strong_header, lnum_width), } @@ -527,7 +525,7 @@ function M.quickfixtextfunc(info) elseif user_data.header == "soft" then -- Soft header when expanded QF list local pieces = { - string.rep(b.soft_header, col_width + 2), + string.rep(b.soft_header, col_width + 1), b.soft_cross, string.rep(b.soft_header, lnum_width), } From 1eb986b0d55693bbaadfd763f0b2a6dd73d559ee Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Sun, 16 Feb 2025 10:05:41 +0800 Subject: [PATCH 08/16] change number[][][] to Range4[][] --- lua/quicker/display.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/quicker/display.lua b/lua/quicker/display.lua index 92c18da..54c9622 100644 --- a/lua/quicker/display.lua +++ b/lua/quicker/display.lua @@ -462,8 +462,8 @@ end ---@field id integer ---@field start_idx integer ---@field end_idx integer ----@field regions table ----@field empty_regions table +---@field regions table +---@field empty_regions table ---@field previous_item QuickFixItem ---@field winid integer ---@field quickfix 1|0 From a23b3b09c88988090b63b381c98ac6a4e9c28407 Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Sun, 16 Feb 2025 11:07:51 +0800 Subject: [PATCH 09/16] do not register callback when config.highlight.max_lines exceeds --- README.md | 8 +-- lua/quicker/config.lua | 7 ++- lua/quicker/display.lua | 40 +------------- lua/quicker/highlight.lua | 109 ------------------------------------- lua/quicker/treesitter.lua | 45 ++++++++------- 5 files changed, 37 insertions(+), 172 deletions(-) diff --git a/README.md b/README.md index 85a9d59..a3ca314 100644 --- a/README.md +++ b/README.md @@ -198,11 +198,11 @@ require("quicker").setup({ -- Keep the cursor to the right of the filename and lnum columns constrain_cursor = true, highlight = { - -- Attach parser to qf buffer, highlight text in real time as you edit. - attach_parser = true, - -- Additionally query treesitter highlight groups using string parser, - -- those highlights only update when you save or refresh. + -- Attach treesitter parser to qf buffer, highlight text in real time as you edit. treesitter = true, + -- Do not register callbacks when buffer line counts exceed this limit. In other words, + -- highlight won't get updated as you edit. + max_lines = 10000, -- Use LSP semantic token highlighting lsp = true, -- Load the referenced buffers to apply more accurate highlights (may be slow) diff --git a/lua/quicker/config.lua b/lua/quicker/config.lua index a42c555..67310d0 100644 --- a/lua/quicker/config.lua +++ b/lua/quicker/config.lua @@ -26,8 +26,11 @@ local default_config = { -- Keep the cursor to the right of the filename and lnum columns constrain_cursor = true, highlight = { - -- Use treesitter highlighting + -- Attach treesitter parser to qf buffer, highlight text in real time as you edit. treesitter = true, + -- Do not register callbacks when buffer line counts exceed this limit. In other words, + -- highlight won't get updated as you edit. + max_lines = 10000, -- Use LSP semantic token highlighting lsp = true, -- Load the referenced buffers to apply more accurate highlights (may be slow) @@ -154,8 +157,8 @@ end ---@field soft_end? string ---@class (exact) quicker.HighlightConfig ----@field attach_parser boolean ---@field treesitter boolean +---@field max_lines integer ---@field lsp boolean ---@field load_buffers boolean diff --git a/lua/quicker/display.lua b/lua/quicker/display.lua index 54c9622..d7e6243 100644 --- a/lua/quicker/display.lua +++ b/lua/quicker/display.lua @@ -216,27 +216,6 @@ local function add_item_highlights_from_buf(qfbufnr, item, line, lnum) offset = offset - item_space end - -- Add treesitter highlights - if config.highlight.treesitter then - for _, hl in ipairs(highlight.buf_get_ts_highlights(item.bufnr, item.lnum)) do - local start_col, end_col, hl_group = hl[1], hl[2], hl[3] - if end_col == -1 then - end_col = src_line:len() - end - -- If the highlight starts at the beginning of the source line, then it might be off the - -- buffer in the quickfix because we've removed leading whitespace. If so, clamp the value - -- to 0. Except, for some reason 0 gives incorrect results, but -1 works properly even - -- though -1 should indicate the *end* of the line. Not sure why this work, but it does. - local hl_start = math.max(-1, start_col + offset) - vim.api.nvim_buf_set_extmark(qfbufnr, ns, lnum - 1, hl_start, { - hl_group = hl_group, - end_col = end_col + offset, - priority = 100, - strict = false, - }) - end - end - -- Add LSP semantic token highlights if config.highlight.lsp then for _, hl in ipairs(highlight.buf_get_lsp_highlights(item.bufnr, item.lnum)) do @@ -342,7 +321,7 @@ add_qf_highlights = function(info) end local ft = vim.filetype.match({ buf = item.bufnr }) - if config.highlight.attach_parser and ft then + if config.highlight.treesitter and ft then info.regions[ft] = info.regions[ft] or {} info.empty_regions[ft] = info.empty_regions[ft] or {} local filename = vim.split(line, EM_QUAD, { plain = true })[1] @@ -370,21 +349,6 @@ add_qf_highlights = function(info) }) end info.previous_item = item - elseif config.highlight.treesitter then - local filename = vim.split(line, EM_QUAD, { plain = true })[1] - local offset = filename:len() + EM_QUAD_LEN - local text = line:sub(offset + 1) - for _, hl in ipairs(highlight.get_heuristic_ts_highlights(item, text)) do - local start_col, end_col, hl_group = hl[1], hl[2], hl[3] - start_col = start_col + offset - end_col = end_col + offset - vim.api.nvim_buf_set_extmark(qfbufnr, ns, i - 1, start_col, { - hl_group = hl_group, - end_col = end_col, - priority = 100, - strict = false, - }) - end end if loaded then add_item_highlights_from_buf(qfbufnr, item, line, i) @@ -415,7 +379,7 @@ add_qf_highlights = function(info) return end end - if config.highlight.attach_parser then + if config.highlight.treesitter then -- cleanup previous regions each time we call setqflist. require("quicker.treesitter").attach(qf_list.qfbufnr, info.empty_regions) require("quicker.treesitter").attach(qf_list.qfbufnr, info.regions) diff --git a/lua/quicker/highlight.lua b/lua/quicker/highlight.lua index a7323aa..0b52c2b 100644 --- a/lua/quicker/highlight.lua +++ b/lua/quicker/highlight.lua @@ -19,71 +19,6 @@ local function get_highlight_query(lang) end end ----@param bufnr integer ----@param lnum integer ----@return quicker.TSHighlight[] -function M.buf_get_ts_highlights(bufnr, lnum) - local filetype = vim.bo[bufnr].filetype - if not filetype or filetype == "" then - filetype = vim.filetype.match({ buf = bufnr }) or "" - end - local lang = vim.treesitter.language.get_lang(filetype) or filetype - if lang == "" then - return {} - end - local ok, parser = pcall(vim.treesitter.get_parser, bufnr, lang) - if not ok or not parser then - return {} - end - - local row = lnum - 1 - if not parser:is_valid() then - parser:parse(true) - end - - local highlights = {} - parser:for_each_tree(function(tstree, tree) - if not tstree then - return - end - - local root_node = tstree:root() - local root_start_row, _, root_end_row, _ = root_node:range() - - -- Only worry about trees within the line range - if root_start_row > row or root_end_row < row then - return - end - - local query = get_highlight_query(tree:lang()) - - -- Some injected languages may not have highlight queries. - if not query then - return - end - - for capture, node, metadata in query:iter_captures(root_node, bufnr, row, root_end_row + 1) do - if capture == nil then - break - end - - local range = vim.treesitter.get_range(node, bufnr, metadata[capture]) - local start_row, start_col, _, end_row, end_col, _ = unpack(range) - if start_row > row then - break - end - local capture_name = query.captures[capture] - local hl = string.format("@%s.%s", capture_name, tree:lang()) - if end_row > start_row then - end_col = -1 - end - table.insert(highlights, { start_col, end_col, hl }) - end - end) - - return highlights -end - ---@class quicker.LSPHighlight ---@field [1] integer start_col ---@field [2] integer end_col @@ -157,50 +92,6 @@ function M.buf_get_lsp_highlights(bufnr, lnum) return lsp_highlights end ----@param item QuickFixItem ----@param line string ----@return quicker.TSHighlight[] -M.get_heuristic_ts_highlights = function(item, line) - local filetype = vim.filetype.match({ buf = item.bufnr }) - if not filetype then - return {} - end - - local lang = vim.treesitter.language.get_lang(filetype) - if not lang then - return {} - end - - local has_parser, parser = pcall(vim.treesitter.get_string_parser, line, lang) - if not has_parser then - return {} - end - - local root = parser:parse(true)[1]:root() - local query = vim.treesitter.query.get(lang, "highlights") - if not query then - return {} - end - - local highlights = {} - for capture, node, metadata in query:iter_captures(root, line) do - if capture == nil then - break - end - - local range = vim.treesitter.get_range(node, line, metadata[capture]) - local start_row, start_col, _, end_row, end_col, _ = unpack(range) - local capture_name = query.captures[capture] - local hl = string.format("@%s.%s", capture_name, lang) - if end_row > start_row then - end_col = -1 - end - table.insert(highlights, { start_col, end_col, hl }) - end - - return highlights -end - function M.set_highlight_groups() if vim.tbl_isempty(vim.api.nvim_get_hl(0, { name = "QuickFixHeaderHard" })) then vim.api.nvim_set_hl(0, "QuickFixHeaderHard", { link = "Delimiter", default = true }) diff --git a/lua/quicker/treesitter.lua b/lua/quicker/treesitter.lua index ba7799b..dd37d40 100644 --- a/lua/quicker/treesitter.lua +++ b/lua/quicker/treesitter.lua @@ -1,4 +1,4 @@ ----@alias quicker.LangRegions table +local config = require("quicker.config") local M = {} @@ -42,6 +42,8 @@ function M.setup() }) end +---@alias quicker.LangRegions table + ---@param buf number ---@param regions quicker.LangRegions function M.attach(buf, regions) @@ -51,35 +53,40 @@ function M.attach(buf, regions) cache[buf][lang].enabled = regions[lang] ~= nil end + local counts = 0 + for _, region in pairs(regions) do + counts = counts + #vim.iter(region):flatten():totable() + end + for lang in pairs(regions) do - M._attach_lang(buf, lang, regions[lang]) + M._attach_lang(buf, lang, regions[lang], counts > config.highlight.max_lines) end end ---@param buf number ----@param lang? string +---@param lang string ---@param regions quicker.LangRegions -function M._attach_lang(buf, lang, regions) - lang = lang or "markdown" - lang = lang == "markdown" and "markdown_inline" or lang - +---@param detach boolean +function M._attach_lang(buf, lang, regions, detach) cache[buf] = cache[buf] or {} - if not cache[buf][lang] then - local ok, parser = pcall(vim.treesitter.get_parser, buf, lang) - if not ok then - return - end - parser:set_included_regions(vim.deepcopy(regions)) - cache[buf][lang] = { - parser = parser, - highlighter = TSHighlighter.new(parser), - } + local ok, parser + if detach then + -- This avoids registering callbacks on the languagetree. + ok, parser = pcall(vim.treesitter.languagetree.new, buf, lang) + else + ok, parser = pcall(vim.treesitter.get_parser, buf, lang) + end + if not ok then + return end + parser:set_included_regions(vim.deepcopy(regions)) + cache[buf][lang] = { + parser = parser, + highlighter = TSHighlighter.new(parser), + } cache[buf][lang].enabled = true - local parser = cache[buf][lang].parser - parser:set_included_regions(vim.deepcopy(regions)) -- Run a full parse for all included regions. There are two reasons: -- 1. When we call `vim.treesitter.get_parser`, we have not set any -- injection ranges. From bbd28401897db3f9122ea2f0c20fdc032022dff1 Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Sun, 16 Feb 2025 12:03:06 +0800 Subject: [PATCH 10/16] cache filetype for each bufnr --- lua/quicker/display.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lua/quicker/display.lua b/lua/quicker/display.lua index d7e6243..f67953d 100644 --- a/lua/quicker/display.lua +++ b/lua/quicker/display.lua @@ -248,6 +248,7 @@ local function highlight_buffer_when_entered(qfbufnr, info) info.end_idx = vim.api.nvim_buf_line_count(qfbufnr) info.regions = {} info.empty_regions = {} + info.ft = {} schedule_highlights(info) end, }) @@ -320,7 +321,12 @@ add_qf_highlights = function(info) loaded = true end - local ft = vim.filetype.match({ buf = item.bufnr }) + local ft = info.ft[item.bufnr] + if ft == nil then + ft = loaded and vim.bo[item.bufnr].filetype or vim.filetype.match({ buf = item.bufnr }) + info.ft[item.bufnr] = ft + end + if config.highlight.treesitter and ft then info.regions[ft] = info.regions[ft] or {} info.empty_regions[ft] = info.empty_regions[ft] or {} @@ -428,6 +434,7 @@ end ---@field end_idx integer ---@field regions table ---@field empty_regions table +---@field ft table ---@field previous_item QuickFixItem ---@field winid integer ---@field quickfix 1|0 @@ -594,6 +601,7 @@ function M.quickfixtextfunc(info) if info.end_idx == #items then info.regions = {} info.empty_regions = {} + info.ft = {} schedule_highlights(info) if qf_list.qfbufnr > 0 then From 9a7fa03f3ebc545d396577fe30b00a280fe4deaa Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Sun, 16 Feb 2025 12:34:34 +0800 Subject: [PATCH 11/16] remove empty_regions --- lua/quicker/display.lua | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lua/quicker/display.lua b/lua/quicker/display.lua index f67953d..a45604a 100644 --- a/lua/quicker/display.lua +++ b/lua/quicker/display.lua @@ -247,7 +247,6 @@ local function highlight_buffer_when_entered(qfbufnr, info) info.start_idx = 1 info.end_idx = vim.api.nvim_buf_line_count(qfbufnr) info.regions = {} - info.empty_regions = {} info.ft = {} schedule_highlights(info) end, @@ -329,7 +328,6 @@ add_qf_highlights = function(info) if config.highlight.treesitter and ft then info.regions[ft] = info.regions[ft] or {} - info.empty_regions[ft] = info.empty_regions[ft] or {} local filename = vim.split(line, EM_QUAD, { plain = true })[1] -- Note: we must include the new line character, when multiple lines are treated as one @@ -386,8 +384,6 @@ add_qf_highlights = function(info) end end if config.highlight.treesitter then - -- cleanup previous regions each time we call setqflist. - require("quicker.treesitter").attach(qf_list.qfbufnr, info.empty_regions) require("quicker.treesitter").attach(qf_list.qfbufnr, info.regions) end @@ -433,7 +429,6 @@ end ---@field start_idx integer ---@field end_idx integer ---@field regions table ----@field empty_regions table ---@field ft table ---@field previous_item QuickFixItem ---@field winid integer @@ -600,7 +595,6 @@ function M.quickfixtextfunc(info) -- If we just rendered the last item, add highlights if info.end_idx == #items then info.regions = {} - info.empty_regions = {} info.ft = {} schedule_highlights(info) From ab48e858a63a8e664ecbc44073188395e3fc2357 Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Sun, 16 Feb 2025 12:43:54 +0800 Subject: [PATCH 12/16] Revert "remove empty_regions" This reverts commit 9a7fa03f3ebc545d396577fe30b00a280fe4deaa. --- lua/quicker/display.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lua/quicker/display.lua b/lua/quicker/display.lua index a45604a..f67953d 100644 --- a/lua/quicker/display.lua +++ b/lua/quicker/display.lua @@ -247,6 +247,7 @@ local function highlight_buffer_when_entered(qfbufnr, info) info.start_idx = 1 info.end_idx = vim.api.nvim_buf_line_count(qfbufnr) info.regions = {} + info.empty_regions = {} info.ft = {} schedule_highlights(info) end, @@ -328,6 +329,7 @@ add_qf_highlights = function(info) if config.highlight.treesitter and ft then info.regions[ft] = info.regions[ft] or {} + info.empty_regions[ft] = info.empty_regions[ft] or {} local filename = vim.split(line, EM_QUAD, { plain = true })[1] -- Note: we must include the new line character, when multiple lines are treated as one @@ -384,6 +386,8 @@ add_qf_highlights = function(info) end end if config.highlight.treesitter then + -- cleanup previous regions each time we call setqflist. + require("quicker.treesitter").attach(qf_list.qfbufnr, info.empty_regions) require("quicker.treesitter").attach(qf_list.qfbufnr, info.regions) end @@ -429,6 +433,7 @@ end ---@field start_idx integer ---@field end_idx integer ---@field regions table +---@field empty_regions table ---@field ft table ---@field previous_item QuickFixItem ---@field winid integer @@ -595,6 +600,7 @@ function M.quickfixtextfunc(info) -- If we just rendered the last item, add highlights if info.end_idx == #items then info.regions = {} + info.empty_regions = {} info.ft = {} schedule_highlights(info) From a38c08aa9d21122b95caf9e3ad5560fada7f27e4 Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Sun, 16 Feb 2025 14:00:54 +0800 Subject: [PATCH 13/16] caculate regions count early --- lua/quicker/display.lua | 9 ++++++-- lua/quicker/treesitter.lua | 45 +++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/lua/quicker/display.lua b/lua/quicker/display.lua index f67953d..4ed2167 100644 --- a/lua/quicker/display.lua +++ b/lua/quicker/display.lua @@ -247,6 +247,7 @@ local function highlight_buffer_when_entered(qfbufnr, info) info.start_idx = 1 info.end_idx = vim.api.nvim_buf_line_count(qfbufnr) info.regions = {} + info.region_count = 0 info.empty_regions = {} info.ft = {} schedule_highlights(info) @@ -329,6 +330,7 @@ add_qf_highlights = function(info) if config.highlight.treesitter and ft then info.regions[ft] = info.regions[ft] or {} + info.region_count = info.region_count + 1 info.empty_regions[ft] = info.empty_regions[ft] or {} local filename = vim.split(line, EM_QUAD, { plain = true })[1] @@ -386,9 +388,10 @@ add_qf_highlights = function(info) end end if config.highlight.treesitter then + local register_cbs = info.region_count <= config.highlight.max_lines -- cleanup previous regions each time we call setqflist. - require("quicker.treesitter").attach(qf_list.qfbufnr, info.empty_regions) - require("quicker.treesitter").attach(qf_list.qfbufnr, info.regions) + require("quicker.treesitter").attach(qf_list.qfbufnr, info.empty_regions, register_cbs) + require("quicker.treesitter").attach(qf_list.qfbufnr, info.regions, register_cbs) end vim.api.nvim_buf_clear_namespace(qf_list.qfbufnr, ns, info.end_idx, -1) @@ -433,6 +436,7 @@ end ---@field start_idx integer ---@field end_idx integer ---@field regions table +---@field region_count integer ---@field empty_regions table ---@field ft table ---@field previous_item QuickFixItem @@ -600,6 +604,7 @@ function M.quickfixtextfunc(info) -- If we just rendered the last item, add highlights if info.end_idx == #items then info.regions = {} + info.region_count = 0 info.empty_regions = {} info.ft = {} schedule_highlights(info) diff --git a/lua/quicker/treesitter.lua b/lua/quicker/treesitter.lua index dd37d40..20dfc51 100644 --- a/lua/quicker/treesitter.lua +++ b/lua/quicker/treesitter.lua @@ -46,47 +46,46 @@ end ---@param buf number ---@param regions quicker.LangRegions -function M.attach(buf, regions) +---@param register_cbs boolean +function M.attach(buf, regions, register_cbs) M.setup() cache[buf] = cache[buf] or {} for lang in pairs(cache[buf]) do cache[buf][lang].enabled = regions[lang] ~= nil end - local counts = 0 - for _, region in pairs(regions) do - counts = counts + #vim.iter(region):flatten():totable() - end - for lang in pairs(regions) do - M._attach_lang(buf, lang, regions[lang], counts > config.highlight.max_lines) + M._attach_lang(buf, lang, regions[lang], register_cbs) end end ---@param buf number ---@param lang string ---@param regions quicker.LangRegions ----@param detach boolean -function M._attach_lang(buf, lang, regions, detach) +---@param register_cbs boolean +function M._attach_lang(buf, lang, regions, register_cbs) cache[buf] = cache[buf] or {} - local ok, parser - if detach then - -- This avoids registering callbacks on the languagetree. - ok, parser = pcall(vim.treesitter.languagetree.new, buf, lang) - else - ok, parser = pcall(vim.treesitter.get_parser, buf, lang) - end - if not ok then - return + if not cache[buf][lang] then + local ok, parser + if register_cbs then + ok, parser = pcall(vim.treesitter.get_parser, buf, lang) + else + ok, parser = pcall(vim.treesitter.languagetree.new, buf, lang) + end + if not ok then + return + end + parser:set_included_regions(vim.deepcopy(regions)) + cache[buf][lang] = { + parser = parser, + highlighter = TSHighlighter.new(parser), + } end - parser:set_included_regions(vim.deepcopy(regions)) - cache[buf][lang] = { - parser = parser, - highlighter = TSHighlighter.new(parser), - } cache[buf][lang].enabled = true + local parser = cache[buf][lang].parser + parser:set_included_regions(vim.deepcopy(regions)) -- Run a full parse for all included regions. There are two reasons: -- 1. When we call `vim.treesitter.get_parser`, we have not set any -- injection ranges. From 81ecc67797a7822cc8f3e512e6d925a61cb3763a Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Mon, 17 Feb 2025 02:35:56 +0800 Subject: [PATCH 14/16] HACK: manually perform an edit after parsing --- lua/quicker/treesitter.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lua/quicker/treesitter.lua b/lua/quicker/treesitter.lua index 20dfc51..c041eb8 100644 --- a/lua/quicker/treesitter.lua +++ b/lua/quicker/treesitter.lua @@ -93,6 +93,11 @@ function M._attach_lang(buf, lang, regions, register_cbs) -- which means it only parses visible areas (the on_win and on_line callback), -- so if we modify the buffer, unvisited area's state get unsynced. pcall(parser.parse, parser, true) + -- Hack: we need to manually perform an edit, otherwise delete an entire line + -- will cause all highlights to disappear. + vim.api.nvim_buf_call(buf, function() + vim.cmd([[norm! "_xu]]) + end) end return M From 532b66e57d366342862f1c9727a4823c53bb3f75 Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Wed, 5 Mar 2025 05:25:54 +0800 Subject: [PATCH 15/16] Revert "fix: EM_QUAD doesn't need a space" This reverts commit 5e9ed34e306190523f96518fbb0f93b0171688ff. --- lua/quicker/display.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lua/quicker/display.lua b/lua/quicker/display.lua index 4ed2167..7fcfb2d 100644 --- a/lua/quicker/display.lua +++ b/lua/quicker/display.lua @@ -5,7 +5,9 @@ local util = require("quicker.util") local M = {} -local EM_QUAD = " " +-- A EM_QUAD and a space, we include an extra space before the real text, because it +-- avoids strange cases such as no highlight when insert at start of qf text. +local EM_QUAD = "  " local EM_QUAD_LEN = EM_QUAD:len() M.EM_QUAD = EM_QUAD M.EM_QUAD_LEN = EM_QUAD_LEN @@ -343,7 +345,7 @@ add_qf_highlights = function(info) -- The parser will treat this as "-- a commentlocal a = 1". local region = { i - 1, - filename:len() + EM_QUAD_LEN, + filename:len() + EM_QUAD_LEN - 1, -- skip EM_QUAD but include a space i, 0, } @@ -485,7 +487,7 @@ function M.quickfixtextfunc(info) if user_data.header == "hard" then -- Header when expanded QF list local pieces = { - string.rep(b.strong_header, col_width + 1), + string.rep(b.strong_header, col_width + 2), b.strong_cross, string.rep(b.strong_header, lnum_width), } @@ -500,7 +502,7 @@ function M.quickfixtextfunc(info) elseif user_data.header == "soft" then -- Soft header when expanded QF list local pieces = { - string.rep(b.soft_header, col_width + 1), + string.rep(b.soft_header, col_width + 2), b.soft_cross, string.rep(b.soft_header, lnum_width), } From 260bfa4fcc4a488bd016929cae067f2a69854079 Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Wed, 5 Mar 2025 05:25:34 +0800 Subject: [PATCH 16/16] conceal spaces --- lua/quicker/config.lua | 1 + lua/quicker/display.lua | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lua/quicker/config.lua b/lua/quicker/config.lua index 67310d0..424b50f 100644 --- a/lua/quicker/config.lua +++ b/lua/quicker/config.lua @@ -3,6 +3,7 @@ local default_config = { opts = { buflisted = false, number = false, + concealcursor = "nvic", relativenumber = false, signcolumn = "auto", winfixheight = true, diff --git a/lua/quicker/display.lua b/lua/quicker/display.lua index 7fcfb2d..f603d02 100644 --- a/lua/quicker/display.lua +++ b/lua/quicker/display.lua @@ -487,7 +487,7 @@ function M.quickfixtextfunc(info) if user_data.header == "hard" then -- Header when expanded QF list local pieces = { - string.rep(b.strong_header, col_width + 2), + string.rep(b.strong_header, col_width + 1), b.strong_cross, string.rep(b.strong_header, lnum_width), } @@ -502,7 +502,7 @@ function M.quickfixtextfunc(info) elseif user_data.header == "soft" then -- Soft header when expanded QF list local pieces = { - string.rep(b.soft_header, col_width + 2), + string.rep(b.soft_header, col_width + 1), b.soft_cross, string.rep(b.soft_header, lnum_width), } @@ -578,6 +578,10 @@ function M.quickfixtextfunc(info) virt_text_pos = "inline", invalidate = true, }) + vim.api.nvim_buf_set_extmark(qf_list.qfbufnr, ns, lnum - 1, end_col, { + end_col = end_col + EM_QUAD_LEN - 1, + conceal = "", + }) idmap[id] = lnum -- Highlight the filename @@ -598,6 +602,10 @@ function M.quickfixtextfunc(info) virt_lines = { header }, virt_lines_above = true, }) + -- vim.api.nvim_buf_set_extmark(qf_list.qfbufnr, ns, lnum - 1, end_col, { + -- end_col = end_col + EM_QUAD_LEN - 1, + -- conceal = "", + -- }) end end end