1+ local path = require " fzf-lua.path"
12local utils = require " fzf-lua.utils"
23local config = require " fzf-lua.config"
34local actions = require " fzf-lua.actions"
45
56local api = vim .api
67local fn = vim .fn
78
9+ local TSInjector = {}
10+
11+ --- @type table<number , table<string ,{ parser : vim.treesitter.LanguageTree , highlighter : vim.treesitter.highlighter , enabled : boolean } >>
12+ TSInjector .cache = {}
13+
14+ function TSInjector .setup ()
15+ if TSInjector ._setup then return true end
16+
17+ TSInjector ._setup = true
18+ TSInjector ._ns = TSInjector ._ns or vim .api .nvim_create_namespace (" fzf-lua.win.highlighter" )
19+
20+ local function wrap_ts_hl_callback (name )
21+ return function (_ , win , buf , ...)
22+ -- print(name, buf, win, TSInjector.cache[buf])
23+ if not TSInjector .cache [buf ] then
24+ return false
25+ end
26+ for _ , hl in pairs (TSInjector .cache [buf ] or {}) do
27+ if hl .enabled then
28+ vim .treesitter .highlighter .active [buf ] = hl .highlighter
29+ vim .treesitter .highlighter [name ](_ , win , buf , ... )
30+ end
31+ end
32+ vim .treesitter .highlighter .active [buf ] = nil
33+ end
34+ end
35+
36+ vim .api .nvim_set_decoration_provider (TSInjector ._ns , {
37+ on_win = wrap_ts_hl_callback (" _on_win" ),
38+ on_line = wrap_ts_hl_callback (" _on_line" ),
39+ })
40+
41+ return true
42+ end
43+
44+ function TSInjector .deregister ()
45+ if not TSInjector ._ns then return end
46+ vim .api .nvim_set_decoration_provider (TSInjector ._ns , { on_win = nil , on_line = nil })
47+ TSInjector ._setup = nil
48+ end
49+
50+ function TSInjector .clear_cache (buf , noassert )
51+ TSInjector .cache [buf ] = nil
52+ assert (noassert or utils .tbl_isempty (TSInjector .cache ))
53+ end
54+
55+ --- @param buf number
56+ function TSInjector .attach (buf , regions )
57+ if not TSInjector .setup () then return end
58+
59+ TSInjector .cache [buf ] = TSInjector .cache [buf ] or {}
60+ for lang , _ in pairs (TSInjector .cache [buf ]) do
61+ TSInjector .cache [buf ][lang ].enabled = regions [lang ] ~= nil
62+ end
63+
64+ for lang , _ in pairs (regions ) do
65+ TSInjector ._attach_lang (buf , lang , regions [lang ])
66+ end
67+ end
68+
69+ --- @param buf number
70+ --- @param lang ? string
71+ function TSInjector ._attach_lang (buf , lang , regions )
72+ if not TSInjector .cache [buf ][lang ] then
73+ local ok , parser = pcall (vim .treesitter .languagetree .new , buf , lang )
74+ if not ok then return end
75+ TSInjector .cache [buf ][lang ] = {
76+ parser = parser ,
77+ highlighter = vim .treesitter .highlighter .new (parser ),
78+ }
79+ end
80+
81+ local parser = TSInjector .cache [buf ][lang ].parser
82+ if not parser then return end
83+
84+ TSInjector .cache [buf ][lang ].enabled = true
85+ parser :set_included_regions (regions )
86+ end
87+
888local FzfWin = {}
989
1090-- singleton instance used in win_leave
@@ -738,6 +818,50 @@ function FzfWin:set_winleave_autocmd()
738818 self :_nvim_create_autocmd (" WinLeave" , self .win_leave , [[ require('fzf-lua.win').win_leave()]] )
739819end
740820
821+ function FzfWin :treesitter_attach ()
822+ if not utils .__HAS_NVIM_09 then return end
823+ if not self ._o .winopts .treesitter then return end
824+ local function trim (s ) return (string.gsub (s , " ^%s*(.-)%s*$" , " %1" )) end
825+ vim .api .nvim_buf_attach (self .fzf_bufnr , false , {
826+ on_lines = function (_ , bufnr , _ , first_changed , last_changed , last_updated , bc )
827+ local lines = api .nvim_buf_get_lines (bufnr , 0 , - 1 , false )
828+ local regions = {}
829+ local empty_regions = {}
830+ for i , line in ipairs (lines ) do
831+ (function ()
832+ -- Lines with code can be of the following formats:
833+ -- file:line:col:text (grep_xxx)
834+ -- file:line:text (grep_project or missing "--column" flag)
835+ -- line:col:text (grep_curbuf)
836+ -- line:text (blines)
837+ local filepath , _lnum , text = line :match (" (.-):?(%d+):(.+)$" )
838+ if not text or text == 0 then return end
839+
840+ filepath = trim (filepath )
841+ local ft = # filepath == 0 and vim .bo [utils .CTX ().bufnr ].ft
842+ or vim .filetype .match ({ filename = path .tail (filepath ) })
843+ if not ft then return end
844+
845+ local lang = vim .treesitter .language .get_lang (ft )
846+ local loaded = lang and utils .has_ts_parser (lang )
847+ if not loaded then return end
848+
849+ -- With the above line match text can start with "%d+:", remove it
850+ text = text :gsub (" ^%d+:" , " " )
851+
852+ local line_idx , text_pos = i - 1 , # line - # text
853+ regions [lang ] = regions [lang ] or {}
854+ empty_regions [lang ] = empty_regions [lang ] or {}
855+ table.insert (regions [lang ], { { line_idx , text_pos , line_idx , line :len () } })
856+ -- print(lang, string.format("[%d]%d:%s", line_idx, _lnum, line:sub(text_pos + 1)))
857+ end )()
858+ end
859+ TSInjector .attach (bufnr , empty_regions )
860+ TSInjector .attach (bufnr , regions )
861+ end
862+ })
863+ end
864+
741865function FzfWin :set_tmp_buffer (no_wipe )
742866 if not self :validate () then return end
743867 -- Store the [would be] detached buffer number
@@ -749,11 +873,16 @@ function FzfWin:set_tmp_buffer(no_wipe)
749873 vim .api .nvim_win_set_buf (self .fzf_winid , self .fzf_bufnr )
750874 -- close the previous fzf term buffer without triggering autocmds
751875 -- this also kills the previous fzf process if its still running
752- if not no_wipe then utils .nvim_buf_delete (detached , { force = true }) end
876+ if not no_wipe then
877+ utils .nvim_buf_delete (detached , { force = true })
878+ TSInjector .clear_cache (detached )
879+ end
753880 -- in case buffer exists prematurely
754881 self :set_winleave_autocmd ()
755882 -- automatically resize fzf window
756883 self :set_redraw_autocmd ()
884+ -- Use treesitter to highlight results on the main fzf window
885+ self :treesitter_attach ()
757886 -- since we have the cursorline workaround from
758887 -- issue #254, resume shows an ugly cursorline.
759888 -- remove it, nvim_win API is better than vim.wo?
@@ -795,7 +924,7 @@ function FzfWin:create()
795924 -- also recall the user's 'on_create' (#394)
796925 if self .winopts .on_create and
797926 type (self .winopts .on_create ) == " function" then
798- self .winopts .on_create ()
927+ self .winopts .on_create ({ winid = self . fzf_winid , bufnr = self . fzf_bufnr } )
799928 end
800929 -- not sure why but when using a split and reusing the window,
801930 -- fzf will not use all the available width until 'redraw' is
@@ -842,6 +971,8 @@ function FzfWin:create()
842971 self :set_winleave_autocmd ()
843972 -- automatically resize fzf window
844973 self :set_redraw_autocmd ()
974+ -- Use treesitter to highlight results on the main fzf window
975+ self :treesitter_attach ()
845976
846977 self :reset_win_highlights (self .fzf_winid )
847978
@@ -916,6 +1047,9 @@ function FzfWin:close(fzf_bufnr)
9161047 if self .fzf_bufnr and vim .api .nvim_buf_is_valid (self .fzf_bufnr ) then
9171048 vim .api .nvim_buf_delete (self .fzf_bufnr , { force = true })
9181049 end
1050+ -- Clear treesitter buffer cache and deregister decoration callbacks
1051+ TSInjector .clear_cache (self .fzf_bufnr , self ._hidden_fzf_bufnr )
1052+ TSInjector .deregister ()
9191053 -- when using `split = "belowright new"` closing the fzf
9201054 -- window may not always return to the correct source win
9211055 -- depending on the user's split configuration (#397)
0 commit comments