feat: add the entire plugin
This commit is contained in:
parent
7eade50d05
commit
21b8cfb470
7 changed files with 382 additions and 5 deletions
41
lua/fugitive-ts/health.lua
Normal file
41
lua/fugitive-ts/health.lua
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
local M = {}
|
||||
|
||||
function M.check()
|
||||
vim.health.start('fugitive-ts.nvim')
|
||||
|
||||
if vim.fn.has('nvim-0.9.0') == 1 then
|
||||
vim.health.ok('Neovim 0.9.0+ detected')
|
||||
else
|
||||
vim.health.error('fugitive-ts.nvim requires Neovim 0.9.0+')
|
||||
end
|
||||
|
||||
local fugitive_loaded = vim.fn.exists(':Git') == 2
|
||||
if fugitive_loaded then
|
||||
vim.health.ok('vim-fugitive detected')
|
||||
else
|
||||
vim.health.warn('vim-fugitive not detected (required for this plugin to be useful)')
|
||||
end
|
||||
|
||||
local common_langs = { 'lua', 'python', 'javascript', 'typescript', 'rust', 'go', 'c', 'cpp' }
|
||||
local available = {}
|
||||
local missing = {}
|
||||
|
||||
for _, lang in ipairs(common_langs) do
|
||||
local ok = pcall(vim.treesitter.language.inspect, lang)
|
||||
if ok then
|
||||
table.insert(available, lang)
|
||||
else
|
||||
table.insert(missing, lang)
|
||||
end
|
||||
end
|
||||
|
||||
if #available > 0 then
|
||||
vim.health.ok('Treesitter parsers available: ' .. table.concat(available, ', '))
|
||||
end
|
||||
|
||||
if #missing > 0 then
|
||||
vim.health.info('Treesitter parsers not installed: ' .. table.concat(missing, ', '))
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
52
lua/fugitive-ts/highlight.lua
Normal file
52
lua/fugitive-ts/highlight.lua
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
local M = {}
|
||||
|
||||
function M.highlight_hunk(bufnr, ns, hunk)
|
||||
local lang = hunk.lang
|
||||
if not lang then
|
||||
return
|
||||
end
|
||||
|
||||
local code_lines = {}
|
||||
for _, line in ipairs(hunk.lines) do
|
||||
table.insert(code_lines, line:sub(2))
|
||||
end
|
||||
|
||||
local code = table.concat(code_lines, '\n')
|
||||
if code == '' then
|
||||
return
|
||||
end
|
||||
|
||||
local ok, parser_obj = pcall(vim.treesitter.get_string_parser, code, lang)
|
||||
if not ok or not parser_obj then
|
||||
return
|
||||
end
|
||||
|
||||
local trees = parser_obj:parse()
|
||||
if not trees or #trees == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local query = vim.treesitter.query.get(lang, 'highlights')
|
||||
if not query then
|
||||
return
|
||||
end
|
||||
|
||||
for id, node, _ in query:iter_captures(trees[1]:root(), code) do
|
||||
local capture_name = '@' .. query.captures[id]
|
||||
local sr, sc, er, ec = node:range()
|
||||
|
||||
local buf_sr = hunk.start_line + sr
|
||||
local buf_er = hunk.start_line + er
|
||||
local buf_sc = sc + 1
|
||||
local buf_ec = ec + 1
|
||||
|
||||
pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_sr, buf_sc, {
|
||||
end_row = buf_er,
|
||||
end_col = buf_ec,
|
||||
hl_group = capture_name,
|
||||
priority = 200,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
109
lua/fugitive-ts/init.lua
Normal file
109
lua/fugitive-ts/init.lua
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
---@class fugitive-ts.Config
|
||||
---@field enabled boolean
|
||||
---@field languages table<string, string>
|
||||
---@field debounce_ms integer
|
||||
|
||||
---@class fugitive-ts
|
||||
---@field attach fun(bufnr?: integer)
|
||||
---@field refresh fun(bufnr?: integer)
|
||||
---@field setup fun(opts?: fugitive-ts.Config)
|
||||
local M = {}
|
||||
|
||||
local highlight = require('fugitive-ts.highlight')
|
||||
local parser = require('fugitive-ts.parser')
|
||||
|
||||
local ns = vim.api.nvim_create_namespace('fugitive_ts')
|
||||
|
||||
---@type fugitive-ts.Config
|
||||
local default_config = {
|
||||
enabled = true,
|
||||
languages = {},
|
||||
debounce_ms = 50,
|
||||
}
|
||||
|
||||
---@type fugitive-ts.Config
|
||||
local config = vim.deepcopy(default_config)
|
||||
|
||||
---@type table<integer, boolean>
|
||||
local attached_buffers = {}
|
||||
|
||||
---@param bufnr integer
|
||||
local function highlight_buffer(bufnr)
|
||||
if not config.enabled then
|
||||
return
|
||||
end
|
||||
|
||||
if not vim.api.nvim_buf_is_valid(bufnr) then
|
||||
return
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
|
||||
|
||||
local hunks = parser.parse_buffer(bufnr, config.languages)
|
||||
for _, hunk in ipairs(hunks) do
|
||||
highlight.highlight_hunk(bufnr, ns, hunk)
|
||||
end
|
||||
end
|
||||
|
||||
---@param bufnr integer
|
||||
---@return fun()
|
||||
local function create_debounced_highlight(bufnr)
|
||||
---@type uv_timer_t?
|
||||
local timer = nil
|
||||
return function()
|
||||
if timer then
|
||||
timer:stop()
|
||||
timer:close()
|
||||
end
|
||||
timer = vim.uv.new_timer()
|
||||
timer:start(
|
||||
config.debounce_ms,
|
||||
0,
|
||||
vim.schedule_wrap(function()
|
||||
timer:close()
|
||||
timer = nil
|
||||
highlight_buffer(bufnr)
|
||||
end)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
---@param bufnr? integer
|
||||
function M.attach(bufnr)
|
||||
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
||||
|
||||
if attached_buffers[bufnr] then
|
||||
return
|
||||
end
|
||||
attached_buffers[bufnr] = true
|
||||
|
||||
local debounced = create_debounced_highlight(bufnr)
|
||||
|
||||
highlight_buffer(bufnr)
|
||||
|
||||
vim.api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI' }, {
|
||||
buffer = bufnr,
|
||||
callback = debounced,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd('BufWipeout', {
|
||||
buffer = bufnr,
|
||||
callback = function()
|
||||
attached_buffers[bufnr] = nil
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
---@param bufnr? integer
|
||||
function M.refresh(bufnr)
|
||||
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
||||
highlight_buffer(bufnr)
|
||||
end
|
||||
|
||||
---@param opts? fugitive-ts.Config
|
||||
function M.setup(opts)
|
||||
opts = opts or {}
|
||||
config = vim.tbl_deep_extend('force', default_config, opts)
|
||||
end
|
||||
|
||||
return M
|
||||
89
lua/fugitive-ts/parser.lua
Normal file
89
lua/fugitive-ts/parser.lua
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
---@class fugitive-ts.Hunk
|
||||
---@field filename string
|
||||
---@field lang string
|
||||
---@field start_line integer
|
||||
---@field lines string[]
|
||||
|
||||
local M = {}
|
||||
|
||||
---@param filename string
|
||||
---@param custom_langs? table<string, string>
|
||||
---@return string?
|
||||
local function get_lang_from_filename(filename, custom_langs)
|
||||
if custom_langs and custom_langs[filename] then
|
||||
return custom_langs[filename]
|
||||
end
|
||||
|
||||
local ft = vim.filetype.match({ filename = filename })
|
||||
if not ft then
|
||||
return nil
|
||||
end
|
||||
|
||||
local lang = vim.treesitter.language.get_lang(ft)
|
||||
if lang then
|
||||
local ok = pcall(vim.treesitter.language.inspect, lang)
|
||||
if ok then
|
||||
return lang
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
---@param bufnr integer
|
||||
---@param custom_langs? table<string, string>
|
||||
---@return fugitive-ts.Hunk[]
|
||||
function M.parse_buffer(bufnr, custom_langs)
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
---@type fugitive-ts.Hunk[]
|
||||
local hunks = {}
|
||||
|
||||
---@type string?
|
||||
local current_filename = nil
|
||||
---@type string?
|
||||
local current_lang = nil
|
||||
---@type integer?
|
||||
local hunk_start = nil
|
||||
---@type string[]
|
||||
local hunk_lines = {}
|
||||
|
||||
local function flush_hunk()
|
||||
if hunk_start and #hunk_lines > 0 and current_lang then
|
||||
table.insert(hunks, {
|
||||
filename = current_filename,
|
||||
lang = current_lang,
|
||||
start_line = hunk_start,
|
||||
lines = hunk_lines,
|
||||
})
|
||||
end
|
||||
hunk_start = nil
|
||||
hunk_lines = {}
|
||||
end
|
||||
|
||||
for i, line in ipairs(lines) do
|
||||
local filename = line:match('^[MADRC%?!]%s+(.+)$')
|
||||
if filename then
|
||||
flush_hunk()
|
||||
current_filename = filename
|
||||
current_lang = get_lang_from_filename(filename, custom_langs)
|
||||
elseif line:match('^@@.-@@') then
|
||||
flush_hunk()
|
||||
hunk_start = i
|
||||
elseif hunk_start then
|
||||
local prefix = line:sub(1, 1)
|
||||
if prefix == ' ' or prefix == '+' or prefix == '-' then
|
||||
table.insert(hunk_lines, line)
|
||||
elseif line == '' or line:match('^[MADRC%?!]%s+') or line:match('^%a') then
|
||||
flush_hunk()
|
||||
current_filename = nil
|
||||
current_lang = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
flush_hunk()
|
||||
|
||||
return hunks
|
||||
end
|
||||
|
||||
return M
|
||||
Loading…
Add table
Add a link
Reference in a new issue