Full Code of simeji/jid for AI

master 2968365762bd cached
17 files
71.7 KB
23.5k tokens
154 symbols
1 requests
Download .txt
Repository: simeji/jid
Branch: master
Commit: 2968365762bd
Files: 17
Total size: 71.7 KB

Directory structure:
gitextract_eacgd7ye/

├── .circleci/
│   └── config.yml
├── .gitignore
├── .goreleaser.yml
├── ChangeLog
├── LICENSE
├── README.md
├── engine.go
├── engine_test.go
├── go.mod
├── go.sum
├── json_manager.go
├── json_manager_test.go
├── query.go
├── query_test.go
├── suggestion.go
├── suggestion_test.go
└── terminal.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .circleci/config.yml
================================================
version: 2.1
jobs:
  test:
    docker:
      - image: circleci/golang:latest
    steps:
      - checkout
      - run: go test -v ./
      - run: go test -v ./cmd/jid
  release:
    docker:
      - image: circleci/golang:latest
    steps:
      - checkout
      - run: 
          name: build jid using goreleaser
          command: curl -sL https://git.io/goreleaser | bash
workflows:
  version: 2.1
  test_and_release:
    jobs:
      - test:
          filters:
            tags:
              only: /v[0-9]+(\.[0-9]+)*(-.*)*/
      - release:
          requires:
            - test
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /v[0-9]+(\.[0-9]+)*(-.*)*/


================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# Folders
_obj
_test

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe
*.test
*.prof
*.swp
*.test
test.*

# jid package
jid
*.out
*.log
dist

.idea


================================================
FILE: .goreleaser.yml
================================================
# This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
before:
  hooks:
    # you may remove this if you don't use vgo
    - go mod download
    # you may remove this if you don't need go generate
    - go generate ./...
builds:
- env:
  - CGO_ENABLED=0
  main: ./cmd/jid/jid.go
  ldflags:
  - -s -w
  goos:
    - windows
    - openbsd
    - netbsd
    - linux
    - freebsd
    - darwin
  goarch:
    - arm64
    - amd64
    - 386
archive:
  name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
  format: zip
  files:
    - none*
checksum:
  name_template: 'checksums.txt'
snapshot:
  name_template: "{{ .Tag }}-next"
changelog:
  sort: asc
  filters:
    exclude:
    - '^docs:'
    - '^test:'
# for Validation
#release:
#  disable: true


================================================
FILE: ChangeLog
================================================
ChangeLog
=======

### 0.7.2 - Jan 23 2017

BugFix:
* fix version number

### 0.7.1 - Jan 5 2017

Features:
* scroll to bottom and top

### 0.7.0 - Jan 4 2017

Features:
* output with color
* monochrome mode option

BugFix:
* multibyte Query & Json key/value

Modified README

### 0.6.3 - 15 Dec 2016

Features:
* Add `-help` and `-h` command for show a help
* Add Ctrl-U command and a query behavior

Change Behaviors:
* Force insert `.`

### 0.6.2 - 9 Dec 2016

Features:
* Add --version flag for homebrew test

### 0.6.1 - 5 Dec 2016

Features:
* Get first argument of `jid` for initial query

Change Behaviors:
* Auto input . at first character of query


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2016 simeji

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# jid

