Fix ANSI processor to handle multi-line regions

This commit is contained in:
Junegunn Choi 2015-07-22 14:19:45 +09:00
parent f71ea5f3ea
commit 5e3cb3a4ea
4 changed files with 72 additions and 20 deletions

View File

@ -36,11 +36,13 @@ func init() {
ansiRegex = regexp.MustCompile("\x1b\\[[0-9;]*[mK]") ansiRegex = regexp.MustCompile("\x1b\\[[0-9;]*[mK]")
} }
func extractColor(str *string) (*string, []ansiOffset) { func extractColor(str *string, state *ansiState) (*string, []ansiOffset, *ansiState) {
var offsets []ansiOffset var offsets []ansiOffset
var output bytes.Buffer var output bytes.Buffer
var state *ansiState
if state != nil {
offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
}
idx := 0 idx := 0
for _, offset := range ansiRegex.FindAllStringIndex(*str, -1) { for _, offset := range ansiRegex.FindAllStringIndex(*str, -1) {
@ -76,7 +78,7 @@ func extractColor(str *string) (*string, []ansiOffset) {
} }
} }
outputStr := output.String() outputStr := output.String()
return &outputStr, offsets return &outputStr, offsets, state
} }
func interpretCode(ansiCode string, prevState *ansiState) *ansiState { func interpretCode(ansiCode string, prevState *ansiState) *ansiState {

View File

@ -14,79 +14,89 @@ func TestExtractColor(t *testing.T) {
} }
src := "hello world" src := "hello world"
var state *ansiState
clean := "\x1b[0m" clean := "\x1b[0m"
check := func(assertion func(ansiOffsets []ansiOffset)) { check := func(assertion func(ansiOffsets []ansiOffset, state *ansiState)) {
output, ansiOffsets := extractColor(&src) output, ansiOffsets, newState := extractColor(&src, state)
state = newState
if *output != "hello world" { if *output != "hello world" {
t.Errorf("Invalid output: {}", output) t.Errorf("Invalid output: {}", output)
} }
fmt.Println(src, ansiOffsets, clean) fmt.Println(src, ansiOffsets, clean)
assertion(ansiOffsets) assertion(ansiOffsets, state)
} }
check(func(offsets []ansiOffset) { check(func(offsets []ansiOffset, state *ansiState) {
if len(offsets) > 0 { if len(offsets) > 0 {
t.Fail() t.Fail()
} }
}) })
state = nil
src = "\x1b[0mhello world" src = "\x1b[0mhello world"
check(func(offsets []ansiOffset) { check(func(offsets []ansiOffset, state *ansiState) {
if len(offsets) > 0 { if len(offsets) > 0 {
t.Fail() t.Fail()
} }
}) })
state = nil
src = "\x1b[1mhello world" src = "\x1b[1mhello world"
check(func(offsets []ansiOffset) { check(func(offsets []ansiOffset, state *ansiState) {
if len(offsets) != 1 { if len(offsets) != 1 {
t.Fail() t.Fail()
} }
assert(offsets[0], 0, 11, -1, -1, true) assert(offsets[0], 0, 11, -1, -1, true)
}) })
state = nil
src = "\x1b[1mhello \x1b[mworld" src = "\x1b[1mhello \x1b[mworld"
check(func(offsets []ansiOffset) { check(func(offsets []ansiOffset, state *ansiState) {
if len(offsets) != 1 { if len(offsets) != 1 {
t.Fail() t.Fail()
} }
assert(offsets[0], 0, 6, -1, -1, true) assert(offsets[0], 0, 6, -1, -1, true)
}) })
state = nil
src = "\x1b[1mhello \x1b[Kworld" src = "\x1b[1mhello \x1b[Kworld"
check(func(offsets []ansiOffset) { check(func(offsets []ansiOffset, state *ansiState) {
if len(offsets) != 1 { if len(offsets) != 1 {
t.Fail() t.Fail()
} }
assert(offsets[0], 0, 11, -1, -1, true) assert(offsets[0], 0, 11, -1, -1, true)
}) })
state = nil
src = "hello \x1b[34;45;1mworld" src = "hello \x1b[34;45;1mworld"
check(func(offsets []ansiOffset) { check(func(offsets []ansiOffset, state *ansiState) {
if len(offsets) != 1 { if len(offsets) != 1 {
t.Fail() t.Fail()
} }
assert(offsets[0], 6, 11, 4, 5, true) assert(offsets[0], 6, 11, 4, 5, true)
}) })
state = nil
src = "hello \x1b[34;45;1mwor\x1b[34;45;1mld" src = "hello \x1b[34;45;1mwor\x1b[34;45;1mld"
check(func(offsets []ansiOffset) { check(func(offsets []ansiOffset, state *ansiState) {
if len(offsets) != 1 { if len(offsets) != 1 {
t.Fail() t.Fail()
} }
assert(offsets[0], 6, 11, 4, 5, true) assert(offsets[0], 6, 11, 4, 5, true)
}) })
state = nil
src = "hello \x1b[34;45;1mwor\x1b[0mld" src = "hello \x1b[34;45;1mwor\x1b[0mld"
check(func(offsets []ansiOffset) { check(func(offsets []ansiOffset, state *ansiState) {
if len(offsets) != 1 { if len(offsets) != 1 {
t.Fail() t.Fail()
} }
assert(offsets[0], 6, 9, 4, 5, true) assert(offsets[0], 6, 9, 4, 5, true)
}) })
state = nil
src = "hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md" src = "hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md"
check(func(offsets []ansiOffset) { check(func(offsets []ansiOffset, state *ansiState) {
if len(offsets) != 3 { if len(offsets) != 3 {
t.Fail() t.Fail()
} }
@ -96,12 +106,47 @@ func TestExtractColor(t *testing.T) {
}) })
// {38,48};5;{38,48} // {38,48};5;{38,48}
state = nil
src = "hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md" src = "hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md"
check(func(offsets []ansiOffset) { check(func(offsets []ansiOffset, state *ansiState) {
if len(offsets) != 2 { if len(offsets) != 2 {
t.Fail() t.Fail()
} }
assert(offsets[0], 6, 9, 38, 48, true) assert(offsets[0], 6, 9, 38, 48, true)
assert(offsets[1], 9, 10, 48, 38, true) assert(offsets[1], 9, 10, 48, 38, true)
}) })
src = "hello \x1b[32;1mworld"
check(func(offsets []ansiOffset, state *ansiState) {
if len(offsets) != 1 {
t.Fail()
}
if state.fg != 2 || state.bg != -1 || !state.bold {
t.Fail()
}
assert(offsets[0], 6, 11, 2, -1, true)
})
src = "hello world"
check(func(offsets []ansiOffset, state *ansiState) {
if len(offsets) != 1 {
t.Fail()
}
if state.fg != 2 || state.bg != -1 || !state.bold {
t.Fail()
}
assert(offsets[0], 0, 11, 2, -1, true)
})
src = "hello \x1b[0;38;5;200;48;5;100mworld"
check(func(offsets []ansiOffset, state *ansiState) {
if len(offsets) != 2 {
t.Fail()
}
if state.fg != 200 || state.bg != 100 || state.bold {
t.Fail()
}
assert(offsets[0], 0, 6, 2, -1, true)
assert(offsets[1], 6, 11, 200, 100, false)
})
} }

