Skip to content

Commit

Permalink
feat(treesitter): use ts highlights in main fzf win
Browse files Browse the repository at this point in the history
  • Loading branch information
ibhagwan committed Nov 25, 2024
1 parent ce97847 commit 121883e
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 2 deletions.
1 change: 1 addition & 0 deletions lua/fzf-lua/defaults.lua
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ M.defaults.grep = {
-- live_grep_glob options
glob_flag = "--iglob", -- for case sensitive globs use '--glob'
glob_separator = "%s%-%-", -- query separator pattern (lua): ' --'
-- winopts = { treesitter = true },
}

M.defaults.args = {
Expand Down
141 changes: 139 additions & 2 deletions lua/fzf-lua/win.lua
Original file line number Diff line number Diff line change
@@ -1,10 +1,92 @@
local path = require "fzf-lua.path"
local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config"
local actions = require "fzf-lua.actions"

local api = vim.api
local fn = vim.fn

local TSInjector = {}

---@type table<number, table<string,{parser: vim.treesitter.LanguageTree, highlighter:vim.treesitter.highlighter, enabled:boolean}>>
TSInjector.cache = {}

function TSInjector.setup()
if TSInjector.did_setup then return true end
TSInjector.did_setup = true
TSInjector.__ts_highlighter = vim.treesitter.highlighter

local function wrap_ts_hl_callback(name)
return function(_, win, buf, ...)
if not TSInjector.cache[buf] then
return false
end
for _, hl in pairs(TSInjector.cache[buf] or {}) do
if hl.enabled then
TSInjector.__ts_highlighter.active[buf] = hl.highlighter
TSInjector.__ts_highlighter[name](_, win, buf, ...)
end
end
TSInjector.__ts_highlighter.active[buf] = nil
end
end

vim.api.nvim_set_decoration_provider(
vim.api.nvim_create_namespace("fzf-lua.win.highlighter"),
{
on_win = wrap_ts_hl_callback("_on_win"),
on_line = wrap_ts_hl_callback("_on_line"),
})

return true
end

function TSInjector.clear_cache(buf)
print("buf wipe, cache for", buf, TSInjector.cache[buf])
TSInjector.cache[buf] = nil
for bufnr, _ in pairs(TSInjector.cache) do
print("still have cache for buf", bufnr)
assert(bufnr)
end
end

---@param buf number
function TSInjector.attach(buf, regions)
if not TSInjector.setup() then return end
TSInjector.cache[buf] = TSInjector.cache[buf] or {}
for lang, _ in pairs(TSInjector.cache[buf]) do
TSInjector.cache[buf][lang].enabled = regions[lang] ~= nil
end

for lang, _ in pairs(regions) do
TSInjector._attach_lang(buf, lang, regions[lang])
end
end

---@param buf number
---@param lang? string
function TSInjector._attach_lang(buf, lang, regions)
lang = lang or "markdown"
lang = lang == "markdown" and "markdown_inline" or lang

TSInjector.cache[buf] = TSInjector.cache[buf] or {}

if not TSInjector.cache[buf][lang] then
local ok, parser = pcall(vim.treesitter.languagetree.new, buf, lang)
if not ok then return end

parser:set_included_regions(regions)
TSInjector.cache[buf][lang] = {
parser = parser,
highlighter = TSInjector.__ts_highlighter.new(parser),
}
end

TSInjector.cache[buf][lang].enabled = true
local parser = TSInjector.cache[buf][lang].parser
parser:set_included_regions(regions)
end

local FzfWin = {}

-- singleton instance used in win_leave
Expand Down Expand Up @@ -738,6 +820,54 @@ function FzfWin:set_winleave_autocmd()
self:_nvim_create_autocmd("WinLeave", self.win_leave, [[require('fzf-lua.win').win_leave()]])
end

function FzfWin:treesitter_attach()
if not utils.__HAS_NVIM_09 then return end
-- if not self._o.winopts.treesitter then return end
local function trim(s) return (string.gsub(s, "^%s*(.-)%s*$", "%1")) end
vim.api.nvim_buf_attach(self.fzf_bufnr, false, {
on_lines = function(_, bufnr, _, first_changed, last_changed, last_updated, bc)
local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
local regions = {}
for i, line in ipairs(lines) do
(function()
-- Lines with code can be of the following formats:
-- file:line:col:text (grep_xxx)
-- file:line:text (grep_project or missing "--column" flag)
-- line:col:text (grep_curbuf)
-- line:text (blines)
local filepath, _lnum, text = line:match("(.-):?(%d+):(.+)$")
if not text or text == 0 then return end

filepath = trim(filepath)
local ft = #filepath == 0 and vim.bo[utils.CTX().bufnr].ft
or vim.filetype.match({ filename = path.tail(filepath) })
if not ft then return end

local lang = vim.treesitter.language.get_lang(ft)
local loaded = lang and utils.has_ts_parser(lang)
if not loaded then return end

-- With the above line match text can start with "%d+:", remove it
text = text:gsub("^%d+:", "")

local line_idx, text_pos = i - 1, #line - #text - 1
regions[lang] = regions[lang] or {}
table.insert(regions[lang], { { line_idx, text_pos, line_idx, line:len() } })
print(path.tail(filepath), ":", lang, line_idx, text_pos, "text:", text)
end)()
end
-- _G.dump(regions)
TSInjector.attach(bufnr, regions)
end
})
-- Clear TSInjector cache
vim.api.nvim_create_autocmd("BufWipeout", {
group = vim.api.nvim_create_augroup("fzf-lua.treesitter.hl", { clear = true }),
buffer = self.fzf_bufnr,
callback = function(ev) TSInjector.clear_cache(ev.buf) end,
})
end

function FzfWin:set_tmp_buffer(no_wipe)
if not self:validate() then return end
-- Store the [would be] detached buffer number
Expand All @@ -749,11 +879,16 @@ function FzfWin:set_tmp_buffer(no_wipe)
vim.api.nvim_win_set_buf(self.fzf_winid, self.fzf_bufnr)
-- close the previous fzf term buffer without triggering autocmds
-- this also kills the previous fzf process if its still running
if not no_wipe then utils.nvim_buf_delete(detached, { force = true }) end
if not no_wipe then
utils.nvim_buf_delete(detached, { force = true })
TSInjector.clear_cache(detached)
end
-- in case buffer exists prematurely
self:set_winleave_autocmd()
-- automatically resize fzf window
self:set_redraw_autocmd()
-- Use treesitter to highlight results on the main fzf window
self:treesitter_attach()
-- since we have the cursorline workaround from
-- issue #254, resume shows an ugly cursorline.
-- remove it, nvim_win API is better than vim.wo?
Expand Down Expand Up @@ -795,7 +930,7 @@ function FzfWin:create()
-- also recall the user's 'on_create' (#394)
if self.winopts.on_create and
type(self.winopts.on_create) == "function" then
self.winopts.on_create()
self.winopts.on_create({ winid = self.fzf_winid, bufnr = self.fzf_bufnr })
end
-- not sure why but when using a split and reusing the window,
-- fzf will not use all the available width until 'redraw' is
Expand Down Expand Up @@ -842,6 +977,8 @@ function FzfWin:create()
self:set_winleave_autocmd()
-- automatically resize fzf window
self:set_redraw_autocmd()
-- Use treesitter to highlight results on the main fzf window
self:treesitter_attach()

self:reset_win_highlights(self.fzf_winid)

Expand Down

0 comments on commit 121883e

Please sign in to comment.