[![Circle CI](https://circleci.com/gh/simeji/jid/tree/master.svg?style=shield)](https://circleci.com/gh/simeji/jid/tree/master)

Json Incremental Digger

It's a very simple tool.  
You can drill down JSON interactively by using filtering queries like [jq](https://stedolan.github.io/jq/).

**Suggestion** and **Auto completion** of this tool will provide you a very comfortable JSON drill down.

## Demo

![demo-jid-main](https://github.com/simeji/jid/wiki/images/demo-jid-main-640-colorize.gif)

## Installation

* [With HomeBrew (for macOS)](#with-homebrew-for-macos)  
* [With MacPorts (for macOS)](#with-macports-for-macos)  
* [With pkg (for FreeBSD)](#with-pkg-for-freebsd)
* [With scoop (for Windows)](#with-scoop-for-windows)
* [Other package management system](#other-package-management-systems)
* [Simply use "jid" command](#simply-use-jid-command)  
* [Build](#build)  

### With HomeBrew (for macOS)

```
brew install jid
```

### With MacPorts (for macOS)

```
sudo port install jid
```

### With pkg (for FreeBSD)

```
pkg install jid
```

### With scoop (for Windows)

```
scoop install jid
```

### Other package management systems

Jid can install by package management systems of below OS.

[![Packaging status](https://repology.org/badge/vertical-allrepos/jid.svg)](https://repology.org/metapackage/jid/versions)


### Simply use "jid" command

If you simply want to use `jid` command, please download binary from below.

https://github.com/simeji/jid/releases

## Build

```
go install github.com/simeji/jid/cmd/jid@latest
```

## Usage

### Quick start

* [simple json example](#simple-json-example)  
* [simple json example2](#simple-json-example2)  
* [with initial query](#with-initial-query)  
* [with curl](#with-curl)  

#### simple json example

Please execute the below command.

```
echo '{"aa":"2AA2","bb":{"aaa":[123,"cccc",[1,2]],"c":321}}'| jid
```

then, jid will be running.

You can dig JSON data incrementally.

When you enter `.bb.aaa[2]`, you will see the following.

```
[Filter]> .bb.aaa[2]
[
  1,
  2
]
```

Then, you press Enter key and output `[1,2]` and exit.

#### simple json example2

This json is used by [demo section](https://github.com/simeji/jid#demo).
```
echo '{"info":{"date":"2016-10-23","version":1.0},"users":[{"name":"simeji","uri":"https://github.com/simeji","id":1},{"name":"simeji2","uri":"https://example.com/simeji","id":2},{"name":"simeji3","uri":"https://example.com/simeji3","id":3}],"userCount":3}}'|jid
```

#### With a initial query

First argument of `jid` is initial query.
(Use JSON same as [Demo](#demo))

![demo-jid-with-query](https://github.com/simeji/jid/wiki/images/demo-jid-with-query-640.gif)

#### with curl

Sample for using [RDAP](https://datatracker.ietf.org/wg/weirds/documents/) data.

```
curl -s http://rdg.afilias.info/rdap/domain/example.info | jid
```

#### Load JSON from a file

```
jid < file.json
```

## Keymaps

|key|description|
|:-----------|:----------|
|`TAB` / `CTRL` + `I` |Show available items and choice them|
|`CTRL` + `W` |Delete from the cursor to the start of the word|
|`CTRL` + `U` |Delete whole query|
|`CTRL` + `F` / Right Arrow (:arrow_right:)|Move cursor a character to the right|
|`CTRL` + `B` / Left Arrow (:arrow_left:)|Move cursor a character to the left|
|`CTRL` + `A`|To the first character of the 'Filter'|
|`CTRL` + `E`|To the end of the 'Filter'|
|`CTRL` + `J`|Scroll json buffer 1 line downwards|
|`CTRL` + `K`|Scroll json buffer 1 line upwards|
|`CTRL` + `G`|Scroll json buffer to bottom|
|`CTRL` + `T`|Scroll json buffer to top|
|`CTRL` + `N`|Scroll json buffer 'Page Down'|
|`CTRL` + `P`|Scroll json buffer 'Page Up'|
|`CTRL` + `L`|Change view mode whole json or keys (only object)|
|`ESC`|Hide a candidate box|

### Option

|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|


================================================
FILE: engine.go
================================================
package jid

import (
	"io"
	"strings"

	termbox "github.com/nsf/termbox-go"
)

const (
	DefaultY     int    = 1
	FilterPrompt string = "[Filter]> "
)

type EngineInterface interface {
	Run() EngineResultInterface
	GetQuery() QueryInterface
}

type EngineResultInterface interface {
	GetQueryString() string
	GetContent() string
	GetError() error
}

type Engine struct {
	manager        *JsonManager
	query          QueryInterface
	queryCursorIdx int
	term           *Terminal
	complete       []string
	keymode        bool
	candidates     []string
	candidatemode  bool
	candidateidx   int
	contentOffset  int
	queryConfirm   bool
	prettyResult   bool
}

type EngineAttribute struct {
	DefaultQuery string
	Monochrome   bool
	PrettyResult 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, ea.Monochrome),
		query:         NewQuery([]rune(ea.DefaultQuery)),
		complete:      []string{"", ""},
		keymode:       false,
		candidates:    []string{},
		candidatemode: false,
		candidateidx:  0,
		contentOffset: 0,
		queryConfirm:  false,
		prettyResult:  ea.PrettyResult,
	}
	e.queryCursorIdx = e.query.Length()
	return e, nil
}

type EngineResult struct {
	content string
	qs      string
	err     error
}

func (er *EngineResult) GetQueryString() string {
	return er.qs
}

func (er *EngineResult) GetContent() string {
	return er.content
}
func (er *EngineResult) GetError() error {
	return er.err
}

func (e *Engine) GetQuery() QueryInterface {
	return e.query
}

func (e *Engine) Run() EngineResultInterface {

	err := termbox.Init()
	if err != nil {
		panic(err)
	}
	defer termbox.Close()

	var contents []string

	for {

		if e.query.StringGet() == "" {
			e.query.StringSet(".")
			e.queryCursorIdx = e.query.Length()
		}

		bl := len(contents)
		contents = e.getContents()
		e.setCandidateData()
		e.queryConfirm = false
		if bl != len(contents) {
			e.contentOffset = 0
		}

		ta := &TerminalDrawAttributes{
			Query:           e.query.StringGet(),
			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)
		}

		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			switch ev.Key {
			case 0:
				e.inputChar(ev.Ch)
			case termbox.KeyBackspace, termbox.KeyBackspace2:
				e.deleteChar()
			case termbox.KeyTab:
				e.tabAction()
			case termbox.KeyArrowLeft, termbox.KeyCtrlB:
				e.moveCursorBackward()
			case termbox.KeyArrowRight, termbox.KeyCtrlF:
				e.moveCursorForward()
			case termbox.KeyHome, termbox.KeyCtrlA:
				e.moveCursorToTop()
			case termbox.KeyEnd, termbox.KeyCtrlE:
				e.moveCursorToEnd()
			case termbox.KeyCtrlK:
				e.scrollToAbove()
			case termbox.KeyCtrlJ:
				e.scrollToBelow()
			case termbox.KeyCtrlG:
				e.scrollToBottom(len(contents))
			case termbox.KeyCtrlT:
				e.scrollToTop()
			case termbox.KeyCtrlN:
				_, h := termbox.Size()
				e.scrollPageDown(len(contents), h)
			case termbox.KeyCtrlP:
				_, h := termbox.Size()
				e.scrollPageUp(h)
			case termbox.KeyCtrlL:
				e.toggleKeymode()
			case termbox.KeyCtrlU:
				e.deleteLineQuery()
			case termbox.KeyCtrlW:
				e.deleteWordBackward()
			case termbox.KeyEsc:
				e.escapeCandidateMode()
			case termbox.KeyEnter:
				if !e.candidatemode {
					var cc string
					var err error
					if e.prettyResult {
						cc, _, _, err = e.manager.GetPretty(e.query, true)
					} else {
						cc, _, _, err = e.manager.Get(e.query, true)
					}

					return &EngineResult{
						content: cc,
						qs:      e.query.StringGet(),
						err:     err,
					}
				}
				e.confirmCandidate()
			case termbox.KeyCtrlC:
				return &EngineResult{}
			default:
			}
		case termbox.EventError:
			panic(ev.Err)
			break
		default:
		}
	}
}

func (e *Engine) getContents() []string {
	var c string
	var contents []string
	c, e.complete, e.candidates, _ = e.manager.GetPretty(e.query, e.queryConfirm)
	if e.keymode {
		contents = e.candidates
	} else {
		contents = strings.Split(c, "\n")
	}
	return contents
}

func (e *Engine) setCandidateData() {
	if l := len(e.candidates); e.complete[0] == "" && l > 1 {
		if e.candidateidx >= l {
			e.candidateidx = 0
		}
	} else {
		e.candidatemode = false
	}
	if !e.candidatemode {
		e.candidateidx = 0
		e.candidates = []string{}
	}
}

func (e *Engine) confirmCandidate() {
	_, _ = e.query.PopKeyword()
	_ = e.query.StringAdd(".")
	_ = e.query.StringAdd(e.candidates[e.candidateidx])
	e.queryCursorIdx = e.query.Length()
	e.queryConfirm = true
}

func (e *Engine) deleteChar() {
	if i := e.queryCursorIdx - 1; i > 0 {
		_ = e.query.Delete(i)
		e.queryCursorIdx--
	}

}

func (e *Engine) deleteLineQuery() {
	_ = e.query.StringSet("")
	e.queryCursorIdx = 0
}

func (e *Engine) scrollToBelow() {
	e.contentOffset++
}

func (e *Engine) scrollToAbove() {
	if o := e.contentOffset - 1; o >= 0 {
		e.contentOffset = o
	}
}

func (e *Engine) scrollToBottom(rownum int) {
	e.contentOffset = rownum - 1
}

func (e *Engine) scrollToTop() {
	e.contentOffset = 0
}

func (e *Engine) scrollPageDown(rownum int, height int) {
	co := rownum - 1
	if o := rownum - e.contentOffset; o > height {
		co = e.contentOffset + (height - DefaultY)
	}
	e.contentOffset = co
}

func (e *Engine) scrollPageUp(height int) {
	co := 0
	if o := e.contentOffset - (height - DefaultY); o > 0 {
		co = o
	}
	e.contentOffset = co
}

func (e *Engine) toggleKeymode() {
	e.keymode = !e.keymode
}
func (e *Engine) deleteWordBackward() {
	if k, _ := e.query.StringPopKeyword(); k != "" && !strings.Contains(k, "[") {
		_ = e.query.StringAdd(".")
	}
	e.queryCursorIdx = e.query.Length()
}
func (e *Engine) tabAction() {
	if !e.candidatemode {
		e.candidatemode = true
		if e.complete[0] != e.complete[1] && e.complete[0] != "" {
			if k, _ := e.query.StringPopKeyword(); !strings.Contains(k, "[") {
				_ = e.query.StringAdd(".")
			}
			_ = e.query.StringAdd(e.complete[1])
		} else {
			_ = e.query.StringAdd(e.complete[0])
		}
	} else {
		e.candidateidx = e.candidateidx + 1
	}
	e.queryCursorIdx = e.query.Length()
}
func (e *Engine) escapeCandidateMode() {
	e.candidatemode = false
}
func (e *Engine) inputChar(ch rune) {
	_ = e.query.Insert([]rune{ch}, e.queryCursorIdx)
	e.queryCursorIdx++
}

func (e *Engine) moveCursorBackward() {
	if i := e.queryCursorIdx - 1; i >= 0 {
		e.queryCursorIdx--
	}
}

func (e *Engine) moveCursorForward() {
	if e.query.Length() > e.queryCursorIdx {
		e.queryCursorIdx++
	}
}

func (e *Engine) moveCursorWordBackwark() {
}
func (e *Engine) moveCursorWordForward() {
}
func (e *Engine) moveCursorToTop() {
	e.queryCursorIdx = 0
}
func (e *Engine) moveCursorToEnd() {
	e.queryCursorIdx = e.query.Length()
}


================================================
FILE: engine_test.go
================================================
package jid

import (
	"bytes"
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNewEngine(t *testing.T) {
	var assert = assert.New(t)

	f, _ := os.Create("/dev/null")
	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.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.queryCursorIdx)

	e = getEngine(`{"name":"go"}`, "nam")
	assert.Equal("", e.query.StringGet())
	assert.Equal(0, e.queryCursorIdx)

	e = getEngine(`{"name":"go"}`, ".nam..")
	assert.Equal("", e.query.StringGet())
	assert.Equal(0, e.queryCursorIdx)
}

func TestDeleteChar(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"name":"go"}`, "")
	e.query.StringSet(".name")
	e.queryCursorIdx = e.query.Length()

	e.deleteChar()
	assert.Equal(".nam", e.query.StringGet())
	assert.Equal(4, e.queryCursorIdx)
}

func TestDeleteWordBackward(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"name":"go"}`, "")
	e.query.StringSet(".name")

	e.deleteWordBackward()
	assert.Equal(".", e.query.StringGet())
	assert.Equal(1, e.queryCursorIdx)

	e.query.StringSet(".name[1]")
	e.deleteWordBackward()
	assert.Equal(".name", e.query.StringGet())
	assert.Equal(5, e.queryCursorIdx)

	e.query.StringSet(".name[")
	e.deleteWordBackward()
	assert.Equal(".name", e.query.StringGet())
	assert.Equal(5, e.queryCursorIdx)
}

func TestDeleteLineQuery(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"name":"go"}`, "")

	e.query.StringSet(".name")
	e.deleteLineQuery()
	assert.Equal("", e.query.StringGet())
	assert.Equal(0, e.queryCursorIdx)
}

func TestScrollToAbove(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"named":"go","NameTest":[1,2,3]}`, "")
	assert.Equal(0, e.contentOffset)
	e.scrollToAbove()
	assert.Equal(0, e.contentOffset)
	e.contentOffset = 5
	e.scrollToAbove()
	assert.Equal(4, e.contentOffset)
	e.scrollToAbove()
	assert.Equal(3, e.contentOffset)
}

func TestScrollToBelow(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"named":"go","NameTest":[1,2,3]}`, "")
	e.scrollToBelow()
	assert.Equal(1, e.contentOffset)
	e.scrollToBelow()
	e.scrollToBelow()
	assert.Equal(3, e.contentOffset)
}
func TestScrollToBottomAndTop(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"named":"go","NameTest":[1,2,3]}`, "")

	e.scrollToBottom(5)
	assert.Equal(4, e.contentOffset)

	e.scrollToTop()
	assert.Equal(0, e.contentOffset)
}

func TestScrollPageUpDown(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"named":"go","NameTest":[1,2,3]}`, "")

	cl := len(e.getContents())
	// Then DefaultY = 1
	e.scrollPageDown(cl, 3)
	assert.Equal(2, e.contentOffset)
	e.scrollPageDown(cl, 3)
	assert.Equal(4, e.contentOffset)

	e.scrollPageUp(3)
	assert.Equal(2, e.contentOffset)

	// term height changed
	e.scrollPageDown(cl, 5)
	assert.Equal(6, e.contentOffset)

	e.scrollPageDown(cl, 5)
	assert.Equal(7, e.contentOffset)

	e.scrollPageDown(cl, 5)
	assert.Equal(7, e.contentOffset)

	e.scrollPageUp(5)
	assert.Equal(3, e.contentOffset)

	e.scrollPageUp(5)
	assert.Equal(0, e.contentOffset)

	e.scrollPageUp(5)
	assert.Equal(0, e.contentOffset)

	// term height > content size + default Y (a filter query line)
	e.scrollPageDown(cl, 10)
	assert.Equal(7, e.contentOffset)
}
func TestGetContents(t *testing.T) {
	var assert = assert.New(t)

	e := getEngine(`{"name":"go"}`, "")
	c := e.getContents()
	assert.Equal([]string{`{`, `  "name": "go"`, "}"}, c)
	assert.Equal([]string{}, e.candidates)
	assert.Equal([]string{"", ""}, e.complete)

	e = getEngine(`{"name":"go", "naming":"simeji", "foo":"bar"}`, "")
	e.query.StringSet(".n")
	c = e.getContents()
	assert.Equal([]string{`{`, `  "foo": "bar",`, `  "name": "go",`, `  "naming": "simeji"`, "}"}, c)
	assert.Equal([]string{"name", "naming"}, e.candidates)
	assert.Equal([]string{"am", "nam"}, e.complete)

	e.keymode = true
	c = e.getContents()
	assert.Equal([]string{"name", "naming"}, c)
	assert.Equal([]string{"name", "naming"}, e.candidates)
	assert.Equal([]string{"am", "nam"}, e.complete)
}

func TestSetCandidateData(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"name":"go"}`, "")

	// case 1
	e.candidates = []string{"test", "testing"}
	e.complete = []string{"est", "test"}
	e.candidatemode = true
	e.candidateidx = 1

	e.setCandidateData()
	assert.False(e.candidatemode)
	assert.Zero(e.candidateidx)
	assert.Equal([]string{}, e.candidates)

	// case 2
	e.candidates = []string{"test"}
	e.complete = []string{"", "test"}
	e.candidatemode = true
	e.candidateidx = 1

	e.setCandidateData()
	assert.False(e.candidatemode)
	assert.Zero(e.candidateidx)
	assert.Equal([]string{}, e.candidates)

	// case 3
	e.candidates = []string{"test", "testing"}
	e.complete = []string{"", "test"}
	e.candidatemode = true
	e.candidateidx = 2

	e.setCandidateData()
	assert.True(e.candidatemode)
	assert.Zero(e.candidateidx)
	assert.Equal([]string{"test", "testing"}, e.candidates)

	// case 4
	e.candidates = []string{"test", "testing"}
	e.complete = []string{"", "test"}
	e.candidatemode = true
	e.candidateidx = 1

	e.setCandidateData()
	assert.True(e.candidatemode)
	assert.Equal(1, e.candidateidx)
	assert.Equal([]string{"test", "testing"}, e.candidates)

	// case 4
	e.candidates = []string{"test", "testing"}
	e.complete = []string{"", "test"}
	e.candidatemode = false
	e.candidateidx = 1

	e.setCandidateData()
	assert.False(e.candidatemode)
	assert.Equal(0, e.candidateidx)
	assert.Equal([]string{}, e.candidates)

}

func TestConfirmCandidate(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"name":"go","NameTest":[1,2,3]}`, "")
	e.query.StringSet(".")
	e.queryConfirm = false
	e.candidates = []string{"test", "testing", "foo"}

	e.candidateidx = 0
	e.confirmCandidate()
	assert.Equal(".test", e.query.StringGet())
	assert.True(e.queryConfirm)
	assert.Equal(5, e.queryCursorIdx)

	e.candidateidx = 2
	e.confirmCandidate()
	assert.Equal(".foo", e.query.StringGet())

	assert.True(e.queryConfirm)
	assert.Equal(4, e.queryCursorIdx)

	e = getEngine(`{"name":"go"}`, "")
	e.query.StringSet(".name.hoge")
	e.candidates = []string{"aaa", "bbb", "ccc"}
	e.candidateidx = 1
	e.confirmCandidate()

	assert.True(e.queryConfirm)
	assert.Equal(9, e.queryCursorIdx)
	assert.Equal(".name.bbb", e.query.StringGet())
}

func TestCtrllAction(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"name":"go","NameTest":[1,2,3]}`, "")
	assert.False(e.keymode)
	e.toggleKeymode()
	assert.True(e.keymode)
	e.toggleKeymode()
	assert.False(e.keymode)
}

func TestTabAction(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"name":"go","NameTest":[1,2,3]}`, "")
	e.query.StringSet(".namet")
	e.complete = []string{"est", "NameTest"}

	e.candidatemode = false
	e.tabAction()
	assert.Equal(".NameTest", e.query.StringGet())

	_, e.complete, _, _ = e.manager.GetPretty(e.query, true)
	e.candidatemode = false
	e.tabAction()
	assert.Equal(".NameTest[", e.query.StringGet())

	_, e.complete, _, _ = e.manager.GetPretty(e.query, true)
	e.candidatemode = false
	e.tabAction()
	assert.Equal(".NameTest[", e.query.StringGet())
}

func TestEscAction(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"name":"go","NameTest":[1,2,3]}`, "")
	assert.False(e.candidatemode)
	e.escapeCandidateMode()
	assert.False(e.candidatemode)
	e.candidatemode = true
	e.escapeCandidateMode()
	assert.False(e.candidatemode)
}

func TestInputChar(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"name":"go"}`, "")
	e.query.StringSet(".name")
	e.queryCursorIdx = e.query.Length()
	assert.Equal(5, e.queryCursorIdx)

	e.inputChar('n')
	assert.Equal(".namen", e.query.StringGet())
	assert.Equal(6, e.queryCursorIdx)

	e.inputChar('.')
	assert.Equal(".namen.", e.query.StringGet())
	assert.Equal(7, e.queryCursorIdx)
}

func TestMoveCursorForwardAndBackward(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"name":"simeji"}`, "")
	e.query.StringSet(".ne")

	e.moveCursorForward()
	assert.Equal(1, e.queryCursorIdx)
	e.moveCursorForward()
	assert.Equal(2, e.queryCursorIdx)
	e.moveCursorForward()
	assert.Equal(3, e.queryCursorIdx)
	e.moveCursorForward()
	assert.Equal(3, e.queryCursorIdx)

	e.moveCursorBackward()
	assert.Equal(2, e.queryCursorIdx)
	e.moveCursorBackward()
	assert.Equal(1, e.queryCursorIdx)
	e.moveCursorBackward()
	assert.Equal(0, e.queryCursorIdx)
	e.moveCursorBackward()
	assert.Equal(0, e.queryCursorIdx)
}

func TestMoveCursorToTopAndEnd(t *testing.T) {
	var assert = assert.New(t)
	e := getEngine(`{"name":"simeji"}`, "")
	e.query.StringSet(".ne")

	e.moveCursorToTop()
	assert.Zero(e.queryCursorIdx)

	e.moveCursorToEnd()
	assert.Equal(3, e.queryCursorIdx)
}

func getEngine(j string, qs string) *Engine {
	r := bytes.NewBufferString(j)
	e, _ := NewEngine(r, &EngineAttribute{
		DefaultQuery: qs,
		Monochrome:   false,
	})
	ee := e.(*Engine)
	return ee
}


================================================
FILE: go.mod
================================================
module github.com/simeji/jid

go 1.20

require (
	github.com/bitly/go-simplejson v0.5.0
	github.com/mattn/go-runewidth v0.0.4
	github.com/nsf/termbox-go v0.0.0-20181027232701-60ab7e3d12ed
	github.com/nwidger/jsoncolor v0.0.0-20170215171346-75a6de4340e5
	github.com/pkg/errors v0.8.0
	github.com/stretchr/testify v1.8.1
)

require (
	github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/fatih/color v1.7.0 // indirect
	github.com/kr/pretty v0.3.1 // indirect
	github.com/mattn/go-colorable v0.0.9 // indirect
	github.com/mattn/go-isatty v0.0.4 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	golang.org/x/sys v0.10.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)


================================================
FILE: go.sum
================================================
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/nsf/termbox-go v0.0.0-20181027232701-60ab7e3d12ed h1:bAVGG6B+R5qpSylrrA+BAMrzYkdAoiTaKPVxRB+4cyM=
github.com/nsf/termbox-go v0.0.0-20181027232701-60ab7e3d12ed/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/nwidger/jsoncolor v0.0.0-20170215171346-75a6de4340e5 h1:d+C3xJdxZT7wNlxqEwbXn3R355CwAhYBL9raVNfSnK0=
github.com/nwidger/jsoncolor v0.0.0-20170215171346-75a6de4340e5/go.mod h1:GYFm0zZgTNeoK1QxuIofRDasy2ibmaJZhZLzwsMXUF4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: json_manager.go
================================================
package jid

import (
	"github.com/bitly/go-simplejson"
	"github.com/pkg/errors"
	"io"
	"regexp"
	"strconv"
	//"strings"
)

type JsonManager struct {
	current    *simplejson.Json
	origin     *simplejson.Json
	suggestion *Suggestion
}

func NewJsonManager(reader io.Reader) (*JsonManager, error) {
	buf, err := io.ReadAll(reader)

	if err != nil {
		return nil, errors.Wrap(err, "invalid data")
	}

	j, err2 := simplejson.NewJson(buf)

	if err2 != nil {
		return nil, errors.Wrap(err2, "invalid json format")
	}

	json := &JsonManager{
		origin:     j,
		current:    j,
		suggestion: NewSuggestion(),
	}

	return json, nil
}

func (jm *JsonManager) Get(q QueryInterface, confirm bool) (string, []string, []string, error) {
	json, suggestion, candidates, _ := jm.GetFilteredData(q, confirm)

	data, enc_err := json.Encode()

	if enc_err != nil {
		return "", []string{"", ""}, []string{"", ""}, errors.Wrap(enc_err, "failure json encode")
	}

	return string(data), suggestion, candidates, nil
}

func (jm *JsonManager) GetPretty(q QueryInterface, confirm bool) (string, []string, []string, error) {
	json, suggestion, candidates, _ := jm.GetFilteredData(q, confirm)
	s, err := json.EncodePretty()
	if err != nil {
		return "", []string{"", ""}, []string{"", ""}, errors.Wrap(err, "failure json encode")
	}
	return string(s), suggestion, candidates, nil
}

func (jm *JsonManager) GetFilteredData(q QueryInterface, confirm bool) (*simplejson.Json, []string, []string, error) {
	json := jm.origin

	lastKeyword := q.StringGetLastKeyword()
	keywords := q.StringGetKeywords()

	idx := 0
	if l := len(keywords); l == 0 {
		return json, []string{"", ""}, []string{}, nil
	} else if l > 0 {
		idx = l - 1
	}
	for _, keyword := range keywords[0:idx] {
		json, _ = getItem(json, keyword)
	}
	reg := regexp.MustCompile(`\[[0-9]*$`)

	suggest := jm.suggestion.Get(json, lastKeyword)
	candidateKeys := jm.suggestion.GetCandidateKeys(json, lastKeyword)
	// hash
	if len(reg.FindString(lastKeyword)) < 1 {
		candidateNum := len(candidateKeys)
		if j, exist := getItem(json, lastKeyword); exist && (confirm || candidateNum == 1) {
			json = j
			candidateKeys = []string{}
			if _, err := json.Array(); err == nil {
				suggest = jm.suggestion.Get(json, "")
			} else {
				suggest = []string{"", ""}
			}
		} else if candidateNum < 1 {
			json = j
			suggest = jm.suggestion.Get(json, "")
		}
	}
	return json, suggest, candidateKeys, nil
}

func (jm *JsonManager) GetCandidateKeys(q QueryInterface) []string {
	return jm.suggestion.GetCandidateKeys(jm.current, q.StringGetLastKeyword())
}

func getItem(json *simplejson.Json, s string) (*simplejson.Json, bool) {
	var result *simplejson.Json
	var exist bool

	re := regexp.MustCompile(`\[([0-9]+)\]`)
	matches := re.FindStringSubmatch(s)

	if s == "" {
		return json, false
	}

	// Query include [
	if len(matches) > 0 {
		index, _ := strconv.Atoi(matches[1])
		if a, err := json.Array(); err != nil {
			exist = false
		} else if len(a) < index {
			exist = false
		}
		result = json.GetIndex(index)
	} else {
		result, exist = json.CheckGet(s)
		if result == nil {
			result = &simplejson.Json{}
		}
	}
	return result, exist
}

func isEmptyJson(j *simplejson.Json) bool {
	switch j.Interface().(type) {
	case nil:
		return true
	default:
		return false
	}
}


================================================
FILE: json_manager_test.go
================================================
package jid

import (
	"bytes"
	"io"
	"testing"

	simplejson "github.com/bitly/go-simplejson"
	"github.com/stretchr/testify/assert"
)

func TestNewJson(t *testing.T) {
	var assert = assert.New(t)

	r := bytes.NewBufferString("{\"name\":\"go\"}")
	jm, e := NewJsonManager(r)

	rr := bytes.NewBufferString("{\"name\":\"go\"}")
	buf, _ := io.ReadAll(rr)
	sj, _ := simplejson.NewJson(buf)

	assert.Equal(jm, &JsonManager{
		current:    sj,
		origin:     sj,
		suggestion: NewSuggestion(),
	})
	assert.Nil(e)

	assert.Equal("go", jm.current.Get("name").MustString())
}

func TestNewJsonWithError(t *testing.T) {
	var assert = assert.New(t)

	r := bytes.NewBufferString("{\"name\":\"go\"")
	jm, e := NewJsonManager(r)

	assert.Nil(jm)
	assert.Regexp("invalid json format", e.Error())
}

func TestGet(t *testing.T) {
	var assert = assert.New(t)

	r := bytes.NewBufferString("{\"name\":\"go\"}")
	jm, _ := NewJsonManager(r)
	q := NewQueryWithString(".name")
	result, suggest, candidateKeys, err := jm.Get(q, false)

	assert.Nil(err)
	assert.Equal(`"go"`, result)
	assert.Equal([]string{``, ``}, suggest)
	assert.Equal([]string{}, candidateKeys)

	// data
	data := `{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}}`
	r = bytes.NewBufferString(data)
	jm, _ = NewJsonManager(r)

	// case 2
	q = NewQueryWithString(".abcde")
	result, suggest, candidateKeys, err = jm.Get(q, false)
	assert.Nil(err)
	//assert.Equal(`"2AA2"`, result)
	assert.Equal(`{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}}`, result)
	assert.Equal([]string{``, "abcde"}, suggest)

	// case 3
	q = NewQueryWithString(".abcde_fgh")
	result, suggest, candidateKeys, err = jm.Get(q, false)
	assert.Nil(err)
	assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, result)
	assert.Equal([]string{``, ``}, suggest)

	// case 4
	q = NewQueryWithString(".abcde_fgh.aaa[2]")
	result, suggest, candidateKeys, err = jm.Get(q, false)
	assert.Equal(`[1,2]`, result)

	// case 5
	q = NewQueryWithString(".abcde_fgh.aaa[3]")
	result, suggest, candidateKeys, err = jm.Get(q, false)
	assert.Nil(err)
	assert.Equal(`null`, result)

	// case 6
	q = NewQueryWithString(".abcde_fgh.aa")
	result, suggest, candidateKeys, err = jm.Get(q, false)
	assert.Nil(err)
	assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, result)
	assert.Equal([]string{`a`, `aaa`}, suggest)

	// case 7
	q = NewQueryWithString(".abcde_fgh.ac")
	result, suggest, candidateKeys, err = jm.Get(q, false)
	assert.Nil(err)
	assert.Equal(`null`, result)
	assert.Equal([]string{``, ``}, suggest)

	// data
	data = `{"abc":"2AA2","def":{"aaa":"bbb"}}`
	r = bytes.NewBufferString(data)
	jm, _ = NewJsonManager(r)

	// case 2
	q = NewQueryWithString(".def")
	result, suggest, candidateKeys, err = jm.Get(q, false)
	assert.Nil(err)
	assert.Equal(`{"aaa":"bbb"}`, result)
	assert.Equal([]string{``, ``}, suggest)
}

func TestGetPretty(t *testing.T) {
	var assert = assert.New(t)

	r := bytes.NewBufferString("{\"name\":\"go\"}")
	jm, _ := NewJsonManager(r)
	q := NewQueryWithString(".name")
	result, _, _, err := jm.GetPretty(q, true)

	assert.Nil(err)
	assert.Equal(`"go"`, result)
}

func TestGetItem(t *testing.T) {
	var assert = assert.New(t)

	rr := bytes.NewBufferString(`{"name":"go"}`)
	buf, _ := io.ReadAll(rr)
	sj, _ := simplejson.NewJson(buf)

	d, _ := getItem(sj, "")
	result, _ := d.Encode()
	assert.Equal(`{"name":"go"}`, string(result))

	d, _ = getItem(sj, "name")
	result, _ = d.Encode()
	assert.Equal(`"go"`, string(result))

	// case 2
	rr = bytes.NewBufferString(`{"name":"go","age":20}`)
	buf, _ = io.ReadAll(rr)
	sj, _ = simplejson.NewJson(buf)

	d, _ = getItem(sj, "age")
	result, _ = d.Encode()
	assert.Equal("20", string(result))

	// case 3
	rr = bytes.NewBufferString(`{"data":{"name":"go","age":20}}`)
	buf, _ = io.ReadAll(rr)
	sj, _ = simplejson.NewJson(buf)

	d, _ = getItem(sj, "data")
	d2, _ := getItem(d, "name")
	d3, _ := getItem(d, "age")
	result2, _ := d2.Encode()
	result3, _ := d3.Encode()

	assert.Equal(`"go"`, string(result2))
	assert.Equal(`20`, string(result3))

	// case 4
	rr = bytes.NewBufferString(`{"data":[{"name":"test","age":30},{"name":"go","age":20}]}`)
	buf, _ = io.ReadAll(rr)
	sj, _ = simplejson.NewJson(buf)

	d, _ = getItem(sj, "data")
	d2, _ = getItem(d, "[1]")
	d3, _ = getItem(d2, "name")
	result, _ = d3.Encode()

	assert.Equal(`"go"`, string(result))

	// case 5
	rr = bytes.NewBufferString(`[{"name":"go","age":20}]`)
	buf, _ = io.ReadAll(rr)
	sj, _ = simplejson.NewJson(buf)

	d, _ = getItem(sj, "")
	result, _ = d.Encode()
	assert.Equal(`[{"age":20,"name":"go"}]`, string(result))

	// case 6
	d, _ = getItem(sj, "[0]")
	result, _ = d.Encode()
	assert.Equal(`{"age":20,"name":"go"}`, string(result))

	// case 7  key contains '.'
	rr = bytes.NewBufferString(`{"na.me":"go","age":20}`)
	buf, _ = io.ReadAll(rr)
	sj, _ = simplejson.NewJson(buf)

	d, _ = getItem(sj, "na.me")
	result, _ = d.Encode()
	assert.Equal(`"go"`, string(result))

}

func TestGetFilteredData(t *testing.T) {
	var assert = assert.New(t)

	// data
	data := `{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"},"cc":{"a":[3,4]}}`
	r := bytes.NewBufferString(data)
	jm, _ := NewJsonManager(r)

	// case 1
	q := NewQueryWithString(".abcde")
	result, s, c, err := jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ := result.Encode()
	assert.Equal(`{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"},"cc":{"a":[3,4]}}`, string(d))
	//assert.Equal(`"2AA2"`, string(d))
	assert.Equal([]string{``, `abcde`}, s)
	assert.Equal([]string{"abcde", "abcde_fgh"}, c)

	// case 2
	q = NewQueryWithString(".abcde_fgh")
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, string(d))
	assert.Equal([]string{``, ``}, s)
	assert.Equal([]string{}, c)

	// case 3
	q = NewQueryWithString(".abcde_fgh.aaa[2]")
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`[1,2]`, string(d))
	assert.Equal([]string{`[`, `[`}, s)

	// case 4
	q = NewQueryWithString(".abcde_fgh.aaa[3]")
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`null`, string(d))
	assert.Equal([]string{``, ``}, s)

	// case 5
	q = NewQueryWithString(".abcde_fgh.aaa")
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`[123,"cccc",[1,2]]`, string(d))
	assert.Equal([]string{`[`, `[`}, s)

	// case 6
	q = NewQueryWithString(".abcde_fgh.aa")
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"}`, string(d))
	assert.Equal([]string{`a`, `aaa`}, s)

	// case 7
	q = NewQueryWithString(".abcde_fgh.aaa[")
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`[123,"cccc",[1,2]]`, string(d))
	assert.Equal([]string{``, `[`}, s)

	// case 8
	q = NewQueryWithString(".")
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`{"abcde":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"},"cc":{"a":[3,4]}}`, string(d))
	assert.Equal([]string{``, ``}, s)

	// case 9
	q = NewQueryWithString(".cc.")
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`{"a":[3,4]}`, string(d))
	assert.Equal([]string{`a`, `a`}, s)
	assert.Equal([]string{"a"}, c)

	// case 2-1
	data = `{"arraytest":[{"aaa":123,"aab":234},[1,2]]}`
	r = bytes.NewBufferString(data)
	jm, _ = NewJsonManager(r)

	q = NewQueryWithString(".arraytest[0]")
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`{"aaa":123,"aab":234}`, string(d))
	assert.Equal([]string{``, ``}, s)
	assert.Equal([]string{}, c)

	// case 3-1
	data = `{"aa":"abcde","bb":{"foo":"bar"}}`
	r = bytes.NewBufferString(data)
	jm, _ = NewJsonManager(r)

	q = NewQueryWithString(".bb")
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`{"foo":"bar"}`, string(d))
	assert.Equal([]string{``, ``}, s)
	assert.Equal([]string{}, c)

	// case 4-1
	data = `[{"name": "simeji"},{"name": "simeji2"}]`
	r = bytes.NewBufferString(data)
	jm, _ = NewJsonManager(r)

	q = NewQueryWithString("")
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`[{"name":"simeji"},{"name":"simeji2"}]`, string(d))
	assert.Equal([]string{``, ``}, s)
	assert.Equal([]string{}, c)

	// case 5-1
	data = `{"PrivateName":"simei", "PrivateAlias": "simeji2"}`
	r = bytes.NewBufferString(data)
	jm, _ = NewJsonManager(r)

	q = NewQueryWithString(".Private")
	result, s, c, err = jm.GetFilteredData(q, false)
	d, _ = result.Encode()
	assert.Equal([]string{``, `Private`}, s)
	assert.Equal([]string{"PrivateAlias", "PrivateName"}, c)

}

