diff --git a/doc/fugitive-ts.nvim.txt b/doc/fugitive-ts.nvim.txt index d7a56fb..6250d2e 100644 --- a/doc/fugitive-ts.nvim.txt +++ b/doc/fugitive-ts.nvim.txt @@ -42,45 +42,51 @@ CONFIGURATION *fugitive-ts-config* Enable debug logging to |:messages| with `[fugitive-ts]` prefix. - {languages} (table, default: {}) - Custom filename to treesitter language mappings. - Useful for non-standard file extensions. - Example: >lua - languages = { - ['.envrc'] = 'bash', - ['Justfile'] = 'just', - } -< - {disabled_languages} (string[], default: {}) - Treesitter language names to skip highlighting. - Example: >lua - disabled_languages = { 'markdown', 'text' } -< {debounce_ms} (integer, default: 0) Debounce delay in milliseconds for re-highlighting after buffer changes. Lower values feel snappier but use more CPU. - {max_lines_per_hunk} (integer, default: 500) - Skip treesitter highlighting for hunks larger than - this many lines. Prevents lag on massive diffs. - - {hide_prefix} (boolean, default: true) + {hide_prefix} (boolean, default: false) Hide diff prefixes (`+`/`-`/` `) using virtual text overlay. Makes code appear without the leading diff character. When `highlights.background` is also enabled, the overlay inherits the line's background color. + {treesitter} (table, default: see below) + Treesitter highlighting options. + See |fugitive-ts.TreesitterConfig| for fields. + + {vim} (table, default: see below) + Vim syntax highlighting options (experimental). + See |fugitive-ts.VimConfig| for fields. + {highlights} (table, default: see below) Controls which highlight features are enabled. See |fugitive-ts.Highlights| for fields. - *fugitive-ts.Highlights* - Highlights table fields: ~ - {treesitter} (boolean, default: true) + *fugitive-ts.TreesitterConfig* + Treesitter config fields: ~ + {enabled} (boolean, default: true) Apply treesitter syntax highlighting to code. + {max_lines} (integer, default: 500) + Skip treesitter highlighting for hunks larger than + this many lines. Prevents lag on massive diffs. + + *fugitive-ts.VimConfig* + Vim config fields: ~ + {enabled} (boolean, default: false) + Experimental: Use vim syntax highlighting as + fallback when no treesitter parser is available. + + {max_lines} (integer, default: 200) + Skip vim syntax highlighting for hunks larger than + this many lines. + + *fugitive-ts.Highlights* + Highlights table fields: ~ {background} (boolean, default: true) Apply background highlighting to `+`/`-` lines using `FugitiveTsAdd`/`FugitiveTsDelete` groups @@ -90,13 +96,14 @@ CONFIGURATION *fugitive-ts-config* Highlight line numbers with matching colors. Only visible if line numbers are enabled. - {vim} (boolean, default: false) - Experimental: Use vim syntax highlighting as - fallback when no treesitter parser is available. - Note: Header context (e.g., `@@ -10,3 +10,4 @@ func()`) is always highlighted with treesitter when a parser is available. + Language detection uses Neovim's built-in |vim.filetype.match()| and + |vim.treesitter.language.get_lang()|. To customize filetype detection + or register treesitter parsers for custom filetypes, use + |vim.filetype.add()| and |vim.treesitter.language.register()|. + ============================================================================== API *fugitive-ts-api* diff --git a/lua/fugitive-ts/highlight.lua b/lua/fugitive-ts/highlight.lua index 755c983..b5adb88 100644 --- a/lua/fugitive-ts/highlight.lua +++ b/lua/fugitive-ts/highlight.lua @@ -65,8 +65,9 @@ local function highlight_text(bufnr, ns, hunk, col_offset, text, lang) end ---@class fugitive-ts.HunkOpts ----@field max_lines integer ---@field hide_prefix boolean +---@field treesitter fugitive-ts.TreesitterConfig +---@field vim fugitive-ts.VimConfig ---@field highlights fugitive-ts.Highlights ---@param bufnr integer @@ -79,13 +80,14 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) return end - if #hunk.lines > opts.max_lines then + local max_lines = opts.treesitter.max_lines + if #hunk.lines > max_lines then dbg( 'skipping hunk %s:%d (%d lines > %d max)', hunk.filename, hunk.start_line, #hunk.lines, - opts.max_lines + max_lines ) return end @@ -120,7 +122,7 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, extmark_opts) end - if line_len > 1 and opts.highlights.treesitter then + if line_len > 1 and opts.treesitter.enabled then pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 1, { end_col = line_len, hl_group = 'Normal', @@ -129,7 +131,7 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) end end - if not opts.highlights.treesitter then + if not opts.treesitter.enabled then return end diff --git a/lua/fugitive-ts/init.lua b/lua/fugitive-ts/init.lua index b71ae24..7f2c037 100644 --- a/lua/fugitive-ts/init.lua +++ b/lua/fugitive-ts/init.lua @@ -1,17 +1,22 @@ ---@class fugitive-ts.Highlights ----@field treesitter boolean ---@field background boolean ---@field gutter boolean ----@field vim boolean + +---@class fugitive-ts.TreesitterConfig +---@field enabled boolean +---@field max_lines integer + +---@class fugitive-ts.VimConfig +---@field enabled boolean +---@field max_lines integer ---@class fugitive-ts.Config ---@field enabled boolean ---@field debug boolean ----@field languages table ----@field disabled_languages string[] ---@field debounce_ms integer ----@field max_lines_per_hunk integer ---@field hide_prefix boolean +---@field treesitter fugitive-ts.TreesitterConfig +---@field vim fugitive-ts.VimConfig ---@field highlights fugitive-ts.Highlights ---@class fugitive-ts @@ -61,16 +66,19 @@ end local default_config = { enabled = true, debug = false, - languages = {}, - disabled_languages = {}, debounce_ms = 0, - max_lines_per_hunk = 500, hide_prefix = false, + treesitter = { + enabled = true, + max_lines = 500, + }, + vim = { + enabled = false, + max_lines = 200, + }, highlights = { - treesitter = true, background = true, gutter = true, - vim = false, }, } @@ -102,12 +110,13 @@ local function highlight_buffer(bufnr) vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) - local hunks = parser.parse_buffer(bufnr, config.languages, config.disabled_languages) + local hunks = parser.parse_buffer(bufnr) dbg('found %d hunks in buffer %d', #hunks, bufnr) for _, hunk in ipairs(hunks) do highlight.highlight_hunk(bufnr, ns, hunk, { - max_lines = config.max_lines_per_hunk, hide_prefix = config.hide_prefix, + treesitter = config.treesitter, + vim = config.vim, highlights = config.highlights, }) end @@ -192,20 +201,31 @@ function M.setup(opts) vim.validate({ enabled = { opts.enabled, 'boolean', true }, debug = { opts.debug, 'boolean', true }, - languages = { opts.languages, 'table', true }, - disabled_languages = { opts.disabled_languages, 'table', true }, debounce_ms = { opts.debounce_ms, 'number', true }, - max_lines_per_hunk = { opts.max_lines_per_hunk, 'number', true }, hide_prefix = { opts.hide_prefix, 'boolean', true }, + treesitter = { opts.treesitter, 'table', true }, + vim = { opts.vim, 'table', true }, highlights = { opts.highlights, 'table', true }, }) + if opts.treesitter then + vim.validate({ + ['treesitter.enabled'] = { opts.treesitter.enabled, 'boolean', true }, + ['treesitter.max_lines'] = { opts.treesitter.max_lines, 'number', true }, + }) + end + + if opts.vim then + vim.validate({ + ['vim.enabled'] = { opts.vim.enabled, 'boolean', true }, + ['vim.max_lines'] = { opts.vim.max_lines, 'number', true }, + }) + end + if opts.highlights then vim.validate({ - ['highlights.treesitter'] = { opts.highlights.treesitter, 'boolean', true }, ['highlights.background'] = { opts.highlights.background, 'boolean', true }, ['highlights.gutter'] = { opts.highlights.gutter, 'boolean', true }, - ['highlights.vim'] = { opts.highlights.vim, 'boolean', true }, }) end diff --git a/lua/fugitive-ts/parser.lua b/lua/fugitive-ts/parser.lua index fa0bd16..3bf9346 100644 --- a/lua/fugitive-ts/parser.lua +++ b/lua/fugitive-ts/parser.lua @@ -26,19 +26,8 @@ local function dbg(msg, ...) end ---@param filename string ----@param custom_langs? table ----@param disabled_langs? string[] ---@return string? -local function get_lang_from_filename(filename, custom_langs, disabled_langs) - if custom_langs and custom_langs[filename] then - local lang = custom_langs[filename] - if disabled_langs and vim.tbl_contains(disabled_langs, lang) then - dbg('lang disabled: %s', lang) - return nil - end - return lang - end - +local function get_lang_from_filename(filename) local ft = vim.filetype.match({ filename = filename }) if not ft then dbg('no filetype for: %s', filename) @@ -47,10 +36,6 @@ local function get_lang_from_filename(filename, custom_langs, disabled_langs) local lang = vim.treesitter.language.get_lang(ft) if lang then - if disabled_langs and vim.tbl_contains(disabled_langs, lang) then - dbg('lang disabled: %s', lang) - return nil - end local ok = pcall(vim.treesitter.language.inspect, lang) if ok then return lang @@ -64,10 +49,8 @@ local function get_lang_from_filename(filename, custom_langs, disabled_langs) end ---@param bufnr integer ----@param custom_langs? table ----@param disabled_langs? string[] ---@return fugitive-ts.Hunk[] -function M.parse_buffer(bufnr, custom_langs, disabled_langs) +function M.parse_buffer(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) ---@type fugitive-ts.Hunk[] local hunks = {} @@ -107,7 +90,7 @@ function M.parse_buffer(bufnr, custom_langs, disabled_langs) if filename then flush_hunk() current_filename = filename - current_lang = get_lang_from_filename(filename, custom_langs, disabled_langs) + current_lang = get_lang_from_filename(filename) if current_lang then dbg('file: %s -> lang: %s', filename, current_lang) end diff --git a/spec/init_spec.lua b/spec/init_spec.lua index ce5b64e..5268b4a 100644 --- a/spec/init_spec.lua +++ b/spec/init_spec.lua @@ -20,16 +20,19 @@ describe('fugitive-ts', function() fugitive_ts.setup({ enabled = false, debug = true, - languages = { ['.envrc'] = 'bash' }, - disabled_languages = { 'markdown' }, debounce_ms = 100, - max_lines_per_hunk = 1000, hide_prefix = false, + treesitter = { + enabled = true, + max_lines = 1000, + }, + vim = { + enabled = false, + max_lines = 200, + }, highlights = { - treesitter = true, background = true, gutter = true, - vim = false, }, }) end) diff --git a/spec/parser_spec.lua b/spec/parser_spec.lua index 567b26c..acbd38f 100644 --- a/spec/parser_spec.lua +++ b/spec/parser_spec.lua @@ -15,19 +15,9 @@ describe('parser', function() end end - local test_langs = { - ['lua/test.lua'] = 'lua', - ['lua/foo.lua'] = 'lua', - ['src/bar.py'] = 'python', - ['test.lua'] = 'lua', - ['test.py'] = 'python', - ['other.lua'] = 'lua', - ['.envrc'] = 'bash', - } - it('returns empty table for empty buffer', function() local bufnr = create_buffer({}) - local hunks = parser.parse_buffer(bufnr, test_langs, {}, false) + local hunks = parser.parse_buffer(bufnr) assert.are.same({}, hunks) delete_buffer(bufnr) end) @@ -40,7 +30,7 @@ describe('parser', function() 'Unstaged (1)', 'M lua/test.lua', }) - local hunks = parser.parse_buffer(bufnr, test_langs, {}, false) + local hunks = parser.parse_buffer(bufnr) assert.are.same({}, hunks) delete_buffer(bufnr) end) @@ -54,7 +44,7 @@ describe('parser', function() '+local new = true', ' return M', }) - local hunks = parser.parse_buffer(bufnr, test_langs, {}, false) + local hunks = parser.parse_buffer(bufnr) assert.are.equal(1, #hunks) assert.are.equal('lua/test.lua', hunks[1].filename) @@ -76,7 +66,7 @@ describe('parser', function() '+ print("hello")', ' end', }) - local hunks = parser.parse_buffer(bufnr, test_langs, {}, false) + local hunks = parser.parse_buffer(bufnr) assert.are.equal(2, #hunks) assert.are.equal(2, hunks[1].start_line) @@ -95,7 +85,7 @@ describe('parser', function() ' def hello():', '+ pass', }) - local hunks = parser.parse_buffer(bufnr, test_langs, {}, false) + local hunks = parser.parse_buffer(bufnr) assert.are.equal(2, #hunks) assert.are.equal('lua/foo.lua', hunks[1].filename) @@ -113,7 +103,7 @@ describe('parser', function() '+print(msg)', ' end', }) - local hunks = parser.parse_buffer(bufnr, test_langs, {}, false) + local hunks = parser.parse_buffer(bufnr) assert.are.equal(1, #hunks) assert.are.equal('function M.hello()', hunks[1].header_context) @@ -128,46 +118,13 @@ describe('parser', function() ' local M = {}', '+local x = 1', }) - local hunks = parser.parse_buffer(bufnr, test_langs, {}, false) + local hunks = parser.parse_buffer(bufnr) assert.are.equal(1, #hunks) assert.is_nil(hunks[1].header_context) delete_buffer(bufnr) end) - it('respects custom language mappings', function() - local bufnr = create_buffer({ - 'M .envrc', - '@@ -1,1 +1,2 @@', - ' export FOO=bar', - '+export BAZ=qux', - }) - local hunks = parser.parse_buffer(bufnr, test_langs, {}, false) - - assert.are.equal(1, #hunks) - assert.are.equal('bash', hunks[1].lang) - delete_buffer(bufnr) - end) - - it('respects disabled_languages', function() - local bufnr = create_buffer({ - 'M test.lua', - '@@ -1,1 +1,2 @@', - ' local M = {}', - '+local x = 1', - 'M test.py', - '@@ -1,1 +1,2 @@', - ' def foo():', - '+ pass', - }) - local hunks = parser.parse_buffer(bufnr, test_langs, { 'lua' }, false) - - assert.are.equal(1, #hunks) - assert.are.equal('test.py', hunks[1].filename) - assert.are.equal('python', hunks[1].lang) - delete_buffer(bufnr) - end) - it('handles all git status prefixes', function() local prefixes = { 'M', 'A', 'D', 'R', 'C', '?', '!' } for _, prefix in ipairs(prefixes) do @@ -177,7 +134,7 @@ describe('parser', function() ' local x = 1', '+local y = 2', }) - local hunks = parser.parse_buffer(bufnr, test_langs, {}, false) + local hunks = parser.parse_buffer(bufnr) assert.are.equal(1, #hunks, 'Failed for prefix: ' .. prefix) delete_buffer(bufnr) end @@ -192,7 +149,7 @@ describe('parser', function() '', 'Some other content', }) - local hunks = parser.parse_buffer(bufnr, test_langs, {}, false) + local hunks = parser.parse_buffer(bufnr) assert.are.equal(1, #hunks) assert.are.equal(2, #hunks[1].lines) @@ -209,7 +166,7 @@ describe('parser', function() '@@ -1,1 +1,1 @@', ' local z = 3', }) - local hunks = parser.parse_buffer(bufnr, test_langs, {}, false) + local hunks = parser.parse_buffer(bufnr) assert.are.equal(2, #hunks) assert.are.equal(2, #hunks[1].lines)