Merge pull request #79 from barrett-ruth/fix/qol-logs
Ansi: Option to Disable the parser
This commit is contained in:
commit
0c9ae37d74
9 changed files with 337 additions and 11 deletions
|
|
@ -148,6 +148,7 @@ Here's an example configuration with lazy.nvim: >lua
|
|||
scrapers = { 'atcoder', 'codeforces', 'cses' },
|
||||
filename = default_filename, -- <contest id> + <problem id>
|
||||
run_panel = {
|
||||
ansi = true,
|
||||
diff_mode = 'vim',
|
||||
next_test_key = '<c-n>',
|
||||
prev_test_key = '<c-p>',
|
||||
|
|
@ -199,6 +200,12 @@ Here's an example configuration with lazy.nvim: >lua
|
|||
|
||||
*cp.RunPanelConfig*
|
||||
Fields: ~
|
||||
{ansi} (boolean, default: true) Enable ANSI color parsing and
|
||||
highlighting. When true, compiler output and test results
|
||||
display with colored syntax highlighting. When false,
|
||||
ANSI escape codes are stripped for plain text display.
|
||||
Requires vim.g.terminal_color_* to be configured for
|
||||
proper color display.
|
||||
{diff_mode} (string, default: "vim") Diff backend: "vim" or "git".
|
||||
Git provides character-level precision, vim uses
|
||||
built-in diff.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
local M = {}
|
||||
|
||||
local logger = require('cp.log')
|
||||
|
||||
---@param raw_output string|table
|
||||
---@return string
|
||||
function M.bytes_to_string(raw_output)
|
||||
|
|
@ -192,6 +194,21 @@ function M.setup_highlight_groups()
|
|||
BrightWhite = vim.g.terminal_color_15,
|
||||
}
|
||||
|
||||
local missing_color = false
|
||||
for _, terminal_color in pairs(color_map) do
|
||||
if terminal_color == nil then
|
||||
missing_color = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if missing_color or #color_map == 0 then
|
||||
logger.log(
|
||||
'ansi terminal colors (vim.g.terminal_color_*) not configured. . ANSI colors will not display properly. ',
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
end
|
||||
|
||||
local combinations = {
|
||||
{ bold = false, italic = false },
|
||||
{ bold = true, italic = false },
|
||||
|
|
@ -202,7 +219,7 @@ function M.setup_highlight_groups()
|
|||
for _, combo in ipairs(combinations) do
|
||||
for color_name, terminal_color in pairs(color_map) do
|
||||
local parts = { 'CpAnsi' }
|
||||
local opts = { fg = terminal_color }
|
||||
local opts = { fg = terminal_color or 'NONE' }
|
||||
|
||||
if combo.bold then
|
||||
table.insert(parts, 'Bold')
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
---@field setup_code? fun(ctx: ProblemContext)
|
||||
|
||||
---@class RunPanelConfig
|
||||
---@field ansi boolean Enable ANSI color parsing and highlighting
|
||||
---@field diff_mode "vim"|"git" Diff backend to use
|
||||
---@field next_test_key string Key to navigate to next test case
|
||||
---@field prev_test_key string Key to navigate to previous test case
|
||||
|
|
@ -97,6 +98,7 @@ M.defaults = {
|
|||
scrapers = constants.PLATFORMS,
|
||||
filename = nil,
|
||||
run_panel = {
|
||||
ansi = true,
|
||||
diff_mode = 'vim',
|
||||
next_test_key = '<c-n>',
|
||||
prev_test_key = '<c-p>',
|
||||
|
|
@ -186,6 +188,11 @@ function M.setup(user_config)
|
|||
})
|
||||
|
||||
vim.validate({
|
||||
ansi = {
|
||||
config.run_panel.ansi,
|
||||
'boolean',
|
||||
'ansi color parsing must be enabled xor disabled',
|
||||
},
|
||||
diff_mode = {
|
||||
config.run_panel.diff_mode,
|
||||
function(value)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ local scrape = require('cp.scrape')
|
|||
local snippets = require('cp.snippets')
|
||||
|
||||
if not vim.fn.has('nvim-0.10.0') then
|
||||
vim.notify('[cp.nvim]: requires nvim-0.10.0+', vim.log.levels.ERROR)
|
||||
logger.log('[cp.nvim]: requires nvim-0.10.0+', vim.log.levels.ERROR)
|
||||
return {}
|
||||
end
|
||||
|
||||
|
|
@ -537,8 +537,10 @@ local function toggle_run_panel(is_debug)
|
|||
refresh_run_panel()
|
||||
|
||||
vim.schedule(function()
|
||||
local ansi = require('cp.ansi')
|
||||
ansi.setup_highlight_groups()
|
||||
if config.run_panel.ansi then
|
||||
local ansi = require('cp.ansi')
|
||||
ansi.setup_highlight_groups()
|
||||
end
|
||||
if current_diff_layout then
|
||||
update_diff_panes()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -241,9 +241,13 @@ local function run_single_test_case(ctx, contest_config, cp_config, test_case)
|
|||
local actual_highlights = {}
|
||||
|
||||
if actual_output ~= '' then
|
||||
local parsed = ansi.parse_ansi_text(actual_output)
|
||||
actual_output = table.concat(parsed.lines, '\n')
|
||||
actual_highlights = parsed.highlights
|
||||
if cp_config.run_panel.ansi then
|
||||
local parsed = ansi.parse_ansi_text(actual_output)
|
||||
actual_output = table.concat(parsed.lines, '\n')
|
||||
actual_highlights = parsed.highlights
|
||||
else
|
||||
actual_output = actual_output:gsub('\027%[[%d;]*[a-zA-Z]', '')
|
||||
end
|
||||
end
|
||||
|
||||
local max_lines = cp_config.run_panel.max_output_lines
|
||||
|
|
@ -362,11 +366,18 @@ end
|
|||
|
||||
function M.handle_compilation_failure(compilation_output)
|
||||
local ansi = require('cp.ansi')
|
||||
local config = require('cp.config').setup()
|
||||
|
||||
-- Always parse the compilation output - it contains everything now
|
||||
local parsed = ansi.parse_ansi_text(compilation_output or '')
|
||||
local clean_text = table.concat(parsed.lines, '\n')
|
||||
local highlights = parsed.highlights
|
||||
local clean_text
|
||||
local highlights = {}
|
||||
|
||||
if config.run_panel.ansi then
|
||||
local parsed = ansi.parse_ansi_text(compilation_output or '')
|
||||
clean_text = table.concat(parsed.lines, '\n')
|
||||
highlights = parsed.highlights
|
||||
else
|
||||
clean_text = (compilation_output or ''):gsub('\027%[[%d;]*[a-zA-Z]', '')
|
||||
end
|
||||
|
||||
for _, test_case in ipairs(run_panel_state.test_cases) do
|
||||
test_case.status = 'fail'
|
||||
|
|
|
|||
|
|
@ -212,4 +212,33 @@ describe('ansi parser', function()
|
|||
assert.is_nil(state.foreground)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('setup_highlight_groups', function()
|
||||
it('creates highlight groups with fallback colors when terminal colors are nil', function()
|
||||
local original_colors = {}
|
||||
for i = 0, 15 do
|
||||
original_colors[i] = vim.g['terminal_color_' .. i]
|
||||
vim.g['terminal_color_' .. i] = nil
|
||||
end
|
||||
|
||||
ansi.setup_highlight_groups()
|
||||
|
||||
local highlight = vim.api.nvim_get_hl(0, { name = 'CpAnsiRed' })
|
||||
-- When 'NONE' is set, nvim_get_hl returns nil for that field
|
||||
assert.is_nil(highlight.fg)
|
||||
|
||||
for i = 0, 15 do
|
||||
vim.g['terminal_color_' .. i] = original_colors[i]
|
||||
end
|
||||
end)
|
||||
|
||||
it('creates highlight groups with proper colors when terminal colors are set', function()
|
||||
vim.g.terminal_color_1 = '#ff0000'
|
||||
|
||||
ansi.setup_highlight_groups()
|
||||
|
||||
local highlight = vim.api.nvim_get_hl(0, { name = 'CpAnsiRed' })
|
||||
assert.equals(0xff0000, highlight.fg)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
|
|||
|
|
@ -81,6 +81,16 @@ describe('cp.config', function()
|
|||
end)
|
||||
|
||||
describe('run_panel config validation', function()
|
||||
it('validates ansi is boolean', function()
|
||||
local invalid_config = {
|
||||
run_panel = { ansi = 'invalid' },
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
config.setup(invalid_config)
|
||||
end, 'ansi: expected ansi color parsing must be enabled xor disabled, got string')
|
||||
end)
|
||||
|
||||
it('validates diff_mode values', function()
|
||||
local invalid_config = {
|
||||
run_panel = { diff_mode = 'invalid' },
|
||||
|
|
@ -114,6 +124,7 @@ describe('cp.config', function()
|
|||
it('accepts valid run_panel config', function()
|
||||
local valid_config = {
|
||||
run_panel = {
|
||||
ansi = false,
|
||||
diff_mode = 'git',
|
||||
next_test_key = 'j',
|
||||
prev_test_key = 'k',
|
||||
|
|
|
|||
215
spec/extmark_spec.lua
Normal file
215
spec/extmark_spec.lua
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
describe('extmarks', function()
|
||||
local spec_helper = require('spec.spec_helper')
|
||||
local highlight
|
||||
|
||||
before_each(function()
|
||||
spec_helper.setup()
|
||||
highlight = require('cp.highlight')
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
spec_helper.teardown()
|
||||
end)
|
||||
|
||||
describe('buffer deletion', function()
|
||||
it('clears namespace on buffer delete', function()
|
||||
local bufnr = 1
|
||||
local namespace = 100
|
||||
local mock_clear = stub(vim.api, 'nvim_buf_clear_namespace')
|
||||
local mock_extmark = stub(vim.api, 'nvim_buf_set_extmark')
|
||||
|
||||
highlight.apply_highlights(bufnr, {
|
||||
{
|
||||
line = 0,
|
||||
col_start = 0,
|
||||
col_end = 5,
|
||||
highlight_group = 'CpDiffAdded',
|
||||
},
|
||||
}, namespace)
|
||||
|
||||
assert.stub(mock_clear).was_called_with(bufnr, namespace, 0, -1)
|
||||
mock_clear:revert()
|
||||
mock_extmark:revert()
|
||||
end)
|
||||
|
||||
it('handles invalid buffer gracefully', function()
|
||||
local bufnr = 999
|
||||
local namespace = 100
|
||||
local mock_clear = stub(vim.api, 'nvim_buf_clear_namespace')
|
||||
local mock_extmark = stub(vim.api, 'nvim_buf_set_extmark')
|
||||
|
||||
mock_clear.on_call_with(bufnr, namespace, 0, -1).invokes(function()
|
||||
error('Invalid buffer')
|
||||
end)
|
||||
|
||||
local success = pcall(highlight.apply_highlights, bufnr, {
|
||||
{
|
||||
line = 0,
|
||||
col_start = 0,
|
||||
col_end = 5,
|
||||
highlight_group = 'CpDiffAdded',
|
||||
},
|
||||
}, namespace)
|
||||
|
||||
assert.is_false(success)
|
||||
mock_clear:revert()
|
||||
mock_extmark:revert()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('namespace isolation', function()
|
||||
it('creates unique namespaces', function()
|
||||
local mock_create = stub(vim.api, 'nvim_create_namespace')
|
||||
mock_create.on_call_with('cp_diff_highlights').returns(100)
|
||||
mock_create.on_call_with('cp_test_list').returns(200)
|
||||
mock_create.on_call_with('cp_ansi_highlights').returns(300)
|
||||
|
||||
local diff_ns = highlight.create_namespace()
|
||||
local test_ns = vim.api.nvim_create_namespace('cp_test_list')
|
||||
local ansi_ns = vim.api.nvim_create_namespace('cp_ansi_highlights')
|
||||
|
||||
assert.equals(100, diff_ns)
|
||||
assert.equals(200, test_ns)
|
||||
assert.equals(300, ansi_ns)
|
||||
|
||||
mock_create:revert()
|
||||
end)
|
||||
|
||||
it('clears specific namespace independently', function()
|
||||
local bufnr = 1
|
||||
local ns1 = 100
|
||||
local ns2 = 200
|
||||
local mock_clear = stub(vim.api, 'nvim_buf_clear_namespace')
|
||||
local mock_extmark = stub(vim.api, 'nvim_buf_set_extmark')
|
||||
|
||||
highlight.apply_highlights(bufnr, {
|
||||
{ line = 0, col_start = 0, col_end = 5, highlight_group = 'CpDiffAdded' },
|
||||
}, ns1)
|
||||
|
||||
highlight.apply_highlights(bufnr, {
|
||||
{ line = 1, col_start = 0, col_end = 3, highlight_group = 'CpDiffRemoved' },
|
||||
}, ns2)
|
||||
|
||||
assert.stub(mock_clear).was_called_with(bufnr, ns1, 0, -1)
|
||||
assert.stub(mock_clear).was_called_with(bufnr, ns2, 0, -1)
|
||||
assert.stub(mock_clear).was_called(2)
|
||||
|
||||
mock_clear:revert()
|
||||
mock_extmark:revert()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('multiple updates', function()
|
||||
it('clears previous extmarks on each update', function()
|
||||
local bufnr = 1
|
||||
local namespace = 100
|
||||
local mock_clear = stub(vim.api, 'nvim_buf_clear_namespace')
|
||||
local mock_extmark = stub(vim.api, 'nvim_buf_set_extmark')
|
||||
|
||||
highlight.apply_highlights(bufnr, {
|
||||
{ line = 0, col_start = 0, col_end = 5, highlight_group = 'CpDiffAdded' },
|
||||
}, namespace)
|
||||
|
||||
highlight.apply_highlights(bufnr, {
|
||||
{ line = 1, col_start = 0, col_end = 3, highlight_group = 'CpDiffRemoved' },
|
||||
}, namespace)
|
||||
|
||||
assert.stub(mock_clear).was_called(2)
|
||||
assert.stub(mock_clear).was_called_with(bufnr, namespace, 0, -1)
|
||||
assert.stub(mock_extmark).was_called(2)
|
||||
|
||||
mock_clear:revert()
|
||||
mock_extmark:revert()
|
||||
end)
|
||||
|
||||
it('handles empty highlights', function()
|
||||
local bufnr = 1
|
||||
local namespace = 100
|
||||
local mock_clear = stub(vim.api, 'nvim_buf_clear_namespace')
|
||||
local mock_extmark = stub(vim.api, 'nvim_buf_set_extmark')
|
||||
|
||||
highlight.apply_highlights(bufnr, {
|
||||
{ line = 0, col_start = 0, col_end = 5, highlight_group = 'CpDiffAdded' },
|
||||
}, namespace)
|
||||
|
||||
highlight.apply_highlights(bufnr, {}, namespace)
|
||||
|
||||
assert.stub(mock_clear).was_called(2)
|
||||
assert.stub(mock_extmark).was_called(1)
|
||||
|
||||
mock_clear:revert()
|
||||
mock_extmark:revert()
|
||||
end)
|
||||
|
||||
it('skips invalid highlights', function()
|
||||
local bufnr = 1
|
||||
local namespace = 100
|
||||
local mock_clear = stub(vim.api, 'nvim_buf_clear_namespace')
|
||||
local mock_extmark = stub(vim.api, 'nvim_buf_set_extmark')
|
||||
|
||||
highlight.apply_highlights(bufnr, {
|
||||
{ line = 0, col_start = 5, col_end = 5, highlight_group = 'CpDiffAdded' },
|
||||
{ line = 1, col_start = 7, col_end = 3, highlight_group = 'CpDiffAdded' },
|
||||
{ line = 2, col_start = 0, col_end = 5, highlight_group = 'CpDiffAdded' },
|
||||
}, namespace)
|
||||
|
||||
assert.stub(mock_clear).was_called_with(bufnr, namespace, 0, -1)
|
||||
assert.stub(mock_extmark).was_called(1)
|
||||
assert.stub(mock_extmark).was_called_with(bufnr, namespace, 2, 0, {
|
||||
end_col = 5,
|
||||
hl_group = 'CpDiffAdded',
|
||||
priority = 100,
|
||||
})
|
||||
|
||||
mock_clear:revert()
|
||||
mock_extmark:revert()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('error handling', function()
|
||||
it('fails when clear_namespace fails', function()
|
||||
local bufnr = 1
|
||||
local namespace = 100
|
||||
local mock_clear = stub(vim.api, 'nvim_buf_clear_namespace')
|
||||
local mock_extmark = stub(vim.api, 'nvim_buf_set_extmark')
|
||||
|
||||
mock_clear.on_call_with(bufnr, namespace, 0, -1).invokes(function()
|
||||
error('Namespace clear failed')
|
||||
end)
|
||||
|
||||
local success = pcall(highlight.apply_highlights, bufnr, {
|
||||
{ line = 0, col_start = 0, col_end = 5, highlight_group = 'CpDiffAdded' },
|
||||
}, namespace)
|
||||
|
||||
assert.is_false(success)
|
||||
assert.stub(mock_extmark).was_not_called()
|
||||
|
||||
mock_clear:revert()
|
||||
mock_extmark:revert()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('parse_and_apply_diff cleanup', function()
|
||||
it('clears namespace before applying parsed diff', function()
|
||||
local bufnr = 1
|
||||
local namespace = 100
|
||||
local mock_clear = stub(vim.api, 'nvim_buf_clear_namespace')
|
||||
local mock_extmark = stub(vim.api, 'nvim_buf_set_extmark')
|
||||
local mock_set_lines = stub(vim.api, 'nvim_buf_set_lines')
|
||||
local mock_get_option = stub(vim.api, 'nvim_get_option_value')
|
||||
local mock_set_option = stub(vim.api, 'nvim_set_option_value')
|
||||
|
||||
mock_get_option.returns(false)
|
||||
|
||||
highlight.parse_and_apply_diff(bufnr, '+hello {+world+}', namespace)
|
||||
|
||||
assert.stub(mock_clear).was_called_with(bufnr, namespace, 0, -1)
|
||||
|
||||
mock_clear:revert()
|
||||
mock_extmark:revert()
|
||||
mock_set_lines:revert()
|
||||
mock_get_option:revert()
|
||||
mock_set_option:revert()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
27
spec/run_spec.lua
Normal file
27
spec/run_spec.lua
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
describe('run module', function()
|
||||
local run = require('cp.run')
|
||||
|
||||
describe('basic functionality', function()
|
||||
it('has required functions', function()
|
||||
assert.is_function(run.load_test_cases)
|
||||
assert.is_function(run.run_test_case)
|
||||
assert.is_function(run.run_all_test_cases)
|
||||
assert.is_function(run.get_run_panel_state)
|
||||
assert.is_function(run.handle_compilation_failure)
|
||||
end)
|
||||
|
||||
it('can get panel state', function()
|
||||
local state = run.get_run_panel_state()
|
||||
assert.is_table(state)
|
||||
assert.is_table(state.test_cases)
|
||||
end)
|
||||
|
||||
it('handles compilation failure', function()
|
||||
local compilation_output = 'error.cpp:1:1: error: undefined variable'
|
||||
|
||||
assert.does_not_error(function()
|
||||
run.handle_compilation_failure(compilation_output)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
Loading…
Add table
Add a link
Reference in a new issue