func TestGetFilteredDataWithMatchQuery(t *testing.T) {
	var assert = assert.New(t)

	data := `{"name":[1,2,3], "naming":{"account":"simeji"}, "test":"simeji", "testing":"ok"}`
	r := bytes.NewBufferString(data)
	jm, _ := NewJsonManager(r)

	q := NewQueryWithString(`.name`)
	result, s, c, err := jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ := result.Encode()
	assert.Equal(`[1,2,3]`, string(d))
	assert.Equal([]string{"[", "["}, s)
	assert.Equal([]string{}, c)

	q = NewQueryWithString(`.naming`)
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`{"account":"simeji"}`, string(d))
	assert.Equal([]string{"", ""}, s)
	assert.Equal([]string{}, c)

	q = NewQueryWithString(`.naming.`)
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`{"account":"simeji"}`, string(d))
	assert.Equal([]string{"account", "account"}, s)
	assert.Equal([]string{"account"}, c)

	q = NewQueryWithString(`.test`)
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`{"name":[1,2,3],"naming":{"account":"simeji"},"test":"simeji","testing":"ok"}`, string(d))
	assert.Equal([]string{"", "test"}, s)
	assert.Equal([]string{"test", "testing"}, c)
}

func TestGetFilteredDataWithContainDots(t *testing.T) {
	var assert = assert.New(t)

	// data
	data := `{"abc.de":"2AA2","abcde_fgh":{"aaa":[123,"cccc",[1,2]],"c":"JJJJ"},"cc":{"a":[3,4]}}`
	r := bytes.NewBufferString(data)
	jm, _ := NewJsonManager(r)

	// case 1
	q := NewQueryWithString(`.\"abc.de\"`)
	result, s, c, err := jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ := result.Encode()
	assert.Equal(`"2AA2"`, string(d))
	assert.Equal([]string{``, ``}, s)
	assert.Equal([]string{}, c)

	// case 2
	q = NewQueryWithString(`."abc.de"`)
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`null`, string(d))
	assert.Equal([]string{``, ``}, s)
	assert.Equal([]string{}, c)

	// case 3
	q = NewQueryWithString(`.abc.de`)
	result, s, c, err = jm.GetFilteredData(q, false)
	assert.Nil(err)
	d, _ = result.Encode()
	assert.Equal(`null`, string(d))
	assert.Equal([]string{"", ""}, s)
	assert.Equal([]string{}, c)
}

