diff --git a/pkg/yqlib/encoder_toml.go b/pkg/yqlib/encoder_toml.go index 4b80626c..69342db5 100644 --- a/pkg/yqlib/encoder_toml.go +++ b/pkg/yqlib/encoder_toml.go @@ -565,27 +565,50 @@ func (te *tomlEncoder) colorizeToml(input []byte) []byte { } // Table sections - [section] or [[array]] + // Only treat '[' as a table section if it appears at the start of the line + // (possibly after whitespace). This avoids mis-coloring inline arrays like + // "ports = [8000, 8001]" as table sections. if ch == '[' { - end := i + 1 - // Check for [[ - if end < len(toml) && toml[end] == '[' { - end++ - } - // Find closing ] - for end < len(toml) && toml[end] != ']' { - end++ - } - // Include closing ] - if end < len(toml) { - end++ - // Check for ]] - if end < len(toml) && toml[end] == ']' { - end++ + isSectionHeader := true + if i > 0 { + isSectionHeader = false + j := i - 1 + for j >= 0 && toml[j] != '\n' { + if toml[j] != ' ' && toml[j] != '\t' && toml[j] != '\r' { + // Found a non-whitespace character before this '[' on the same line, + // so this is not a table header. + break + } + j-- + } + if j < 0 || toml[j] == '\n' { + // Reached the start of the string or a newline without encountering + // any non-whitespace, so '[' is at the logical start of the line. + isSectionHeader = true } } - result.WriteString(sectionColor(toml[i:end])) - i = end - continue + if isSectionHeader { + end := i + 1 + // Check for [[ + if end < len(toml) && toml[end] == '[' { + end++ + } + // Find closing ] + for end < len(toml) && toml[end] != ']' { + end++ + } + // Include closing ] + if end < len(toml) { + end++ + // Check for ]] + if end < len(toml) && toml[end] == ']' { + end++ + } + } + result.WriteString(sectionColor(toml[i:end])) + i = end + continue + } } // Strings - quoted text (double or single quotes) diff --git a/pkg/yqlib/toml_test.go b/pkg/yqlib/toml_test.go index 91cfd30d..7f79403d 100644 --- a/pkg/yqlib/toml_test.go +++ b/pkg/yqlib/toml_test.go @@ -3,8 +3,10 @@ package yqlib import ( "bufio" "fmt" + "strings" "testing" + "github.com/fatih/color" "github.com/mikefarah/yq/v4/test" ) @@ -625,3 +627,61 @@ func TestTomlScenarios(t *testing.T) { } documentScenarios(t, "usage", "toml", genericScenarios, documentTomlScenario) } + +// TestTomlColorization tests that colorization correctly distinguishes +// between table section headers and inline arrays +func TestTomlColorization(t *testing.T) { + // Test that inline arrays are not colored as table sections + encoder := &tomlEncoder{prefs: TomlPreferences{ColorsEnabled: true}} + + // Create TOML with both table sections and inline arrays + input := []byte(`[database] +enabled = true +ports = [8000, 8001, 8002] + +[servers] +alpha = "test" +`) + + result := encoder.colorizeToml(input) + resultStr := string(result) + + // The bug would cause the inline array [8000, 8001, 8002] to be + // colored with the section color (Yellow + Bold) instead of being + // left uncolored or colored differently. + // + // To test this, we check that the section color codes appear only + // for actual table sections, not for inline arrays. + + // Get the ANSI codes for section color (Yellow + Bold) + sectionColor := color.New(color.FgYellow, color.Bold).SprintFunc() + sampleSection := sectionColor("[database]") + + // Extract just the ANSI codes from the sample + // ANSI codes start with \x1b[ + var ansiStart string + for i := 0; i < len(sampleSection); i++ { + if sampleSection[i] == '\x1b' { + // Find the end of the ANSI sequence (ends with 'm') + end := i + for end < len(sampleSection) && sampleSection[end] != 'm' { + end++ + } + if end < len(sampleSection) { + ansiStart = sampleSection[i : end+1] + break + } + } + } + + // Count how many times the section color appears in the output + // It should appear exactly twice: once for [database] and once for [servers] + // If it appears more times (e.g., for [8000, 8001, 8002]), that's the bug + sectionColorCount := strings.Count(resultStr, ansiStart) + + // We expect exactly 2 occurrences (for [database] and [servers]) + // The bug would cause more occurrences (e.g., also for [8000) + if sectionColorCount != 2 { + t.Errorf("Expected section color to appear exactly 2 times (for [database] and [servers]), but it appeared %d times.\nOutput: %s", sectionColorCount, resultStr) + } +}