2018-11-09 18:57:24 +00:00
|
|
|
'use strict'
|
2019-01-30 05:21:39 +00:00
|
|
|
const fs = require('fs')
|
|
|
|
const tty = require('tty')
|
2018-11-09 18:57:24 +00:00
|
|
|
const blessed = require('@medv/blessed')
|
2018-11-02 18:24:56 +00:00
|
|
|
const stringWidth = require('string-width')
|
2018-11-09 18:57:24 +00:00
|
|
|
const reduce = require('./reduce')
|
|
|
|
const print = require('./print')
|
2018-12-14 17:48:29 +00:00
|
|
|
const find = require('./find')
|
2018-12-02 16:02:31 +00:00
|
|
|
const config = require('./config')
|
2018-11-09 18:57:24 +00:00
|
|
|
|
|
|
|
module.exports = function start(filename, source) {
|
2018-12-08 09:38:59 +00:00
|
|
|
// Current rendered object on a screen.
|
2018-11-09 18:57:24 +00:00
|
|
|
let json = source
|
2018-12-08 09:38:59 +00:00
|
|
|
|
|
|
|
// Contains map from row number to expand path.
|
|
|
|
// Example: {0: '', 1: '.foo', 2: '.foo[0]'}
|
2018-11-09 18:57:24 +00:00
|
|
|
let index = new Map()
|
2018-12-08 09:38:59 +00:00
|
|
|
|
|
|
|
// Contains expanded paths. Example: ['', '.foo']
|
|
|
|
// Empty string represents root path.
|
2018-11-09 18:57:24 +00:00
|
|
|
const expanded = new Set()
|
2018-12-08 09:38:59 +00:00
|
|
|
expanded.add('')
|
2018-11-02 18:24:56 +00:00
|
|
|
|
2018-12-14 17:48:29 +00:00
|
|
|
// Current search regexp and generator.
|
|
|
|
let highlight = null
|
|
|
|
let findGen = null
|
2018-12-14 18:13:39 +00:00
|
|
|
let currentPath = null
|
2018-12-14 17:48:29 +00:00
|
|
|
|
2019-01-30 05:21:39 +00:00
|
|
|
// Reopen tty
|
2019-01-30 03:33:13 +00:00
|
|
|
let ttyReadStream
|
|
|
|
let ttyWriteStream
|
2019-01-30 05:21:39 +00:00
|
|
|
if (process.platform === 'win32') {
|
|
|
|
const cfs = process.binding('fs')
|
|
|
|
ttyReadStream = tty.ReadStream(cfs.open('conin$', fs.constants.O_RDWR | fs.constants.O_EXCL, 0o666))
|
|
|
|
ttyWriteStream = tty.WriteStream(cfs.open('conout$', fs.constants.O_RDWR | fs.constants.O_EXCL, 0o666))
|
|
|
|
} else {
|
|
|
|
const ttyFd = fs.openSync('/dev/tty', 'r+')
|
|
|
|
ttyReadStream = tty.ReadStream(ttyFd)
|
|
|
|
ttyWriteStream = tty.WriteStream(ttyFd)
|
|
|
|
}
|
2019-01-30 02:34:25 +00:00
|
|
|
|
2018-11-02 18:24:56 +00:00
|
|
|
const program = blessed.program({
|
2019-01-30 02:34:25 +00:00
|
|
|
input: ttyReadStream,
|
2019-01-30 05:21:39 +00:00
|
|
|
output: ttyWriteStream,
|
2018-11-02 18:24:56 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
const screen = blessed.screen({
|
|
|
|
program: program,
|
|
|
|
smartCSR: true,
|
|
|
|
fullUnicode: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
const box = blessed.box({
|
|
|
|
parent: screen,
|
2018-11-08 02:48:06 +00:00
|
|
|
tags: false,
|
2018-11-02 18:24:56 +00:00
|
|
|
left: 0,
|
|
|
|
top: 0,
|
|
|
|
width: '100%',
|
|
|
|
height: '100%',
|
2018-11-09 18:57:24 +00:00
|
|
|
mouse: true,
|
2018-11-02 18:24:56 +00:00
|
|
|
keys: true,
|
|
|
|
vi: true,
|
2018-11-09 18:57:24 +00:00
|
|
|
ignoreArrows: true,
|
2018-11-02 18:24:56 +00:00
|
|
|
alwaysScroll: true,
|
|
|
|
scrollable: true,
|
|
|
|
})
|
|
|
|
|
2018-11-09 18:57:24 +00:00
|
|
|
const input = blessed.textbox({
|
|
|
|
parent: screen,
|
|
|
|
bottom: 0,
|
|
|
|
left: 0,
|
|
|
|
height: 1,
|
|
|
|
width: '100%',
|
|
|
|
})
|
2018-11-02 18:24:56 +00:00
|
|
|
|
2018-12-14 17:48:29 +00:00
|
|
|
const search = blessed.textbox({
|
|
|
|
parent: screen,
|
|
|
|
bottom: 0,
|
|
|
|
left: 0,
|
|
|
|
height: 1,
|
|
|
|
width: '100%',
|
|
|
|
})
|
|
|
|
|
2018-12-15 07:07:39 +00:00
|
|
|
const statusBar = blessed.box({
|
|
|
|
parent: screen,
|
|
|
|
tags: false,
|
|
|
|
bottom: 0,
|
|
|
|
left: 0,
|
|
|
|
height: 1,
|
|
|
|
width: '100%',
|
|
|
|
})
|
|
|
|
|
2018-12-02 12:43:11 +00:00
|
|
|
const autocomplete = blessed.list({
|
|
|
|
parent: screen,
|
|
|
|
width: 6,
|
|
|
|
height: 7,
|
|
|
|
left: 1,
|
|
|
|
bottom: 1,
|
2018-12-02 16:02:31 +00:00
|
|
|
style: config.list,
|
2018-12-02 12:43:11 +00:00
|
|
|
})
|
|
|
|
|
2018-11-09 18:57:24 +00:00
|
|
|
screen.title = filename
|
2018-12-02 12:43:11 +00:00
|
|
|
box.focus()
|
2018-11-09 18:57:24 +00:00
|
|
|
input.hide()
|
2018-12-14 17:48:29 +00:00
|
|
|
search.hide()
|
2018-12-15 07:07:39 +00:00
|
|
|
statusBar.hide()
|
2018-12-02 12:43:11 +00:00
|
|
|
autocomplete.hide()
|
2018-11-09 18:57:24 +00:00
|
|
|
|
2018-12-02 12:43:11 +00:00
|
|
|
screen.key(['escape', 'q', 'C-c'], function () {
|
2018-12-01 05:17:38 +00:00
|
|
|
program.disableMouse() // If exit program immediately, stdin may still receive
|
|
|
|
setTimeout(() => process.exit(0), 10) // mouse events which will be printed in stdout.
|
2018-11-02 18:24:56 +00:00
|
|
|
})
|
2018-11-09 18:57:24 +00:00
|
|
|
|
2018-12-01 05:17:38 +00:00
|
|
|
screen.on('resize', function () {
|
|
|
|
render()
|
|
|
|
})
|
|
|
|
|
2018-12-02 12:43:11 +00:00
|
|
|
input.on('submit', function () {
|
|
|
|
if (autocomplete.hidden) {
|
2018-12-15 06:19:27 +00:00
|
|
|
const code = input.getValue()
|
|
|
|
if (/^\//.test(code)) {
|
|
|
|
// Forgive a mistake to the user. This looks like user wanted to search something.
|
|
|
|
apply('')
|
|
|
|
applyPattern(code)
|
|
|
|
} else {
|
|
|
|
apply(code)
|
|
|
|
}
|
2018-12-02 12:43:11 +00:00
|
|
|
} else {
|
|
|
|
// Autocomplete selected
|
|
|
|
let code = input.getValue()
|
2018-12-02 12:52:22 +00:00
|
|
|
let replace = autocomplete.getSelected()
|
2018-12-11 08:15:12 +00:00
|
|
|
if (/^[a-z]\w*$/i.test(replace)) {
|
2018-12-02 12:52:22 +00:00
|
|
|
replace = '.' + replace
|
|
|
|
} else {
|
2018-12-02 13:25:57 +00:00
|
|
|
replace = `["${replace}"]`
|
2018-12-02 12:52:22 +00:00
|
|
|
}
|
|
|
|
code = code.replace(/\.\w*$/, replace)
|
2018-11-09 18:57:24 +00:00
|
|
|
|
2018-12-02 12:43:11 +00:00
|
|
|
input.setValue(code)
|
|
|
|
autocomplete.hide()
|
|
|
|
update(code)
|
|
|
|
|
|
|
|
// Keep editing code
|
|
|
|
input.readInput()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
input.on('cancel', function () {
|
|
|
|
if (autocomplete.hidden) {
|
2018-12-15 06:19:27 +00:00
|
|
|
const code = input.getValue()
|
|
|
|
apply(code)
|
2018-11-09 18:57:24 +00:00
|
|
|
} else {
|
2018-12-02 12:43:11 +00:00
|
|
|
// Autocomplete not selected
|
|
|
|
autocomplete.hide()
|
|
|
|
screen.render()
|
|
|
|
|
|
|
|
// Keep editing code
|
|
|
|
input.readInput()
|
2018-11-09 18:57:24 +00:00
|
|
|
}
|
2018-11-02 18:24:56 +00:00
|
|
|
})
|
|
|
|
|
2018-11-09 18:57:24 +00:00
|
|
|
input.on('update', function (code) {
|
2018-12-02 12:43:11 +00:00
|
|
|
update(code)
|
|
|
|
complete(code)
|
|
|
|
})
|
|
|
|
|
|
|
|
input.key('up', function () {
|
|
|
|
if (!autocomplete.hidden) {
|
|
|
|
autocomplete.up()
|
|
|
|
screen.render()
|
2018-11-09 18:57:24 +00:00
|
|
|
}
|
2018-12-02 12:43:11 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
input.key('down', function () {
|
|
|
|
if (!autocomplete.hidden) {
|
|
|
|
autocomplete.down()
|
|
|
|
screen.render()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
input.key('C-u', function () {
|
|
|
|
input.setValue('')
|
|
|
|
update('')
|
2018-11-09 18:57:24 +00:00
|
|
|
render()
|
|
|
|
})
|
2018-11-02 18:24:56 +00:00
|
|
|
|
2018-12-02 12:43:11 +00:00
|
|
|
input.key('C-w', function () {
|
|
|
|
let code = input.getValue()
|
2018-12-02 12:52:22 +00:00
|
|
|
code = code.replace(/[\.\[][^\.\[]*$/, '')
|
2018-12-02 12:43:11 +00:00
|
|
|
input.setValue(code)
|
|
|
|
update(code)
|
|
|
|
render()
|
|
|
|
})
|
2018-11-09 18:57:24 +00:00
|
|
|
|
2018-12-14 17:48:29 +00:00
|
|
|
search.on('submit', function (pattern) {
|
2018-12-15 06:19:27 +00:00
|
|
|
applyPattern(pattern)
|
2018-12-14 17:48:29 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
search.on('cancel', function () {
|
|
|
|
highlight = null
|
2018-12-14 18:13:39 +00:00
|
|
|
currentPath = null
|
2018-12-14 17:48:29 +00:00
|
|
|
|
|
|
|
search.hide()
|
|
|
|
search.setValue('')
|
|
|
|
|
|
|
|
box.height = '100%'
|
|
|
|
box.focus()
|
|
|
|
|
|
|
|
program.cursorPos(0, 0)
|
|
|
|
render()
|
|
|
|
})
|
|
|
|
|
2018-12-01 05:17:38 +00:00
|
|
|
box.key('.', function () {
|
2018-12-15 07:07:39 +00:00
|
|
|
hideStatusBar()
|
2018-11-09 18:57:24 +00:00
|
|
|
box.height = '100%-1'
|
|
|
|
input.show()
|
2018-12-01 05:17:38 +00:00
|
|
|
if (input.getValue() === '') {
|
|
|
|
input.setValue('.')
|
2018-12-02 12:43:11 +00:00
|
|
|
complete('.')
|
2018-12-01 05:17:38 +00:00
|
|
|
}
|
2018-11-09 18:57:24 +00:00
|
|
|
input.readInput()
|
2018-12-14 17:48:29 +00:00
|
|
|
screen.render()
|
|
|
|
})
|
|
|
|
|
|
|
|
box.key('/', function () {
|
2018-12-15 07:07:39 +00:00
|
|
|
hideStatusBar()
|
2018-12-14 17:48:29 +00:00
|
|
|
box.height = '100%-1'
|
|
|
|
search.show()
|
|
|
|
search.setValue('/')
|
|
|
|
search.readInput()
|
|
|
|
screen.render()
|
2018-11-09 18:57:24 +00:00
|
|
|
})
|
2018-11-02 18:24:56 +00:00
|
|
|
|
|
|
|
box.key('e', function () {
|
2018-12-15 07:07:39 +00:00
|
|
|
hideStatusBar()
|
2018-12-02 12:43:11 +00:00
|
|
|
expanded.clear()
|
2018-12-21 07:00:18 +00:00
|
|
|
for (let path of dfs(json)) {
|
2018-12-21 06:46:16 +00:00
|
|
|
if (expanded.size < 1000) {
|
|
|
|
expanded.add(path)
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2018-11-02 18:24:56 +00:00
|
|
|
render()
|
|
|
|
})
|
2018-11-09 18:57:24 +00:00
|
|
|
|
2018-11-02 18:24:56 +00:00
|
|
|
box.key('S-e', function () {
|
2018-12-15 07:07:39 +00:00
|
|
|
hideStatusBar()
|
2018-11-02 18:24:56 +00:00
|
|
|
expanded.clear()
|
|
|
|
expanded.add('')
|
|
|
|
render()
|
2018-12-08 09:38:59 +00:00
|
|
|
|
|
|
|
// Make sure cursor stay on JSON object.
|
|
|
|
const [n] = getLine(program.y)
|
|
|
|
if (typeof n === 'undefined' || !index.has(n)) {
|
|
|
|
// No line under cursor
|
|
|
|
let rest = [...index.keys()]
|
|
|
|
if (rest.length > 0) {
|
|
|
|
const next = Math.max(...rest)
|
|
|
|
let y = box.getScreenNumber(next) - box.childBase
|
|
|
|
if (y <= 0) {
|
|
|
|
y = 0
|
|
|
|
}
|
|
|
|
const line = box.getScreenLine(y + box.childBase)
|
|
|
|
program.cursorPos(y, line.search(/\S/))
|
|
|
|
}
|
|
|
|
}
|
2018-11-02 18:24:56 +00:00
|
|
|
})
|
|
|
|
|
2018-12-14 17:48:29 +00:00
|
|
|
box.key('n', function () {
|
2018-12-15 07:07:39 +00:00
|
|
|
hideStatusBar()
|
2018-12-14 17:48:29 +00:00
|
|
|
findNext()
|
|
|
|
})
|
|
|
|
|
2018-12-04 19:34:54 +00:00
|
|
|
box.key(['up', 'k'], function () {
|
2018-12-15 07:07:39 +00:00
|
|
|
hideStatusBar()
|
2018-11-09 18:57:24 +00:00
|
|
|
program.showCursor()
|
2018-12-08 09:38:59 +00:00
|
|
|
let rest = [...index.keys()]
|
2018-11-09 18:57:24 +00:00
|
|
|
|
2018-12-01 17:29:44 +00:00
|
|
|
const [n] = getLine(program.y)
|
2018-12-08 09:38:59 +00:00
|
|
|
if (typeof n !== 'undefined') {
|
|
|
|
rest = rest.filter(i => i < n)
|
|
|
|
}
|
|
|
|
|
2018-11-09 18:57:24 +00:00
|
|
|
if (rest.length > 0) {
|
|
|
|
const next = Math.max(...rest)
|
2018-12-01 17:29:44 +00:00
|
|
|
|
|
|
|
let y = box.getScreenNumber(next) - box.childBase
|
2018-11-09 18:57:24 +00:00
|
|
|
if (y <= 0) {
|
|
|
|
box.scroll(-1)
|
|
|
|
screen.render()
|
2018-12-01 17:29:44 +00:00
|
|
|
y = 0
|
2018-11-02 18:24:56 +00:00
|
|
|
}
|
2018-12-01 17:29:44 +00:00
|
|
|
|
|
|
|
const line = box.getScreenLine(y + box.childBase)
|
|
|
|
program.cursorPos(y, line.search(/\S/))
|
2018-11-02 18:24:56 +00:00
|
|
|
}
|
2018-11-09 18:57:24 +00:00
|
|
|
})
|
2018-11-02 18:24:56 +00:00
|
|
|
|
2018-12-04 19:34:54 +00:00
|
|
|
box.key(['down', 'j'], function () {
|
2018-12-15 07:07:39 +00:00
|
|
|
hideStatusBar()
|
2018-11-09 18:57:24 +00:00
|
|
|
program.showCursor()
|
2018-12-08 09:38:59 +00:00
|
|
|
let rest = [...index.keys()]
|
2018-11-09 18:57:24 +00:00
|
|
|
|
2018-12-01 17:29:44 +00:00
|
|
|
const [n] = getLine(program.y)
|
2018-12-08 09:38:59 +00:00
|
|
|
if (typeof n !== 'undefined') {
|
|
|
|
rest = rest.filter(i => i > n)
|
|
|
|
}
|
|
|
|
|
2018-11-09 18:57:24 +00:00
|
|
|
if (rest.length > 0) {
|
|
|
|
const next = Math.min(...rest)
|
2018-12-01 17:29:44 +00:00
|
|
|
|
|
|
|
let y = box.getScreenNumber(next) - box.childBase
|
2018-11-09 18:57:24 +00:00
|
|
|
if (y >= box.height) {
|
|
|
|
box.scroll(1)
|
|
|
|
screen.render()
|
2018-12-01 17:29:44 +00:00
|
|
|
y = box.height - 1
|
2018-11-02 18:24:56 +00:00
|
|
|
}
|
2018-12-01 17:29:44 +00:00
|
|
|
|
|
|
|
const line = box.getScreenLine(y + box.childBase)
|
|
|
|
program.cursorPos(y, line.search(/\S/))
|
2018-11-02 18:24:56 +00:00
|
|
|
}
|
2018-11-09 18:57:24 +00:00
|
|
|
})
|
2018-11-02 18:24:56 +00:00
|
|
|
|
2018-12-04 19:34:54 +00:00
|
|
|
box.key(['right', 'l'], function () {
|
2018-12-15 07:07:39 +00:00
|
|
|
hideStatusBar()
|
2018-12-01 17:29:44 +00:00
|
|
|
const [n, line] = getLine(program.y)
|
2018-11-09 18:57:24 +00:00
|
|
|
program.showCursor()
|
2018-12-01 17:29:44 +00:00
|
|
|
program.cursorPos(program.y, line.search(/\S/))
|
|
|
|
const path = index.get(n)
|
2018-11-09 18:57:24 +00:00
|
|
|
if (!expanded.has(path)) {
|
|
|
|
expanded.add(path)
|
|
|
|
render()
|
2018-11-02 18:24:56 +00:00
|
|
|
}
|
2018-11-09 18:57:24 +00:00
|
|
|
})
|
2018-11-02 18:24:56 +00:00
|
|
|
|
2018-12-04 19:34:54 +00:00
|
|
|
box.key(['left', 'h'], function () {
|
2018-12-15 07:07:39 +00:00
|
|
|
hideStatusBar()
|
2018-12-01 17:29:44 +00:00
|
|
|
const [n, line] = getLine(program.y)
|
2018-11-09 18:57:24 +00:00
|
|
|
program.showCursor()
|
2018-12-01 17:29:44 +00:00
|
|
|
program.cursorPos(program.y, line.search(/\S/))
|
|
|
|
const path = index.get(n)
|
2018-11-09 18:57:24 +00:00
|
|
|
if (expanded.has(path)) {
|
|
|
|
expanded.delete(path)
|
|
|
|
render()
|
2018-11-02 18:24:56 +00:00
|
|
|
}
|
2018-11-09 18:57:24 +00:00
|
|
|
})
|
2018-11-02 18:24:56 +00:00
|
|
|
|
|
|
|
box.on('click', function (mouse) {
|
2018-12-15 07:07:39 +00:00
|
|
|
hideStatusBar()
|
2018-12-01 17:29:44 +00:00
|
|
|
const [n, line] = getLine(mouse.y)
|
2018-11-02 18:24:56 +00:00
|
|
|
if (mouse.x >= stringWidth(line)) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-12-01 17:29:44 +00:00
|
|
|
program.hideCursor()
|
|
|
|
program.cursorPos(mouse.y, line.search(/\S/))
|
2018-12-02 12:43:11 +00:00
|
|
|
autocomplete.hide()
|
2018-11-09 18:57:24 +00:00
|
|
|
|
2018-12-01 17:29:44 +00:00
|
|
|
const path = index.get(n)
|
2018-11-02 18:24:56 +00:00
|
|
|
if (expanded.has(path)) {
|
|
|
|
expanded.delete(path)
|
|
|
|
} else {
|
|
|
|
expanded.add(path)
|
|
|
|
}
|
|
|
|
render()
|
|
|
|
})
|
|
|
|
|
2018-12-15 07:07:39 +00:00
|
|
|
box.on('scroll', function () {
|
|
|
|
hideStatusBar()
|
|
|
|
})
|
|
|
|
|
2018-12-01 17:29:44 +00:00
|
|
|
function getLine(y) {
|
|
|
|
const dy = box.childBase + y
|
|
|
|
const n = box.getNumber(dy)
|
|
|
|
const line = box.getScreenLine(dy)
|
2018-12-08 09:38:59 +00:00
|
|
|
if (typeof line === 'undefined') {
|
|
|
|
return [n, '']
|
|
|
|
}
|
2018-12-01 17:29:44 +00:00
|
|
|
return [n, line]
|
|
|
|
}
|
|
|
|
|
2018-12-15 06:19:27 +00:00
|
|
|
function apply(code) {
|
2018-12-02 12:43:11 +00:00
|
|
|
if (code && code.length !== 0) {
|
|
|
|
try {
|
|
|
|
json = reduce(source, code)
|
|
|
|
} catch (e) {
|
|
|
|
// pass
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
box.height = '100%'
|
|
|
|
input.hide()
|
|
|
|
json = source
|
|
|
|
}
|
|
|
|
box.focus()
|
|
|
|
program.cursorPos(0, 0)
|
|
|
|
render()
|
|
|
|
}
|
|
|
|
|
|
|
|
function complete(inputCode) {
|
|
|
|
const match = inputCode.match(/\.(\w*)$/)
|
|
|
|
const code = /^\.\w*$/.test(inputCode) ? '.' : inputCode.replace(/\.\w*$/, '')
|
|
|
|
|
|
|
|
let json
|
|
|
|
try {
|
|
|
|
json = reduce(source, code)
|
|
|
|
} catch (e) {
|
|
|
|
}
|
|
|
|
|
|
|
|
if (match) {
|
|
|
|
if (typeof json === 'object' && json.constructor === Object) {
|
|
|
|
const keys = Object.keys(json).filter(key => key.startsWith(match[1]))
|
|
|
|
|
2018-12-02 16:16:25 +00:00
|
|
|
// Hide if there is nothing to show or
|
|
|
|
// don't show if there is complete match.
|
|
|
|
if (keys.length === 0 || (keys.length === 1 && keys[0] === match[1])) {
|
2018-12-02 12:43:11 +00:00
|
|
|
autocomplete.hide()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
autocomplete.width = Math.max(...keys.map(key => key.length)) + 1
|
|
|
|
autocomplete.height = Math.min(7, keys.length)
|
|
|
|
autocomplete.left = Math.min(
|
|
|
|
screen.width - autocomplete.width,
|
|
|
|
code.length === 1 ? 1 : code.length + 1
|
|
|
|
)
|
|
|
|
|
|
|
|
let selectFirst = autocomplete.items.length !== keys.length
|
|
|
|
autocomplete.setItems(keys)
|
|
|
|
|
|
|
|
if (selectFirst) {
|
|
|
|
autocomplete.select(autocomplete.items.length - 1)
|
|
|
|
}
|
|
|
|
if (autocomplete.hidden) {
|
|
|
|
autocomplete.show()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
autocomplete.clearItems()
|
|
|
|
autocomplete.hide()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function update(code) {
|
|
|
|
if (code && code.length !== 0) {
|
|
|
|
try {
|
|
|
|
const pretender = reduce(source, code)
|
2018-12-15 06:19:27 +00:00
|
|
|
if (
|
|
|
|
typeof pretender !== 'undefined'
|
|
|
|
&& typeof pretender !== 'function'
|
|
|
|
&& !(pretender instanceof RegExp)
|
|
|
|
) {
|
2018-12-02 12:43:11 +00:00
|
|
|
json = pretender
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// pass
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (code === '') {
|
|
|
|
json = source
|
|
|
|
}
|
2018-12-14 17:48:29 +00:00
|
|
|
|
2019-02-09 11:12:22 +00:00
|
|
|
if (highlight) {
|
|
|
|
findGen = find(json, highlight)
|
|
|
|
}
|
2018-12-02 12:43:11 +00:00
|
|
|
render()
|
|
|
|
}
|
|
|
|
|
2018-12-15 06:19:27 +00:00
|
|
|
function applyPattern(pattern) {
|
|
|
|
let regex
|
|
|
|
let m = pattern.match(/^\/(.*)\/([gimuy]*)$/)
|
|
|
|
if (m) {
|
|
|
|
try {
|
|
|
|
regex = new RegExp(m[1], m[2])
|
|
|
|
} catch (e) {
|
|
|
|
// Wrong regexp.
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
m = pattern.match(/^\/(.*)$/)
|
|
|
|
if (m) {
|
|
|
|
try {
|
|
|
|
regex = new RegExp(m[1], 'gi')
|
|
|
|
} catch (e) {
|
|
|
|
// Wrong regexp.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
highlight = regex
|
|
|
|
|
2019-02-09 11:12:22 +00:00
|
|
|
search.hide()
|
|
|
|
|
2018-12-15 06:19:27 +00:00
|
|
|
if (highlight) {
|
|
|
|
findGen = find(json, highlight)
|
|
|
|
findNext()
|
|
|
|
} else {
|
|
|
|
findGen = null
|
|
|
|
currentPath = null
|
|
|
|
}
|
|
|
|
search.setValue('')
|
|
|
|
|
|
|
|
box.height = '100%'
|
|
|
|
box.focus()
|
|
|
|
|
|
|
|
program.cursorPos(0, 0)
|
|
|
|
render()
|
|
|
|
}
|
|
|
|
|
2018-12-14 17:48:29 +00:00
|
|
|
function findNext() {
|
|
|
|
if (!findGen) {
|
|
|
|
return
|
|
|
|
}
|
2018-12-15 07:07:39 +00:00
|
|
|
|
2018-12-14 17:48:29 +00:00
|
|
|
const {value: path, done} = findGen.next()
|
2018-12-15 07:07:39 +00:00
|
|
|
|
|
|
|
if (done) {
|
|
|
|
showStatusBar('Pattern not found')
|
|
|
|
} else {
|
|
|
|
|
2018-12-14 18:13:39 +00:00
|
|
|
currentPath = ''
|
2018-12-14 17:48:29 +00:00
|
|
|
for (let p of path) {
|
2018-12-14 18:13:39 +00:00
|
|
|
expanded.add(currentPath += p)
|
2018-12-14 17:48:29 +00:00
|
|
|
}
|
|
|
|
render()
|
|
|
|
|
|
|
|
for (let [k, v] of index) {
|
2018-12-14 18:13:39 +00:00
|
|
|
if (v === currentPath) {
|
2018-12-15 05:48:37 +00:00
|
|
|
let y = box.getScreenNumber(k)
|
|
|
|
|
|
|
|
// Scroll one line up for better view and make sure it's not negative.
|
|
|
|
if (--y < 0) {
|
|
|
|
y = 0
|
|
|
|
}
|
|
|
|
|
2018-12-14 17:48:29 +00:00
|
|
|
box.scrollTo(y)
|
|
|
|
screen.render()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-15 07:07:39 +00:00
|
|
|
function showStatusBar(status) {
|
|
|
|
statusBar.show()
|
|
|
|
statusBar.setContent(config.statusBar(` ${status} `))
|
|
|
|
screen.render()
|
|
|
|
}
|
|
|
|
|
|
|
|
function hideStatusBar() {
|
|
|
|
if (!statusBar.hidden) {
|
|
|
|
statusBar.hide()
|
|
|
|
statusBar.setContent('')
|
|
|
|
screen.render()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-02 18:24:56 +00:00
|
|
|
function render() {
|
2018-11-09 18:57:24 +00:00
|
|
|
let content
|
2018-12-14 18:13:39 +00:00
|
|
|
[content, index] = print(json, {expanded, highlight, currentPath})
|
2018-11-09 18:57:24 +00:00
|
|
|
|
|
|
|
if (typeof content === 'undefined') {
|
|
|
|
content = 'undefined'
|
|
|
|
}
|
2018-11-02 18:24:56 +00:00
|
|
|
|
|
|
|
box.setContent(content)
|
|
|
|
screen.render()
|
|
|
|
}
|
|
|
|
|
|
|
|
render()
|
|
|
|
}
|
2018-11-09 18:57:24 +00:00
|
|
|
|
2018-12-21 07:00:18 +00:00
|
|
|
function* bfs(json) {
|
|
|
|
const queue = [[json, '']]
|
|
|
|
|
2019-01-30 03:33:13 +00:00
|
|
|
while (queue.length > 0) {
|
2018-12-21 07:00:18 +00:00
|
|
|
const [v, path] = queue.shift()
|
|
|
|
|
|
|
|
if (!v) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Array.isArray(v)) {
|
|
|
|
yield path
|
|
|
|
let i = 0
|
|
|
|
for (let item of v) {
|
|
|
|
const p = path + '[' + (i++) + ']'
|
|
|
|
queue.push([item, p])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof v === 'object' && v.constructor === Object) {
|
|
|
|
yield path
|
|
|
|
for (let [key, value] of Object.entries(v)) {
|
|
|
|
const p = path + '.' + key
|
|
|
|
queue.push([value, p])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function* dfs(v, path = '') {
|
2018-11-09 18:57:24 +00:00
|
|
|
if (!v) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Array.isArray(v)) {
|
2018-12-21 06:46:16 +00:00
|
|
|
yield path
|
2018-11-09 18:57:24 +00:00
|
|
|
let i = 0
|
|
|
|
for (let item of v) {
|
2018-12-21 07:00:18 +00:00
|
|
|
yield* dfs(item, path + '[' + (i++) + ']')
|
2018-11-09 18:57:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof v === 'object' && v.constructor === Object) {
|
2018-12-21 06:46:16 +00:00
|
|
|
yield path
|
2018-11-09 18:57:24 +00:00
|
|
|
for (let [key, value] of Object.entries(v)) {
|
2018-12-21 07:00:18 +00:00
|
|
|
yield* dfs(value, path + '.' + key)
|
2018-11-09 18:57:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-12-21 07:00:18 +00:00
|
|
|
|