func TestGetCandidateKeys(t *testing.T) {
	var assert = assert.New(t)
	data := `{"name":[1,2,3], "naming":{"account":"simeji"}, "test":"simeji", "testing":"ok"}`
	r := bytes.NewBufferString(data)
	jm, _ := NewJsonManager(r)

	q := NewQueryWithString(`.n`)

	keys := jm.GetCandidateKeys(q)
	assert.Equal([]string{"name", "naming"}, keys)

	q = NewQueryWithString(`.`)
	keys = jm.GetCandidateKeys(q)
	assert.Equal([]string{"name", "naming", "test", "testing"}, keys)

	q = NewQueryWithString(`.test`)
	keys = jm.GetCandidateKeys(q)
	assert.Equal([]string{"test", "testing"}, keys)

	q = NewQueryWithString(`.testi`)
	keys = jm.GetCandidateKeys(q)
	assert.Equal([]string{"testing"}, keys)

	q = NewQueryWithString(`.testia`)
	keys = jm.GetCandidateKeys(q)
	assert.Equal([]string{}, keys)
}

func TestGetCurrentKeys(t *testing.T) {
	var assert = assert.New(t)
	r := bytes.NewBufferString(`{"name":"go","age":20,"weight":60}`)
	buf, _ := io.ReadAll(r)
	sj, _ := simplejson.NewJson(buf)

	keys := getCurrentKeys(sj)
	assert.Equal([]string{"age", "name", "weight"}, keys)

	r = bytes.NewBufferString(`[2,3,"aa"]`)
	buf, _ = io.ReadAll(r)
	sj, _ = simplejson.NewJson(buf)

	keys = getCurrentKeys(sj)
	assert.Equal([]string{}, keys)
}