View File

@ -69,14 +69,17 @@ func Run(opts *Options) {
} }
if opts.Ansi { if opts.Ansi {
if opts.Theme != nil { if opts.Theme != nil {
var state *ansiState
ansiProcessor = func(data *string) (*string, []ansiOffset) { ansiProcessor = func(data *string) (*string, []ansiOffset) {
return extractColor(data) trimmed, offsets, newState := extractColor(data, state)
state = newState
return trimmed, offsets
} }
} else { } else {
// When color is disabled but ansi option is given, // When color is disabled but ansi option is given,
// we simply strip out ANSI codes from the input // we simply strip out ANSI codes from the input
ansiProcessor = func(data *string) (*string, []ansiOffset) { ansiProcessor = func(data *string) (*string, []ansiOffset) {
trimmed, _ := extractColor(data) trimmed, _, _ := extractColor(data, nil)
return trimmed, nil return trimmed, nil
} }
} }

View File

@ -378,6 +378,7 @@ func (t *Terminal) printHeader() {
return return
} }
max := C.MaxY() max := C.MaxY()
var state *ansiState
for idx, lineStr := range t.header { for idx, lineStr := range t.header {
if !t.reverse { if !t.reverse {
idx = len(t.header) - idx - 1 idx = len(t.header) - idx - 1
@ -389,7 +390,8 @@ func (t *Terminal) printHeader() {
if line >= max { if line >= max {
break break
} }
trimmed, colors := extractColor(&lineStr) trimmed, colors, newState := extractColor(&lineStr, state)
state = newState
item := &Item{ item := &Item{
text: trimmed, text: trimmed,
index: 0, index: 0,