diff --git a/README.md b/README.md index 3001f4a..dc044c1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ You can drill down JSON interactively by using filtering queries like [jq](https ## Demo -![demo-jid-main](https://github.com/simeji/jid/wiki/images/demo-jid-main-640.gif) +![demo-jid-main](https://github.com/simeji/jid/wiki/images/demo-jid-main-640-colorize.gif) ## Installation @@ -112,6 +112,11 @@ jid < file.json ### Option -First argument: Initial query - --q : Print query (for jq) +|option|description| +|:-----------|:----------| +|First argument ($1) | Initial query| +|-h | print a help| +|-help | print a help| +|-version | print the version and exit| +|-q | Output query mode (for jq)| +|-M | monochrome output mode| diff --git a/cmd/jid/jid.go b/cmd/jid/jid.go index bd07393..e20ef87 100644 --- a/cmd/jid/jid.go +++ b/cmd/jid/jid.go @@ -16,12 +16,14 @@ func main() { var qm bool var help bool var version bool + var mono bool qs := "." flag.BoolVar(&qm, "q", false, "Output query mode") flag.BoolVar(&help, "h", false, "print a help") flag.BoolVar(&help, "help", false, "print a help") flag.BoolVar(&version, "version", false, "print the version and exit") + flag.BoolVar(&mono, "M", false, "monochrome output mode") flag.Parse() if help { @@ -38,7 +40,12 @@ func main() { qs = args[0] } - e, err := jid.NewEngine(content, qs) + ea := &jid.EngineAttribute{ + DefaultQuery: qs, + Monochrome: mono, + } + + e, err := jid.NewEngine(content, ea) if err != nil { fmt.Println(err) diff --git a/engine.go b/engine.go index b695cff..767103e 100644 --- a/engine.go +++ b/engine.go @@ -24,28 +24,33 @@ type EngineResultInterface interface { } type Engine struct { - manager *JsonManager - query QueryInterface - term *Terminal - complete []string - keymode bool - candidates []string - candidatemode bool - candidateidx int - contentOffset int - queryConfirm bool - cursorOffsetX int + manager *JsonManager + query QueryInterface + queryCursorIdx int + term *Terminal + complete []string + keymode bool + candidates []string + candidatemode bool + candidateidx int + contentOffset int + queryConfirm bool } -func NewEngine(s io.Reader, qs string) (EngineInterface, error) { +type EngineAttribute struct { + DefaultQuery string + Monochrome bool +} + +func NewEngine(s io.Reader, ea *EngineAttribute) (EngineInterface, error) { j, err := NewJsonManager(s) if err != nil { return nil, err } e := &Engine{ manager: j, - term: NewTerminal(FilterPrompt, DefaultY), - query: NewQuery([]rune(qs)), + term: NewTerminal(FilterPrompt, DefaultY, ea.Monochrome), + query: NewQuery([]rune(ea.DefaultQuery)), complete: []string{"", ""}, keymode: false, candidates: []string{}, @@ -53,9 +58,8 @@ func NewEngine(s io.Reader, qs string) (EngineInterface, error) { candidateidx: 0, contentOffset: 0, queryConfirm: false, - cursorOffsetX: 0, } - e.cursorOffsetX = len(e.query.Get()) + e.queryCursorIdx = e.query.Length() return e, nil } @@ -94,7 +98,7 @@ func (e *Engine) Run() EngineResultInterface { if e.query.StringGet() == "" { e.query.StringSet(".") - e.cursorOffsetX = len(e.query.StringGet()) + e.queryCursorIdx = e.query.Length() } contents = e.getContents() @@ -103,15 +107,17 @@ func (e *Engine) Run() EngineResultInterface { ta := &TerminalDrawAttributes{ Query: e.query.StringGet(), - CursorOffsetX: e.cursorOffsetX, Contents: contents, CandidateIndex: e.candidateidx, ContentsOffsetY: e.contentOffset, Complete: e.complete[0], Candidates: e.candidates, + CursorOffset: e.query.IndexOffset(e.queryCursorIdx), + } + err = e.term.Draw(ta) + if err != nil { + panic(err) } - - e.term.draw(ta) switch ev := termbox.PollEvent(); ev.Type { case termbox.EventKey: @@ -193,21 +199,22 @@ func (e *Engine) setCandidateData() { func (e *Engine) confirmCandidate() { _, _ = e.query.PopKeyword() _ = e.query.StringAdd(".") - q := e.query.StringAdd(e.candidates[e.candidateidx]) - e.cursorOffsetX = len(q) + _ = e.query.StringAdd(e.candidates[e.candidateidx]) + e.queryCursorIdx = e.query.Length() e.queryConfirm = true } func (e *Engine) deleteChar() { - if e.cursorOffsetX > 0 { - _ = e.query.Delete(e.cursorOffsetX - 1) - e.cursorOffsetX -= 1 + if i := e.queryCursorIdx - 1; i > 0 { + _ = e.query.Delete(i) + e.queryCursorIdx-- } + } func (e *Engine) deleteLineQuery() { _ = e.query.StringSet("") - e.cursorOffsetX = 0 + e.queryCursorIdx = 0 } func (e *Engine) scrollToBelow() { @@ -225,14 +232,12 @@ func (e *Engine) deleteWordBackward() { if k, _ := e.query.StringPopKeyword(); k != "" && !strings.Contains(k, "[") { _ = e.query.StringAdd(".") } - e.cursorOffsetX = len(e.query.Get()) + e.queryCursorIdx = e.query.Length() } func (e *Engine) tabAction() { if !e.candidatemode { e.candidatemode = true - if e.query.StringGet() == "" { - _ = e.query.StringAdd(".") - } else if e.complete[0] != e.complete[1] && e.complete[0] != "" { + if e.complete[0] != e.complete[1] && e.complete[0] != "" { if k, _ := e.query.StringPopKeyword(); !strings.Contains(k, "[") { _ = e.query.StringAdd(".") } @@ -243,36 +248,35 @@ func (e *Engine) tabAction() { } else { e.candidateidx = e.candidateidx + 1 } - e.cursorOffsetX = len(e.query.Get()) + e.queryCursorIdx = e.query.Length() } func (e *Engine) escapeCandidateMode() { e.candidatemode = false } func (e *Engine) inputChar(ch rune) { - b := len(e.query.Get()) - q := e.query.StringInsert(string(ch), e.cursorOffsetX) - if b < len(q) { - e.cursorOffsetX += 1 - } + _ = e.query.Insert([]rune{ch}, e.queryCursorIdx) + e.queryCursorIdx++ } func (e *Engine) moveCursorBackward() { - if e.cursorOffsetX > 0 { - e.cursorOffsetX -= 1 + if i := e.queryCursorIdx - 1; i >= 0 { + e.queryCursorIdx-- } } + func (e *Engine) moveCursorForward() { - if len(e.query.Get()) > e.cursorOffsetX { - e.cursorOffsetX += 1 + if e.query.Length() > e.queryCursorIdx { + e.queryCursorIdx++ } } + func (e *Engine) moveCursorWordBackwark() { } func (e *Engine) moveCursorWordForward() { } func (e *Engine) moveCursorToTop() { - e.cursorOffsetX = 0 + e.queryCursorIdx = 0 } func (e *Engine) moveCursorToEnd() { - e.cursorOffsetX = len(e.query.Get()) + e.queryCursorIdx = e.query.Length() } diff --git a/engine_test.go b/engine_test.go index 1a37537..9d02acc 100644 --- a/engine_test.go +++ b/engine_test.go @@ -12,40 +12,43 @@ func TestNewEngine(t *testing.T) { var assert = assert.New(t) f, _ := os.Create("/dev/null") - e, err := NewEngine(f, "") + e, err := NewEngine(f, &EngineAttribute{ + DefaultQuery: "", + Monochrome: false, + }) assert.Nil(e) assert.NotNil(err) ee := getEngine(`{"name":"go"}`, "") assert.NotNil(ee) assert.Equal("", ee.query.StringGet()) - assert.Equal(0, ee.cursorOffsetX) + assert.Equal(0, ee.queryCursorIdx) } func TestNewEngineWithQuery(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"go"}`, ".nam") assert.Equal(".nam", e.query.StringGet()) - assert.Equal(4, e.cursorOffsetX) + assert.Equal(4, e.queryCursorIdx) e = getEngine(`{"name":"go"}`, "nam") assert.Equal("", e.query.StringGet()) - assert.Equal(0, e.cursorOffsetX) + assert.Equal(0, e.queryCursorIdx) e = getEngine(`{"name":"go"}`, ".nam..") assert.Equal("", e.query.StringGet()) - assert.Equal(0, e.cursorOffsetX) + assert.Equal(0, e.queryCursorIdx) } func TestDeleteChar(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"go"}`, "") e.query.StringSet(".name") - e.cursorOffsetX = 5 + e.queryCursorIdx = e.query.Length() e.deleteChar() assert.Equal(".nam", e.query.StringGet()) - assert.Equal(4, e.cursorOffsetX) + assert.Equal(4, e.queryCursorIdx) } func TestDeleteWordBackward(t *testing.T) { @@ -55,17 +58,17 @@ func TestDeleteWordBackward(t *testing.T) { e.deleteWordBackward() assert.Equal(".", e.query.StringGet()) - assert.Equal(1, e.cursorOffsetX) + assert.Equal(1, e.queryCursorIdx) e.query.StringSet(".name[1]") e.deleteWordBackward() assert.Equal(".name", e.query.StringGet()) - assert.Equal(5, e.cursorOffsetX) + assert.Equal(5, e.queryCursorIdx) e.query.StringSet(".name[") e.deleteWordBackward() assert.Equal(".name", e.query.StringGet()) - assert.Equal(5, e.cursorOffsetX) + assert.Equal(5, e.queryCursorIdx) } func TestDeleteLineQuery(t *testing.T) { @@ -75,7 +78,7 @@ func TestDeleteLineQuery(t *testing.T) { e.query.StringSet(".name") e.deleteLineQuery() assert.Equal("", e.query.StringGet()) - assert.Equal(0, e.cursorOffsetX) + assert.Equal(0, e.queryCursorIdx) } func TestScrollToAbove(t *testing.T) { @@ -196,14 +199,14 @@ func TestConfirmCandidate(t *testing.T) { e.confirmCandidate() assert.Equal(".test", e.query.StringGet()) assert.True(e.queryConfirm) - assert.Equal(5, e.cursorOffsetX) + assert.Equal(5, e.queryCursorIdx) e.candidateidx = 2 e.confirmCandidate() assert.Equal(".foo", e.query.StringGet()) assert.True(e.queryConfirm) - assert.Equal(4, e.cursorOffsetX) + assert.Equal(4, e.queryCursorIdx) e = getEngine(`{"name":"go"}`, "") e.query.StringSet(".name.hoge") @@ -212,7 +215,7 @@ func TestConfirmCandidate(t *testing.T) { e.confirmCandidate() assert.True(e.queryConfirm) - assert.Equal(9, e.cursorOffsetX) + assert.Equal(9, e.queryCursorIdx) assert.Equal(".name.bbb", e.query.StringGet()) } @@ -262,16 +265,16 @@ func TestInputChar(t *testing.T) { var assert = assert.New(t) e := getEngine(`{"name":"go"}`, "") e.query.StringSet(".name") - e.cursorOffsetX = len(e.query.Get()) - assert.Equal(5, e.cursorOffsetX) + e.queryCursorIdx = e.query.Length() + assert.Equal(5, e.queryCursorIdx) e.inputChar('n') assert.Equal(".namen", e.query.StringGet()) - assert.Equal(6, e.cursorOffsetX) + assert.Equal(6, e.queryCursorIdx) e.inputChar('.') assert.Equal(".namen.", e.query.StringGet()) - assert.Equal(7, e.cursorOffsetX) + assert.Equal(7, e.queryCursorIdx) } func TestMoveCursorForwardAndBackward(t *testing.T) { @@ -279,24 +282,23 @@ func TestMoveCursorForwardAndBackward(t *testing.T) { e := getEngine(`{"name":"simeji"}`, "") e.query.StringSet(".ne") - e.cursorOffsetX = 0 e.moveCursorForward() - assert.Equal(1, e.cursorOffsetX) + assert.Equal(1, e.queryCursorIdx) e.moveCursorForward() - assert.Equal(2, e.cursorOffsetX) + assert.Equal(2, e.queryCursorIdx) e.moveCursorForward() - assert.Equal(3, e.cursorOffsetX) + assert.Equal(3, e.queryCursorIdx) e.moveCursorForward() - assert.Equal(3, e.cursorOffsetX) + assert.Equal(3, e.queryCursorIdx) e.moveCursorBackward() - assert.Equal(2, e.cursorOffsetX) + assert.Equal(2, e.queryCursorIdx) e.moveCursorBackward() - assert.Equal(1, e.cursorOffsetX) + assert.Equal(1, e.queryCursorIdx) e.moveCursorBackward() - assert.Equal(0, e.cursorOffsetX) + assert.Equal(0, e.queryCursorIdx) e.moveCursorBackward() - assert.Equal(0, e.cursorOffsetX) + assert.Equal(0, e.queryCursorIdx) } func TestMoveCursorToTopAndEnd(t *testing.T) { @@ -304,17 +306,19 @@ func TestMoveCursorToTopAndEnd(t *testing.T) { e := getEngine(`{"name":"simeji"}`, "") e.query.StringSet(".ne") - e.cursorOffsetX = 2 e.moveCursorToTop() - assert.Zero(e.cursorOffsetX) + assert.Zero(e.queryCursorIdx) e.moveCursorToEnd() - assert.Equal(3, e.cursorOffsetX) + assert.Equal(3, e.queryCursorIdx) } func getEngine(j string, qs string) *Engine { r := bytes.NewBufferString(j) - e, _ := NewEngine(r, qs) + e, _ := NewEngine(r, &EngineAttribute{ + DefaultQuery: qs, + Monochrome: false, + }) ee := e.(*Engine) return ee } diff --git a/query.go b/query.go index 8cb890f..00ec199 100644 --- a/query.go +++ b/query.go @@ -3,6 +3,8 @@ package jid import ( "regexp" "strings" + + "github.com/mattn/go-runewidth" ) type QueryInterface interface { @@ -12,6 +14,9 @@ type QueryInterface interface { Add(query []rune) []rune Delete(i int) []rune Clear() []rune + Length() int + IndexOffset(int) int + GetChar(int) rune GetKeywords() [][]rune GetLastKeyword() []rune PopKeyword() ([]rune, []rune) @@ -45,6 +50,29 @@ func (q *Query) Get() []rune { return *q.query } +func (q *Query) GetChar(idx int) rune { + var r rune = 0 + qq := q.Get() + if l := len(qq); l > idx && idx >= 0 { + r = qq[idx] + } + return r +} + +func (q *Query) Length() int { + return len(q.Get()) +} + +func (q *Query) IndexOffset(i int) int { + o := 0 + if l := q.Length(); i >= l { + o = runewidth.StringWidth(q.StringGet()) + } else if i >= 0 && i < l { + o = runewidth.StringWidth(string(q.Get()[:i])) + } + return o +} + func (q *Query) Set(query []rune) []rune { if validate(query) { q.query = &query @@ -73,20 +101,26 @@ func (q *Query) Add(query []rune) []rune { } func (q *Query) Delete(i int) []rune { + var d []rune qq := q.Get() lastIdx := len(qq) if i < 0 { if lastIdx+i >= 0 { + d = qq[lastIdx+i:] qq = qq[0 : lastIdx+i] } else { + d = qq qq = qq[0:0] } } else if i == 0 { + d = []rune{} qq = qq[1:] } else if i > 0 && i < lastIdx { + d = []rune{qq[i]} qq = append(qq[:i], qq[i+1:]...) } - return q.Set(qq) + _ = q.Set(qq) + return d } func (q *Query) Clear() []rune { diff --git a/query_test.go b/query_test.go index 5cb600b..d411dac 100644 --- a/query_test.go +++ b/query_test.go @@ -71,6 +71,62 @@ func TestQueryGet(t *testing.T) { assert.Equal(q.Get(), []rune(".test")) } +func TestQueryLength(t *testing.T) { + var assert = assert.New(t) + + v := []rune(".test") + q := NewQuery(v) + + assert.Equal(5, q.Length()) + + v = []rune(".string.日本語.japan") + q = NewQuery(v) + + assert.Equal(17, q.Length()) +} + +func TestQueryIndexOffsetN(t *testing.T) { + var assert = assert.New(t) + + v := []rune(".test") + q := NewQuery(v) + + assert.Equal(4, q.IndexOffset(4)) + assert.Equal(0, q.IndexOffset(0)) + assert.Equal(0, q.IndexOffset(-1)) + assert.Equal(5, q.IndexOffset(6)) + + //off-------012345679-101213|j_15,n_19 + v = []rune(".string.日本語.japan") + //idx-------012345678-9-10|j_12,n_16 + q = NewQuery(v) + + assert.Equal(19, q.IndexOffset(16)) + assert.Equal(10, q.IndexOffset(9)) +} + +func TestQueryGetChar(t *testing.T) { + var assert = assert.New(t) + + v := []rune(".test") + q := NewQuery(v) + + assert.Equal('e', q.GetChar(2)) + assert.Equal('t', q.GetChar(4)) + assert.Equal('.', q.GetChar(0)) + assert.Equal('.', q.GetChar(0)) + assert.Equal(rune(0), q.GetChar(-1)) + assert.Equal(rune(0), q.GetChar(6)) + + v = []rune(".string.日本語.japan") + q = NewQuery(v) + + assert.Equal('n', q.GetChar(5)) + assert.Equal('本', q.GetChar(9)) + assert.Equal('.', q.GetChar(11)) + assert.Equal(rune(0), q.GetChar(17)) +} + func TestQuerySet(t *testing.T) { var assert = assert.New(t) @@ -108,7 +164,8 @@ func TestQueryInsert(t *testing.T) { assert.Equal([]rune(".wwhello.world"), q.Insert([]rune("w"), 1)) assert.Equal([]rune(".wwhello.world"), q.Insert([]rune("."), 1)) assert.Equal([]rune(".wwh.ello.world"), q.Insert([]rune("."), 4)) - assert.Equal([]rune(".wwh.ello.world"), q.Insert([]rune("a"), 20)) + assert.Equal([]rune(".wwh.ello.worldg"), q.Insert([]rune("g"), 15)) + assert.Equal([]rune(".wwh.ello.worldg"), q.Insert([]rune("a"), 20)) } func TestQueryStringInsert(t *testing.T) { var assert = assert.New(t) @@ -138,19 +195,30 @@ func TestQueryDelete(t *testing.T) { v := []rune(".helloworld") q := NewQuery(v) - assert.Equal([]rune(".helloworl"), q.Delete(-1)) - assert.Equal([]rune(".hellowor"), q.Delete(-1)) - assert.Equal([]rune(".hellow"), q.Delete(-2)) - assert.Equal([]rune(""), q.Delete(-8)) + assert.Equal([]rune("d"), q.Delete(-1)) + assert.Equal([]rune(".helloworl"), q.Get()) + assert.Equal([]rune("l"), q.Delete(-1)) + assert.Equal([]rune(".hellowor"), q.Get()) + assert.Equal([]rune("or"), q.Delete(-2)) + assert.Equal([]rune(".hellow"), q.Get()) + assert.Equal([]rune(".hellow"), q.Delete(-8)) + assert.Equal([]rune(""), q.Get()) q = NewQuery([]rune(".hello.world")) - assert.Equal([]rune(".hello.world"), q.Delete(0)) - assert.Equal([]rune(".ello.world"), q.Delete(1)) - assert.Equal([]rune(".llo.world"), q.Delete(1)) - assert.Equal([]rune(".llo.world"), q.Delete(0)) - assert.Equal([]rune(".ll.world"), q.Delete(3)) - assert.Equal([]rune(".llworld"), q.Delete(3)) - assert.Equal([]rune(".llorld"), q.Delete(3)) + assert.Equal([]rune(""), q.Delete(0)) + assert.Equal([]rune(".hello.world"), q.Get()) + assert.Equal([]rune("h"), q.Delete(1)) + assert.Equal([]rune(".ello.world"), q.Get()) + assert.Equal([]rune("e"), q.Delete(1)) + assert.Equal([]rune(".llo.world"), q.Get()) + assert.Equal([]rune(""), q.Delete(0)) + assert.Equal([]rune(".llo.world"), q.Get()) + assert.Equal([]rune("o"), q.Delete(3)) + assert.Equal([]rune(".ll.world"), q.Get()) + assert.Equal([]rune("."), q.Delete(3)) + assert.Equal([]rune(".llworld"), q.Get()) + assert.Equal([]rune("w"), q.Delete(3)) + assert.Equal([]rune(".llorld"), q.Get()) } func TestGetKeywords(t *testing.T) { diff --git a/terminal.go b/terminal.go index 1577a51..8c64c04 100644 --- a/terminal.go +++ b/terminal.go @@ -1,33 +1,49 @@ package jid import ( - "github.com/nsf/termbox-go" + "fmt" + "io/ioutil" "regexp" + "strings" + + "github.com/mattn/go-runewidth" + "github.com/nsf/termbox-go" + "github.com/nwidger/jsoncolor" ) type Terminal struct { - defaultY int - prompt string + defaultY int + prompt string + formatter *jsoncolor.Formatter + monochrome bool + outputArea *[][]termbox.Cell } type TerminalDrawAttributes struct { Query string - CursorOffsetX int Contents []string CandidateIndex int ContentsOffsetY int Complete string Candidates []string + CursorOffset int } -func NewTerminal(prompt string, defaultY int) *Terminal { - return &Terminal{ - prompt: prompt, - defaultY: defaultY, +func NewTerminal(prompt string, defaultY int, monochrome bool) *Terminal { + t := &Terminal{ + prompt: prompt, + defaultY: defaultY, + monochrome: monochrome, + outputArea: &[][]termbox.Cell{}, + formatter: nil, + } + if !monochrome { + t.formatter = t.initColorizeFormatter() } + return t } -func (t *Terminal) draw(attr *TerminalDrawAttributes) { +func (t *Terminal) Draw(attr *TerminalDrawAttributes) error { query := attr.Query complete := attr.Complete @@ -38,39 +54,175 @@ func (t *Terminal) draw(attr *TerminalDrawAttributes) { termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) - fs := t.prompt + query - cs := complete y := t.defaultY - t.drawln(0, 0, fs+cs, []([]int){[]int{len(fs), len(fs) + len(cs)}}) + t.drawFilterLine(query, complete) if len(candidates) > 0 { y = t.drawCandidates(0, t.defaultY, candidateidx, candidates) } - for idx, row := range rows { + cellsArr, err := t.rowsToCells(rows) + if err != nil { + return err + } + + for idx, cells := range cellsArr { if i := idx - contentOffsetY; i >= 0 { - t.drawln(0, i+y, row, nil) + t.drawCells(0, i+y, cells) } } - termbox.SetCursor(len(t.prompt)+attr.CursorOffsetX, 0) + + termbox.SetCursor(len(t.prompt)+attr.CursorOffset, 0) termbox.Flush() + return nil } -func (t *Terminal) drawln(x int, y int, str string, matches [][]int) { +func (t *Terminal) drawFilterLine(qs string, complete string) error { + fs := t.prompt + qs + cs := complete + str := fs + cs + color := termbox.ColorDefault backgroundColor := termbox.ColorDefault + var cells []termbox.Cell + match := []int{len(fs), len(fs + cs)} + var c termbox.Attribute for i, s := range str { c = color - for _, match := range matches { - if i >= match[0]+1 && i < match[1] { - c = termbox.ColorGreen + if i >= match[0] && i < match[1] { + c = termbox.ColorGreen + } + cells = append(cells, termbox.Cell{ + Ch: s, + Fg: c, + Bg: backgroundColor, + }) + } + t.drawCells(0, 0, cells) + return nil +} + +type termboxSprintfFuncer struct { + fg termbox.Attribute + bg termbox.Attribute + outputArea *[][]termbox.Cell +} + +func (tsf *termboxSprintfFuncer) SprintfFunc() func(format string, a ...interface{}) string { + return func(format string, a ...interface{}) string { + cells := tsf.outputArea + idx := len(*cells) - 1 + str := fmt.Sprintf(format, a...) + for _, s := range str { + if s == '\n' { + *cells = append(*cells, []termbox.Cell{}) + idx++ + continue } + (*cells)[idx] = append((*cells)[idx], termbox.Cell{ + Ch: s, + Fg: tsf.fg, + Bg: tsf.bg, + }) } - termbox.SetCell(x+i, y, s, c, backgroundColor) + return "dummy" + } +} + +func (t *Terminal) initColorizeFormatter() *jsoncolor.Formatter { + formatter := jsoncolor.NewFormatter() + + regular := &termboxSprintfFuncer{ + fg: termbox.ColorDefault, + bg: termbox.ColorDefault, + outputArea: t.outputArea, + } + + bold := &termboxSprintfFuncer{ + fg: termbox.AttrBold, + bg: termbox.ColorDefault, + outputArea: t.outputArea, + } + + blueBold := &termboxSprintfFuncer{ + fg: termbox.ColorBlue | termbox.AttrBold, + bg: termbox.ColorDefault, + outputArea: t.outputArea, + } + + green := &termboxSprintfFuncer{ + fg: termbox.ColorGreen, + bg: termbox.ColorDefault, + outputArea: t.outputArea, + } + + blackBold := &termboxSprintfFuncer{ + fg: termbox.ColorBlack | termbox.AttrBold, + bg: termbox.ColorDefault, + outputArea: t.outputArea, + } + + formatter.SpaceColor = regular + formatter.CommaColor = bold + formatter.ColonColor = bold + formatter.ObjectColor = bold + formatter.ArrayColor = bold + formatter.FieldQuoteColor = blueBold + formatter.FieldColor = blueBold + formatter.StringQuoteColor = green + formatter.StringColor = green + formatter.TrueColor = regular + formatter.FalseColor = regular + formatter.NumberColor = regular + formatter.NullColor = blackBold + + return formatter +} + +func (t *Terminal) rowsToCells(rows []string) ([][]termbox.Cell, error) { + *t.outputArea = [][]termbox.Cell{[]termbox.Cell{}} + + var err error + + if t.formatter != nil { + err = t.formatter.Format(ioutil.Discard, []byte(strings.Join(rows, "\n"))) + } + + cells := *t.outputArea + + if err != nil || t.monochrome { + cells = [][]termbox.Cell{} + for _, row := range rows { + var cls []termbox.Cell + for _, char := range row { + cls = append(cls, termbox.Cell{ + Ch: char, + Fg: termbox.ColorDefault, + Bg: termbox.ColorDefault, + }) + } + cells = append(cells, cls) + } + } + + return cells, nil +} + +func (t *Terminal) drawCells(x int, y int, cells []termbox.Cell) { + i := 0 + for _, c := range cells { + termbox.SetCell(x+i, y, c.Ch, c.Fg, c.Bg) + + w := runewidth.RuneWidth(c.Ch) + if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(c.Ch) { + w = 1 + } + + i += w } } @@ -98,13 +250,19 @@ func (t *Terminal) drawCandidates(x int, y int, index int, candidates []string) for i, row := range rows { match := re.FindStringIndex(row) var c termbox.Attribute - for ii, s := range row { + ii := 0 + for k, s := range row { c = color backgroundColor = termbox.ColorMagenta - if match != nil && ii >= match[0]+1 && ii < match[1]-1 { + if match != nil && k >= match[0]+1 && k < match[1]-1 { backgroundColor = termbox.ColorWhite } termbox.SetCell(x+ii, y+i, s, c, backgroundColor) + w := runewidth.RuneWidth(s) + if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(s) { + w = 1 + } + ii += w } } return y + len(rows)