func TestIsEmptyJson(t *testing.T) {
	var assert = assert.New(t)
	r := bytes.NewBufferString(`{"name":"go"}`)
	buf, _ := io.ReadAll(r)
	sj, _ := simplejson.NewJson(buf)

	assert.Equal(false, isEmptyJson(sj))
	assert.Equal(true, isEmptyJson(&simplejson.Json{}))
}


================================================
FILE: query.go
================================================
package jid

import (
	"regexp"
	"strings"

	runewidth "github.com/mattn/go-runewidth"
)

type QueryInterface interface {
	Get() []rune
	Set(query []rune) []rune
	Insert(query []rune, idx int) []rune
	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)
	StringGet() string
	StringSet(query string) string
	StringInsert(query string, idx int) string
	StringAdd(query string) string
	StringGetKeywords() []string
	StringGetLastKeyword() string
	StringPopKeyword() (string, []rune)
}

type Query struct {
	query    *[]rune
	complete *[]rune
}

func NewQuery(query []rune) *Query {
	q := &Query{
		query:    &[]rune{},
		complete: &[]rune{},
	}
	_ = q.Set(query)
	return q
}
func NewQueryWithString(query string) *Query {
	return NewQuery([]rune(query))
}

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
	}
	return q.Get()
}

func (q *Query) Insert(query []rune, idx int) []rune {
	qq := q.Get()
	if idx == 0 {
		qq = append(query, qq...)
	} else if idx > 0 && len(qq) >= idx {
		_q := make([]rune, idx+len(query)-1)
		copy(_q, qq[:idx])
		qq = append(append(_q, query...), qq[idx:]...)
	}
	return q.Set(qq)
}

func (q *Query) StringInsert(query string, idx int) string {
	return string(q.Insert([]rune(query), idx))
}

func (q *Query) Add(query []rune) []rune {
	return q.Set(append(q.Get(), query...))
}

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:]...)
	}
	_ = q.Set(qq)
	return d
}

func (q *Query) Clear() []rune {
	return q.Set([]rune(""))
}

func (q *Query) GetKeywords() [][]rune {
	qq := *q.query

	if qq == nil || string(qq) == "" {
		return [][]rune{}
	}

	splitQuery := []string{}
	rr := []rune{}
	enclosed := true
	ql := len(*q.query)
	for i := 0; i < ql; i++ {
		r := qq[i]
		if ii := i + 1; r == '\\' && ql > ii && qq[ii] == '"' {
			enclosed = !enclosed
			i++ // skip '"(double quortation)'
			continue
		}
		if enclosed && r == '.' {
			splitQuery = append(splitQuery, string(rr))
			rr = []rune{}
		} else {
			rr = append(rr, r)
		}
	}
	if rr != nil {
		v := []string{string(rr)}
		if !enclosed {
			v = strings.Split(string(rr), ".")
		}
		splitQuery = append(splitQuery, v...)
	}
	lastIdx := len(splitQuery) - 1

	keywords := [][]rune{}
	for i, keyword := range splitQuery {
		if keyword != "" || i == lastIdx {
			re := regexp.MustCompile(`\[[0-9]*\]?`)
			matchIndexes := re.FindAllStringIndex(keyword, -1)
			if len(matchIndexes) < 1 {
				keywords = append(keywords, []rune(keyword))
			} else {
				if matchIndexes[0][0] > 0 {
					keywords = append(keywords, []rune(keyword[0:matchIndexes[0][0]]))
				}
				for _, matchIndex := range matchIndexes {
					k := keyword[matchIndex[0]:matchIndex[1]]
					keywords = append(keywords, []rune(k))
				}
			}
		}
	}
	return keywords
}

func (q *Query) GetLastKeyword() []rune {
	keywords := q.GetKeywords()
	if l := len(keywords); l > 0 {
		return keywords[l-1]
	}
	return []rune("")
}

func (q *Query) StringGetLastKeyword() string {
	return string(q.GetLastKeyword())
}

func (q *Query) PopKeyword() ([]rune, []rune) {
	keyword := q.GetLastKeyword()
	nq := string(keyword)
	qq := q.StringGet()

	for _, r := range keyword {
		if r == '.' {
			nq = `\"` + string(keyword) + `\"`
			break
		}
	}
	re := regexp.MustCompile(`(\.)?(\\")?` + regexp.QuoteMeta(nq) + "$")

	qq = re.ReplaceAllString(qq, "")

	query := q.Set([]rune(qq))
	return keyword, query
}

func (q *Query) StringGet() string {
	return string(q.Get())
}

func (q *Query) StringSet(query string) string {
	return string(q.Set([]rune(query)))
}

func (q *Query) StringAdd(query string) string {
	return string(q.Add([]rune(query)))
}

func (q *Query) StringGetKeywords() []string {
	var keywords []string
	for _, keyword := range q.GetKeywords() {
		keywords = append(keywords, string(keyword))
	}
	return keywords
}

func (q *Query) StringPopKeyword() (string, []rune) {
	keyword, query := q.PopKeyword()
	return string(keyword), query
}

func validate(r []rune) bool {
	s := string(r)
	if s == "" {
		return true
	}
	if regexp.MustCompile(`^[^.]`).MatchString(s) {
		return false
	}
	if regexp.MustCompile(`\.{2,}`).MatchString(s) {
		return false
	}
	if regexp.MustCompile(`\[[0-9]*\][^\.\[]`).MatchString(s) {
		return false
	}
	if regexp.MustCompile(`\[{2,}|\]{2,}`).MatchString(s) {
		return false
	}
	if regexp.MustCompile(`.\.\[`).MatchString(s) {
		return false
	}
	return true
}


================================================
FILE: query_test.go
================================================
package jid

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestValidate(t *testing.T) {
	var assert = assert.New(t)

	assert.True(validate([]rune(".test.name")))
	assert.True(validate([]rune(".test.name.")))
	assert.True(validate([]rune(".test[0].name.")))
	assert.True(validate([]rune(".[0].name.")))
	assert.True(validate([]rune(".name[9][1]")))
	assert.True(validate([]rune(".[0][1].name.")))

	assert.False(validate([]rune("[0].name.")))
	assert.False(validate([]rune(".test[0]].name.")))
	assert.False(validate([]rune(".test..name")))
	assert.False(validate([]rune(".test.name..")))
	assert.False(validate([]rune(".test[[0]].name.")))
	assert.False(validate([]rune(".test[0]name.")))
	assert.False(validate([]rune(".test.[0].name.")))
}

func TestNewQuery(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(".name")
	q := NewQuery(v)

	assert.Equal(*q.query, []rune(".name"))
	assert.Equal(*q.complete, []rune(""))
}

func TestNewQueryWithInvalidQuery(t *testing.T) {
	var assert = assert.New(t)

	v := []rune("name")
	q := NewQuery(v)

	assert.Equal(*q.query, []rune(""))
	assert.Equal(*q.complete, []rune(""))
}

func TestNewQueryWithString(t *testing.T) {
	var assert = assert.New(t)

	q := NewQueryWithString(".name")

	assert.Equal(*q.query, []rune(".name"))
	assert.Equal(*q.complete, []rune(""))
}

func TestNewQueryWithStringWithInvalidQuery(t *testing.T) {
	var assert = assert.New(t)

	q := NewQueryWithString("name")

	assert.Equal(*q.query, []rune(""))
	assert.Equal(*q.complete, []rune(""))
}

func TestQueryGet(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(".test")
	q := NewQuery(v)

	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)

	v := []rune(".hello")
	q := NewQuery(v)

	assert.Equal([]rune(".world"), q.Set([]rune(".world")))
	assert.Equal("", string(q.Set([]rune(""))))
}

func TestQuerySetWithInvalidQuery(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(".hello")
	q := NewQuery(v)

	assert.Equal(q.Set([]rune("world")), []rune(".hello"))
}

func TestQueryAdd(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(".hello")
	q := NewQuery(v)

	assert.Equal(q.Add([]rune("world")), []rune(".helloworld"))
}
func TestQueryInsert(t *testing.T) {
	var assert = assert.New(t)
	v := []rune(".hello.world")
	q := NewQuery(v)

	assert.Equal([]rune(".hello.world"), q.Insert([]rune("w"), 0))
	assert.Equal([]rune(".whello.world"), q.Insert([]rune("w"), 1))
	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.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)
	q := NewQueryWithString(".hello.world")

	assert.Equal(".hello.world", q.StringInsert("w", 0))
	assert.Equal(".whello.world", q.StringInsert("w", 1))
	assert.Equal(".wwhello.world", q.StringInsert("w", 1))
	assert.Equal(".wwhello.world", q.StringInsert(".", 1))
	assert.Equal(".wwh.ello.world", q.StringInsert(".", 4))
	assert.Equal(".wwh.ello.worlda", q.StringInsert("a", 15))
	assert.Equal(".wwh.ello.worlda", q.StringInsert("a", 20))
}

func TestQueryClear(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(".test")
	q := NewQuery(v)

	assert.Equal(q.Clear(), []rune(""))
}

func TestQueryDelete(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(".helloworld")
	q := NewQuery(v)

	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(""), 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) {
	var assert = assert.New(t)

	v := []rune(".test.name")
	q := NewQuery(v)
	assert.Equal([][]rune{
		[]rune("test"),
		[]rune("name"),
	}, q.GetKeywords())

	v = []rune("")
	q = NewQuery(v)
	assert.Equal([][]rune{}, q.GetKeywords())

	v = []rune(".test.name.")
	q = NewQuery(v)
	assert.Equal([][]rune{
		[]rune("test"),
		[]rune("name"),
		[]rune(""),
	}, q.GetKeywords())

	v = []rune(".hello")
	q = NewQuery(v)
	assert.Equal(q.GetKeywords(), [][]rune{
		[]rune("hello"),
	})

	v = []rune(".hello.")
	q = NewQuery(v)
	assert.Equal(q.GetKeywords(), [][]rune{
		[]rune("hello"),
		[]rune(""),
	})

	v = []rune(".hello[")
	q = NewQuery(v)
	assert.Equal(q.GetKeywords(), [][]rune{
		[]rune("hello"),
		[]rune("["),
	})

	v = []rune(".hello[12")
	q = NewQuery(v)
	assert.Equal(q.GetKeywords(), [][]rune{
		[]rune("hello"),
		[]rune("[12"),
	})

	v = []rune(".hello[0]")
	q = NewQuery(v)
	assert.Equal(q.GetKeywords(), [][]rune{
		[]rune("hello"),
		[]rune("[0]"),
	})

	v = []rune(".hello[13][0]")
	q = NewQuery(v)
	assert.Equal(q.GetKeywords(), [][]rune{
		[]rune("hello"),
		[]rune("[13]"),
		[]rune("[0]"),
	})

	v = []rune(".[3][23].hello[13][0]")
	q = NewQuery(v)
	assert.Equal(q.GetKeywords(), [][]rune{
		[]rune("[3]"),
		[]rune("[23]"),
		[]rune("hello"),
		[]rune("[13]"),
		[]rune("[0]"),
	})

}
func TestGetKeywordsWithDots(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(`.test.\"na.me\"`)
	q := NewQuery(v)
	assert.Equal([][]rune{
		[]rune("test"),
		[]rune("na.me"),
	}, q.GetKeywords())

	v = []rune(`.test.\"na.me\`)
	q = NewQuery(v)
	assert.Equal([][]rune{
		[]rune("test"),
		[]rune("na"),
		[]rune(`me\`),
	}, q.GetKeywords())

}

func TestGetLastKeyword(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(".test.name")
	q := NewQuery(v)
	assert.Equal(q.GetLastKeyword(), []rune("name"))

	v = []rune(".test.")
	q = NewQuery(v)
	assert.Equal(q.GetLastKeyword(), []rune(""))

	v = []rune(".test")
	q = NewQuery(v)
	assert.Equal(q.GetLastKeyword(), []rune("test"))

	v = []rune("")
	q = NewQuery(v)
	assert.Equal(q.GetLastKeyword(), []rune(""))
}

func TestStringGetLastKeyword(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(".test.name")
	q := NewQuery(v)
	assert.Equal(q.StringGetLastKeyword(), "name")

	v = []rune(".test.")
	q = NewQuery(v)
	assert.Equal(q.StringGetLastKeyword(), "")

	v = []rune(".test")
	q = NewQuery(v)
	assert.Equal(q.StringGetLastKeyword(), "test")

	v = []rune("")
	q = NewQuery(v)
	assert.Equal(q.StringGetLastKeyword(), "")
}

func TestPopKeyword(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(".test.name")
	q := NewQuery(v)
	k, query := q.PopKeyword()
	assert.Equal([]rune("name"), k)
	assert.Equal([]rune(".test"), query)
	assert.Equal([]rune(".test"), q.Get())

	v = []rune(".a[0")
	q = NewQuery(v)
	k, query = q.PopKeyword()
	assert.Equal([]rune("[0"), k)
	assert.Equal([]rune(".a"), query)
	assert.Equal([]rune(".a"), q.Get())

	k, query = q.PopKeyword()
	assert.Equal([]rune("a"), k)
	assert.Equal([]rune(""), query)
	assert.Equal([]rune(""), q.Get())

	v = []rune(".")
	q = NewQuery(v)
	k, query = q.PopKeyword()
	assert.Equal([]rune(""), k)
	assert.Equal([]rune(""), query)
	assert.Equal([]rune(""), q.Get())

	v = []rune(".test.name.")
	q = NewQuery(v)
	k, query = q.PopKeyword()
	assert.Equal([]rune(""), k)
	assert.Equal([]rune(".test.name"), query)
	assert.Equal([]rune(".test.name"), q.Get())

	v = []rune(`.name.\"te.st\"`)
	q = NewQuery(v)
	k, query = q.PopKeyword()
	assert.Equal([]rune("te.st"), k)
	assert.Equal([]rune(".name"), query)
	assert.Equal([]rune(".name"), q.Get())

	v = []rune(`.name.\"te.st\".hoge`)
	q = NewQuery(v)
	k, query = q.PopKeyword()
	assert.Equal([]rune("hoge"), k)
	assert.Equal([]rune(`.name.\"te.st\"`), query)
	assert.Equal([]rune(`.name.\"te.st\"`), q.Get())

	v = []rune(`.name.\"te`)
	q = NewQuery(v)
	k, query = q.PopKeyword()
	assert.Equal([]rune(`te`), k)
	assert.Equal([]rune(`.name`), query)
	assert.Equal([]rune(`.name`), q.Get())
}

func TestQueryStringGet(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(".test")
	q := NewQuery(v)

	assert.Equal(q.StringGet(), ".test")
}

func TestQueryStringSet(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(".hello")
	q := NewQuery(v)

	assert.Equal(q.StringSet(".world"), ".world")
}

func TestQueryStringAdd(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(".hello")
	q := NewQuery(v)

	assert.Equal(q.StringAdd("world"), ".helloworld")
}

func TestStringGetKeywords(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(".test.name")
	q := NewQuery(v)
	assert.Equal(q.StringGetKeywords(), []string{
		"test",
		"name",
	})

	v = []rune(".test.name")
	q = NewQuery(v)
	assert.Equal(q.StringGetKeywords(), []string{
		"test",
		"name",
	})

	v = []rune("")
	q = NewQuery(v)
	kws := q.StringGetKeywords()
	assert.Equal([]string(nil), kws)
	assert.Equal(0, len(kws))
}

func TestStringPopKeyword(t *testing.T) {
	var assert = assert.New(t)

	v := []rune(".test.name")
	q := NewQuery(v)
	k, query := q.StringPopKeyword()
	assert.Equal(k, "name")
	assert.Equal(query, []rune(".test"))
	assert.Equal(q.Get(), []rune(".test"))

	v = []rune(".test.name.")
	q = NewQuery(v)
	k, query = q.StringPopKeyword()
	assert.Equal(k, "")
	assert.Equal(query, []rune(".test.name"))
	assert.Equal(q.Get(), []rune(".test.name"))

	v = []rune(".test.name[23]")
	q = NewQuery(v)
	k, query = q.StringPopKeyword()
	assert.Equal(k, "[23]")
	assert.Equal(query, []rune(".test.name"))
	assert.Equal(q.Get(), []rune(".test.name"))
}


================================================
FILE: suggestion.go
================================================
package jid

import (
	"regexp"
	"sort"
	"strings"

	simplejson "github.com/bitly/go-simplejson"
)

type SuggestionInterface interface {
	Get(json *simplejson.Json, keyword string) []string
	GetCandidateKeys(json *simplejson.Json, keyword string) []string
}

type SuggestionDataType int

const (
	UNKNOWN SuggestionDataType = iota
	ARRAY
	MAP
	NUMBER
	STRING
	BOOL
)

type Suggestion struct {
}

func NewSuggestion() *Suggestion {
	return &Suggestion{}
}

func (s *Suggestion) Get(json *simplejson.Json, keyword string) []string {
	var completion string
	var suggestion string

	if a, err := json.Array(); err == nil {
		if len(a) > 1 {
			kw := regexp.MustCompile(`\[([0-9]+)?\]?`).FindString(keyword)
			if kw == "" {
				return []string{"[", "["}
			} else if kw == "[" {
				return []string{"", "["}
			}
			return []string{strings.Replace(kw+"]", kw, "", -1), kw + "]"}
		}
		return []string{strings.Replace(`[0]`, keyword, "", -1), `[0]`}
	}

	candidateKeys := s.GetCandidateKeys(json, keyword)

	if keyword == "" {
		if l := len(candidateKeys); l > 1 {
			return []string{"", ""}
		} else if l == 1 {
			return []string{candidateKeys[0], candidateKeys[0]}
		}
	}

	for _, key := range candidateKeys {
		// first
		if suggestion == "" && key != "" {
			suggestion = key
		} else {
			axis := suggestion
			if len(suggestion) > len(key) {
				axis = key
			}
			max := 0
			for i, _ := range axis {
				if suggestion[i] != key[i] {
					break
				}
				max = i
			}
			if max == 0 {
				suggestion = ""
				break
			}
			suggestion = suggestion[0 : max+1]
		}
	}
	if reg, err := regexp.Compile("(?i)^" + keyword); err == nil {
		completion = reg.ReplaceAllString(suggestion, "")
	}
	return []string{completion, suggestion}
}

func (s *Suggestion) GetCandidateKeys(json *simplejson.Json, keyword string) []string {
	candidates := []string{}

	if _, err := json.Array(); err == nil {
		return []string{}
	}

	if keyword == "" {
		return getCurrentKeys(json)
	}

	reg, err := regexp.Compile(`(?i)^(\\")?` + keyword + `(\\")?`)
	if err != nil {
		return []string{}
	}
	for _, key := range getCurrentKeys(json) {
		if reg.MatchString(key) {
			candidates = append(candidates, key)
		}
	}
	return candidates
}

func getCurrentKeys(json *simplejson.Json) []string {

	kk := []string{}
	m, err := json.Map()

	if err != nil {
		return kk
	}
	for k := range m {
		kk = append(kk, k)
	}
	sort.Strings(kk)

	keys := []string{}
	for _, k := range kk {
		if strings.Contains(k, ".") {
			var sb strings.Builder
			sb.Grow(len(k) + 4)
			sb.WriteString(`\"`)
			sb.WriteString(k)
			sb.WriteString(`\"`)
			k = sb.String()
		}
		keys = append(keys, k)
	}
	return keys
}

func (s *Suggestion) GetCurrentType(json *simplejson.Json) SuggestionDataType {
	if _, err := json.Array(); err == nil {
		return ARRAY
	} else if _, err = json.Map(); err == nil {
		return MAP
	} else if _, err = json.String(); err == nil {
		return STRING
	}
	return UNKNOWN
}


================================================
FILE: suggestion_test.go
================================================
package jid

import (
	"bytes"
	"io"
	"testing"

	simplejson "github.com/bitly/go-simplejson"
	"github.com/stretchr/testify/assert"
)

func TestNewSuggestion(t *testing.T) {
	var assert = assert.New(t)
	assert.Equal(NewSuggestion(), &Suggestion{})
}

func TestSuggestionGet(t *testing.T) {
	var assert = assert.New(t)
	j := createJson(`{"name":"simeji-github"}`)
	s := NewSuggestion()

	j = createJson(`{"name":"simeji-github", "naming":"simeji", "nickname":"simejisimeji"}`)
	assert.Equal([]string{"m", "nam"}, s.Get(j, "na"))

	j = createJson(`{"abcde":"simeji-github", "abcdef":"simeji", "ab":"simejisimeji"}`)
	assert.Equal([]string{"", ""}, s.Get(j, ""))
	assert.Equal([]string{"b", "ab"}, s.Get(j, "a"))
	assert.Equal([]string{"de", "abcde"}, s.Get(j, "abc"))
	assert.Equal([]string{"", "abcde"}, s.Get(j, "abcde"))

	j = createJson(`["zero"]`)
	assert.Equal([]string{"[0]", "[0]"}, s.Get(j, ""))
	assert.Equal([]string{"0]", "[0]"}, s.Get(j, "["))
	assert.Equal([]string{"]", "[0]"}, s.Get(j, "[0"))

	j = createJson(`["zero", "one"]`)
	assert.Equal([]string{"[", "["}, s.Get(j, ""))
	assert.Equal([]string{"", "["}, s.Get(j, "["))
	assert.Equal([]string{"]", "[0]"}, s.Get(j, "[0"))

	j = createJson(`{"Abcabc":"simeji-github", "Abcdef":"simeji"}`)
	assert.Equal([]string{"bc", "Abc"}, s.Get(j, "a"))
	assert.Equal([]string{"c", "Abc"}, s.Get(j, "ab"))

	j = createJson(`{"RootDeviceNames":"simeji-github", "RootDeviceType":"simeji"}`)
	assert.Equal([]string{"ootDevice", "RootDevice"}, s.Get(j, "r"))
	assert.Equal([]string{"ootDevice", "RootDevice"}, s.Get(j, "R"))
}

func TestSuggestionGetCurrentType(t *testing.T) {
	var assert = assert.New(t)
	s := NewSuggestion()

	j := createJson(`[1,2,3]`)
	assert.Equal(ARRAY, s.GetCurrentType(j))
	j = createJson(`{"name":[1,2,3], "naming":{"account":"simeji"}, "test":"simeji", "testing":"ok"}`)
	assert.Equal(MAP, s.GetCurrentType(j))
	j = createJson(`"name"`)
	assert.Equal(STRING, s.GetCurrentType(j))
	j = createJson("1")
	assert.Equal(UNKNOWN, s.GetCurrentType(j))
}

func TestSuggestionGetCandidateKeys(t *testing.T) {
	var assert = assert.New(t)
	j := createJson(`{"naming":"simeji", "nickname":"simejisimeji", "city":"tokyo", "name":"simeji-github" }`)
	s := NewSuggestion()

	assert.Equal([]string{"city", "name", "naming", "nickname"}, s.GetCandidateKeys(j, ""))
	assert.Equal([]string{"name", "naming", "nickname"}, s.GetCandidateKeys(j, "n"))
	assert.Equal([]string{"name", "naming"}, s.GetCandidateKeys(j, "na"))
	assert.Equal([]string{}, s.GetCandidateKeys(j, "nana"))

	j = createJson(`{"abcde":"simeji-github", "abcdef":"simeji", "ab":"simejisimeji"}`)
	assert.Equal([]string{"abcde", "abcdef"}, s.GetCandidateKeys(j, "abcde"))

	j = createJson(`{"name":"simeji-github"}`)
	assert.Equal([]string{"name"}, s.GetCandidateKeys(j, ""))

	j = createJson(`{"n":"simeji-github"}`)
	assert.Equal([]string{"n"}, s.GetCandidateKeys(j, ""))

	j = createJson(`[1,2,"aa"]`)
	s = NewSuggestion()
	assert.Equal([]string{}, s.GetCandidateKeys(j, "["))
}
func TestSuggestionGetCandidateKeysWithDots(t *testing.T) {
	var assert = assert.New(t)
	j := createJson(`{"nam.ing":"simeji", "nickname":"simejisimeji", "city":"tokyo", "name":"simeji-github" }`)
	s := NewSuggestion()

	assert.Equal([]string{"city", `\"nam.ing\"`, "name", "nickname"}, s.GetCandidateKeys(j, ""))
	assert.Equal([]string{`\"nam.ing\"`, "name", "nickname"}, s.GetCandidateKeys(j, "n"))
}

func createJson(s string) *simplejson.Json {
	r := bytes.NewBufferString(s)
	buf, _ := io.ReadAll(r)
	j, _ := simplejson.NewJson(buf)
	return j
}


================================================
FILE: terminal.go
================================================
package jid

import (
	"fmt"
	"io"
	"regexp"
	"strings"

	runewidth "github.com/mattn/go-runewidth"
	termbox "github.com/nsf/termbox-go"
	"github.com/nwidger/jsoncolor"
)

type Terminal struct {
	defaultY   int
	prompt     string
	formatter  *jsoncolor.Formatter
	monochrome bool
	outputArea *[][]termbox.Cell
}

type TerminalDrawAttributes struct {
	Query           string
	Contents        []string
	CandidateIndex  int
	ContentsOffsetY int
	Complete        string
	Candidates      []string
	CursorOffset    int
}

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) error {

	query := attr.Query
	complete := attr.Complete
	rows := attr.Contents
	candidates := attr.Candidates
	candidateidx := attr.CandidateIndex
	contentOffsetY := attr.ContentsOffsetY

	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)

	y := t.defaultY
	_, h := termbox.Size()

	t.drawFilterLine(query, complete)

	if len(candidates) > 0 {
		y = t.drawCandidates(0, t.defaultY, candidateidx, candidates)
	}

	cellsArr, err := t.rowsToCells(rows)
	if err != nil {
		return err
	}

	for idx, cells := range cellsArr {
		i := idx - contentOffsetY
		if i >= 0 {
			t.drawCells(0, i+y, cells)
		}
		if i > h {
			break
		}
	}

	termbox.SetCursor(len(t.prompt)+attr.CursorOffset, 0)

	termbox.Flush()
	return nil
}

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
		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,
			})
		}
		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(io.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
	}
}

func (t *Terminal) drawCandidates(x int, y int, index int, candidates []string) int {
	color := termbox.ColorBlack
	backgroundColor := termbox.ColorWhite

	w, _ := termbox.Size()

	ss := candidates[index]
	re := regexp.MustCompile("[[:space:]]" + regexp.QuoteMeta(ss) + "[[:space:]]")

	var rows []string
	var str string
	for _, word := range candidates {
		combine := " "
		if l := len(str); l+len(word)+1 >= w {
			rows = append(rows, str+" ")
			str = ""
		}
		str += combine + word
	}
	rows = append(rows, str+" ")

	for i, row := range rows {
		match := re.FindStringIndex(row)
		var c termbox.Attribute
		ii := 0
		for k, s := range row {
			c = color
			backgroundColor = termbox.ColorMagenta
			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)
}
Download .txt
gitextract_eacgd7ye/

├── .circleci/
│   └── config.yml
├── .gitignore
├── .goreleaser.yml
├── ChangeLog
├── LICENSE
├── README.md
├── engine.go
├── engine_test.go
├── go.mod
├── go.sum
├── json_manager.go
├── json_manager_test.go
├── query.go
├── query_test.go
├── suggestion.go
├── suggestion_test.go
└── terminal.go
Download .txt
SYMBOL INDEX (154 symbols across 9 files)

FILE: engine.go
  constant DefaultY (line 11) | DefaultY     int    = 1
  constant FilterPrompt (line 12) | FilterPrompt string = "[Filter]> "
  type EngineInterface (line 15) | type EngineInterface interface
  type EngineResultInterface (line 20) | type EngineResultInterface interface
  type Engine (line 26) | type Engine struct
    method GetQuery (line 86) | func (e *Engine) GetQuery() QueryInterface {
    method Run (line 90) | func (e *Engine) Run() EngineResultInterface {
    method getContents (line 197) | func (e *Engine) getContents() []string {
    method setCandidateData (line 209) | func (e *Engine) setCandidateData() {
    method confirmCandidate (line 223) | func (e *Engine) confirmCandidate() {
    method deleteChar (line 231) | func (e *Engine) deleteChar() {
    method deleteLineQuery (line 239) | func (e *Engine) deleteLineQuery() {
    method scrollToBelow (line 244) | func (e *Engine) scrollToBelow() {
    method scrollToAbove (line 248) | func (e *Engine) scrollToAbove() {
    method scrollToBottom (line 254) | func (e *Engine) scrollToBottom(rownum int) {
    method scrollToTop (line 258) | func (e *Engine) scrollToTop() {
    method scrollPageDown (line 262) | func (e *Engine) scrollPageDown(rownum int, height int) {
    method scrollPageUp (line 270) | func (e *Engine) scrollPageUp(height int) {
    method toggleKeymode (line 278) | func (e *Engine) toggleKeymode() {
    method deleteWordBackward (line 281) | func (e *Engine) deleteWordBackward() {
    method tabAction (line 287) | func (e *Engine) tabAction() {
    method escapeCandidateMode (line 303) | func (e *Engine) escapeCandidateMode() {
    method inputChar (line 306) | func (e *Engine) inputChar(ch rune) {
    method moveCursorBackward (line 311) | func (e *Engine) moveCursorBackward() {
    method moveCursorForward (line 317) | func (e *Engine) moveCursorForward() {
    method moveCursorWordBackwark (line 323) | func (e *Engine) moveCursorWordBackwark() {
    method moveCursorWordForward (line 325) | func (e *Engine) moveCursorWordForward() {
    method moveCursorToTop (line 327) | func (e *Engine) moveCursorToTop() {
    method moveCursorToEnd (line 330) | func (e *Engine) moveCursorToEnd() {
  type EngineAttribute (line 41) | type EngineAttribute struct
  function NewEngine (line 47) | func NewEngine(s io.Reader, ea *EngineAttribute) (EngineInterface, error) {
  type EngineResult (line 69) | type EngineResult struct
    method GetQueryString (line 75) | func (er *EngineResult) GetQueryString() string {
    method GetContent (line 79) | func (er *EngineResult) GetContent() string {
    method GetError (line 82) | func (er *EngineResult) GetError() error {

FILE: engine_test.go
  function TestNewEngine (line 11) | func TestNewEngine(t *testing.T) {
  function TestNewEngineWithQuery (line 28) | func TestNewEngineWithQuery(t *testing.T) {
  function TestDeleteChar (line 43) | func TestDeleteChar(t *testing.T) {
  function TestDeleteWordBackward (line 54) | func TestDeleteWordBackward(t *testing.T) {
  function TestDeleteLineQuery (line 74) | func TestDeleteLineQuery(t *testing.T) {
  function TestScrollToAbove (line 84) | func TestScrollToAbove(t *testing.T) {
  function TestScrollToBelow (line 97) | func TestScrollToBelow(t *testing.T) {
  function TestScrollToBottomAndTop (line 106) | func TestScrollToBottomAndTop(t *testing.T) {
  function TestScrollPageUpDown (line 117) | func TestScrollPageUpDown(t *testing.T) {
  function TestGetContents (line 154) | func TestGetContents(t *testing.T) {
  function TestSetCandidateData (line 177) | func TestSetCandidateData(t *testing.T) {
  function TestConfirmCandidate (line 238) | func TestConfirmCandidate(t *testing.T) {
  function TestCtrllAction (line 269) | func TestCtrllAction(t *testing.T) {
  function TestTabAction (line 279) | func TestTabAction(t *testing.T) {
  function TestEscAction (line 300) | func TestEscAction(t *testing.T) {
  function TestInputChar (line 311) | func TestInputChar(t *testing.T) {
  function TestMoveCursorForwardAndBackward (line 327) | func TestMoveCursorForwardAndBackward(t *testing.T) {
  function TestMoveCursorToTopAndEnd (line 351) | func TestMoveCursorToTopAndEnd(t *testing.T) {
  function getEngine (line 363) | func getEngine(j string, qs string) *Engine {

FILE: json_manager.go
  type JsonManager (line 12) | type JsonManager struct
    method Get (line 40) | func (jm *JsonManager) Get(q QueryInterface, confirm bool) (string, []...
    method GetPretty (line 52) | func (jm *JsonManager) GetPretty(q QueryInterface, confirm bool) (stri...
    method GetFilteredData (line 61) | func (jm *JsonManager) GetFilteredData(q QueryInterface, confirm bool)...
    method GetCandidateKeys (line 99) | func (jm *JsonManager) GetCandidateKeys(q QueryInterface) []string {
  function NewJsonManager (line 18) | func NewJsonManager(reader io.Reader) (*JsonManager, error) {
  function getItem (line 103) | func getItem(json *simplejson.Json, s string) (*simplejson.Json, bool) {
  function isEmptyJson (line 132) | func isEmptyJson(j *simplejson.Json) bool {

FILE: json_manager_test.go
  function TestNewJson (line 12) | func TestNewJson(t *testing.T) {
  function TestNewJsonWithError (line 32) | func TestNewJsonWithError(t *testing.T) {
  function TestGet (line 42) | func TestGet(t *testing.T) {
  function TestGetPretty (line 113) | func TestGetPretty(t *testing.T) {
  function TestGetItem (line 125) | func TestGetItem(t *testing.T) {
  function TestGetFilteredData (line 200) | func TestGetFilteredData(t *testing.T) {
  function TestGetFilteredDataWithMatchQuery (line 336) | func TestGetFilteredDataWithMatchQuery(t *testing.T) {
  function TestGetFilteredDataWithContainDots (line 376) | func TestGetFilteredDataWithContainDots(t *testing.T) {
  function TestGetCandidateKeys (line 412) | func TestGetCandidateKeys(t *testing.T) {
  function TestGetCurrentKeys (line 440) | func TestGetCurrentKeys(t *testing.T) {
  function TestIsEmptyJson (line 457) | func TestIsEmptyJson(t *testing.T) {

FILE: query.go
  type QueryInterface (line 10) | type QueryInterface interface
  type Query (line 32) | type Query struct
    method Get (line 49) | func (q *Query) Get() []rune {
    method GetChar (line 53) | func (q *Query) GetChar(idx int) rune {
    method Length (line 62) | func (q *Query) Length() int {
    method IndexOffset (line 66) | func (q *Query) IndexOffset(i int) int {
    method Set (line 76) | func (q *Query) Set(query []rune) []rune {
    method Insert (line 83) | func (q *Query) Insert(query []rune, idx int) []rune {
    method StringInsert (line 95) | func (q *Query) StringInsert(query string, idx int) string {
    method Add (line 99) | func (q *Query) Add(query []rune) []rune {
    method Delete (line 103) | func (q *Query) Delete(i int) []rune {
    method Clear (line 126) | func (q *Query) Clear() []rune {
    method GetKeywords (line 130) | func (q *Query) GetKeywords() [][]rune {
    method GetLastKeyword (line 185) | func (q *Query) GetLastKeyword() []rune {
    method StringGetLastKeyword (line 193) | func (q *Query) StringGetLastKeyword() string {
    method PopKeyword (line 197) | func (q *Query) PopKeyword() ([]rune, []rune) {
    method StringGet (line 216) | func (q *Query) StringGet() string {
    method StringSet (line 220) | func (q *Query) StringSet(query string) string {
    method StringAdd (line 224) | func (q *Query) StringAdd(query string) string {
    method StringGetKeywords (line 228) | func (q *Query) StringGetKeywords() []string {
    method StringPopKeyword (line 236) | func (q *Query) StringPopKeyword() (string, []rune) {
  function NewQuery (line 37) | func NewQuery(query []rune) *Query {
  function NewQueryWithString (line 45) | func NewQueryWithString(query string) *Query {
  function validate (line 241) | func validate(r []rune) bool {

FILE: query_test.go
  function TestValidate (line 9) | func TestValidate(t *testing.T) {
  function TestNewQuery (line 28) | func TestNewQuery(t *testing.T) {
  function TestNewQueryWithInvalidQuery (line 38) | func TestNewQueryWithInvalidQuery(t *testing.T) {
  function TestNewQueryWithString (line 48) | func TestNewQueryWithString(t *testing.T) {
  function TestNewQueryWithStringWithInvalidQuery (line 57) | func TestNewQueryWithStringWithInvalidQuery(t *testing.T) {
  function TestQueryGet (line 66) | func TestQueryGet(t *testing.T) {
  function TestQueryLength (line 75) | func TestQueryLength(t *testing.T) {
  function TestQueryIndexOffsetN (line 89) | func TestQueryIndexOffsetN(t *testing.T) {
  function TestQueryGetChar (line 109) | func TestQueryGetChar(t *testing.T) {
  function TestQuerySet (line 131) | func TestQuerySet(t *testing.T) {
  function TestQuerySetWithInvalidQuery (line 141) | func TestQuerySetWithInvalidQuery(t *testing.T) {
  function TestQueryAdd (line 150) | func TestQueryAdd(t *testing.T) {
  function TestQueryInsert (line 158) | func TestQueryInsert(t *testing.T) {
  function TestQueryStringInsert (line 171) | func TestQueryStringInsert(t *testing.T) {
  function TestQueryClear (line 184) | func TestQueryClear(t *testing.T) {
  function TestQueryDelete (line 193) | func TestQueryDelete(t *testing.T) {
  function TestGetKeywords (line 225) | func TestGetKeywords(t *testing.T) {
  function TestGetKeywordsWithDots (line 300) | func TestGetKeywordsWithDots(t *testing.T) {
  function TestGetLastKeyword (line 320) | func TestGetLastKeyword(t *testing.T) {
  function TestStringGetLastKeyword (line 340) | func TestStringGetLastKeyword(t *testing.T) {
  function TestPopKeyword (line 360) | func TestPopKeyword(t *testing.T) {
  function TestQueryStringGet (line 418) | func TestQueryStringGet(t *testing.T) {
  function TestQueryStringSet (line 427) | func TestQueryStringSet(t *testing.T) {
  function TestQueryStringAdd (line 436) | func TestQueryStringAdd(t *testing.T) {
  function TestStringGetKeywords (line 445) | func TestStringGetKeywords(t *testing.T) {
  function TestStringPopKeyword (line 469) | func TestStringPopKeyword(t *testing.T) {

FILE: suggestion.go
  type SuggestionInterface (line 11) | type SuggestionInterface interface
  type SuggestionDataType (line 16) | type SuggestionDataType
  constant UNKNOWN (line 19) | UNKNOWN SuggestionDataType = iota
  constant ARRAY (line 20) | ARRAY
  constant MAP (line 21) | MAP
  constant NUMBER (line 22) | NUMBER
  constant STRING (line 23) | STRING
  constant BOOL (line 24) | BOOL
  type Suggestion (line 27) | type Suggestion struct
    method Get (line 34) | func (s *Suggestion) Get(json *simplejson.Json, keyword string) []stri...
    method GetCandidateKeys (line 90) | func (s *Suggestion) GetCandidateKeys(json *simplejson.Json, keyword s...
    method GetCurrentType (line 141) | func (s *Suggestion) GetCurrentType(json *simplejson.Json) SuggestionD...
  function NewSuggestion (line 30) | func NewSuggestion() *Suggestion {
  function getCurrentKeys (line 113) | func getCurrentKeys(json *simplejson.Json) []string {

FILE: suggestion_test.go
  function TestNewSuggestion (line 12) | func TestNewSuggestion(t *testing.T) {
  function TestSuggestionGet (line 17) | func TestSuggestionGet(t *testing.T) {
  function TestSuggestionGetCurrentType (line 50) | func TestSuggestionGetCurrentType(t *testing.T) {
  function TestSuggestionGetCandidateKeys (line 64) | func TestSuggestionGetCandidateKeys(t *testing.T) {
  function TestSuggestionGetCandidateKeysWithDots (line 87) | func TestSuggestionGetCandidateKeysWithDots(t *testing.T) {
  function createJson (line 96) | func createJson(s string) *simplejson.Json {

FILE: terminal.go
  type Terminal (line 14) | type Terminal struct
    method Draw (line 46) | func (t *Terminal) Draw(attr *TerminalDrawAttributes) error {
    method drawFilterLine (line 87) | func (t *Terminal) drawFilterLine(qs string, complete string) error {
    method initColorizeFormatter (line 141) | func (t *Terminal) initColorizeFormatter() *jsoncolor.Formatter {
    method rowsToCells (line 191) | func (t *Terminal) rowsToCells(rows []string) ([][]termbox.Cell, error) {
    method drawCells (line 220) | func (t *Terminal) drawCells(x int, y int, cells []termbox.Cell) {
    method drawCandidates (line 234) | func (t *Terminal) drawCandidates(x int, y int, index int, candidates ...
  type TerminalDrawAttributes (line 22) | type TerminalDrawAttributes struct
  function NewTerminal (line 32) | func NewTerminal(prompt string, defaultY int, monochrome bool) *Terminal {
  type termboxSprintfFuncer (line 114) | type termboxSprintfFuncer struct
    method SprintfFunc (line 120) | func (tsf *termboxSprintfFuncer) SprintfFunc() func(format string, a ....
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (82K chars).
[
  {
    "path": ".circleci/config.yml",
    "chars": 716,
    "preview": "version: 2.1\njobs:\n  test:\n    docker:\n      - image: circleci/golang:latest\n    steps:\n      - checkout\n      - run: go"
  },
  {
    "path": ".gitignore",
    "chars": 329,
    "preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture spe"
  },
  {
    "path": ".goreleaser.yml",
    "chars": 821,
    "preview": "# This is an example goreleaser.yaml file with some sane defaults.\n# Make sure to check the documentation at http://gore"
  },
  {
    "path": "ChangeLog",
    "chars": 658,
    "preview": "ChangeLog\n=======\n\n### 0.7.2 - Jan 23 2017\n\nBugFix:\n* fix version number\n\n### 0.7.1 - Jan 5 2017\n\nFeatures:\n* scroll to "
  },
  {
    "path": "LICENSE",
    "chars": 1073,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016 simeji\n\nPermission is hereby granted, free of charge, to any person obtaining "
  },
  {
    "path": "README.md",
    "chars": 3996,
    "preview": "# jid\n\n[![Circle CI](https://circleci.com/gh/simeji/jid/tree/master.svg?style=shield)](https://circleci.com/gh/simeji/ji"
  },
  {
    "path": "engine.go",
    "chars": 6961,
    "preview": "package jid\n\nimport (\n\t\"io\"\n\t\"strings\"\n\n\ttermbox \"github.com/nsf/termbox-go\"\n)\n\nconst (\n\tDefaultY     int    = 1\n\tFilter"
  },
  {
    "path": "engine_test.go",
    "chars": 9144,
    "preview": "package jid\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewEngine(t *testing."
  },
  {
    "path": "go.mod",
    "chars": 763,
    "preview": "module github.com/simeji/jid\n\ngo 1.20\n\nrequire (\n\tgithub.com/bitly/go-simplejson v0.5.0\n\tgithub.com/mattn/go-runewidth v"
  },
  {
    "path": "go.sum",
    "chars": 4073,
    "preview": "github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=\ngithub.com/bitly/go-simplejson v0."
  },
  {
    "path": "json_manager.go",
    "chars": 3293,
    "preview": "package jid\n\nimport (\n\t\"github.com/bitly/go-simplejson\"\n\t\"github.com/pkg/errors\"\n\t\"io\"\n\t\"regexp\"\n\t\"strconv\"\n\t//\"strings\""
  },
  {
    "path": "json_manager_test.go",
    "chars": 12692,
    "preview": "package jid\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\tsimplejson \"github.com/bitly/go-simplejson\"\n\t\"github.com/stretchr/test"
  },
  {
    "path": "query.go",
    "chars": 5265,
    "preview": "package jid\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\trunewidth \"github.com/mattn/go-runewidth\"\n)\n\ntype QueryInterface interface "
  },
  {
    "path": "query_test.go",
    "chars": 11307,
    "preview": "package jid\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestValidate(t *testing.T) {\n\tvar assert"
  },
  {
    "path": "suggestion.go",
    "chars": 2941,
    "preview": "package jid\n\nimport (\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\n\tsimplejson \"github.com/bitly/go-simplejson\"\n)\n\ntype SuggestionInter"
  },
  {
    "path": "suggestion_test.go",
    "chars": 3558,
    "preview": "package jid\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\tsimplejson \"github.com/bitly/go-simplejson\"\n\t\"github.com/stretchr/test"
  },
  {
    "path": "terminal.go",
    "chars": 5863,
    "preview": "package jid\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"regexp\"\n\t\"strings\"\n\n\trunewidth \"github.com/mattn/go-runewidth\"\n\ttermbox \"github.com"
  }
]

About this extraction

This page contains the full source code of the simeji/jid GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (71.7 KB), approximately 23.5k tokens, and a symbol index with 154 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!