mirror of
https://github.com/antonmedv/fx
synced 2024-11-09 07:10:38 +00:00
242 lines
4.9 KiB
JavaScript
242 lines
4.9 KiB
JavaScript
'use strict'
|
|
const fs = require('fs')
|
|
const tty = require('tty')
|
|
const blessed = require('@medv/blessed')
|
|
const stringWidth = require('string-width')
|
|
const reduce = require('./reduce')
|
|
const print = require('./print')
|
|
|
|
module.exports = function start(filename, source) {
|
|
let json = source
|
|
let index = new Map()
|
|
const expanded = new Set()
|
|
expanded.add('') // Root of JSON
|
|
|
|
const ttyFd = fs.openSync('/dev/tty', 'r+')
|
|
|
|
const program = blessed.program({
|
|
input: tty.ReadStream(ttyFd),
|
|
output: tty.WriteStream(ttyFd),
|
|
})
|
|
|
|
const screen = blessed.screen({
|
|
program: program,
|
|
smartCSR: true,
|
|
fullUnicode: true,
|
|
})
|
|
|
|
const box = blessed.box({
|
|
parent: screen,
|
|
tags: false,
|
|
left: 0,
|
|
top: 0,
|
|
width: '100%',
|
|
height: '100%',
|
|
mouse: true,
|
|
keys: true,
|
|
vi: true,
|
|
ignoreArrows: true,
|
|
alwaysScroll: true,
|
|
scrollable: true,
|
|
})
|
|
|
|
const input = blessed.textbox({
|
|
parent: screen,
|
|
bottom: 0,
|
|
left: 0,
|
|
height: 1,
|
|
width: '100%',
|
|
})
|
|
|
|
screen.title = filename
|
|
input.hide()
|
|
|
|
screen.key(['escape', 'q', 'C-c'], function (ch, key) {
|
|
program.disableMouse() // If exit program immediately, stdin may still receive
|
|
setTimeout(() => process.exit(0), 10) // mouse events which will be printed in stdout.
|
|
})
|
|
|
|
screen.on('resize', function () {
|
|
render()
|
|
})
|
|
|
|
input.on('action', function () {
|
|
const code = input.getValue()
|
|
|
|
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()
|
|
})
|
|
|
|
input.on('update', function (code) {
|
|
if (code && code.length !== 0) {
|
|
try {
|
|
const pretender = reduce(source, code)
|
|
if (typeof pretender !== 'undefined' && typeof pretender !== 'function') {
|
|
json = pretender
|
|
}
|
|
} catch (e) {
|
|
// pass
|
|
}
|
|
}
|
|
render()
|
|
})
|
|
|
|
|
|
box.key('.', function () {
|
|
box.height = '100%-1'
|
|
input.show()
|
|
if (input.getValue() === '') {
|
|
input.setValue('.')
|
|
}
|
|
input.readInput()
|
|
render()
|
|
})
|
|
|
|
box.key('e', function () {
|
|
walk(json, path => expanded.size < 1000 && expanded.add(path))
|
|
render()
|
|
})
|
|
|
|
box.key('S-e', function () {
|
|
expanded.clear()
|
|
expanded.add('')
|
|
render()
|
|
})
|
|
|
|
box.key('up', function () {
|
|
program.showCursor()
|
|
|
|
const [n] = getLine(program.y)
|
|
const rest = [...index.keys()].filter(i => i < n)
|
|
if (rest.length > 0) {
|
|
const next = Math.max(...rest)
|
|
|
|
let y = box.getScreenNumber(next) - box.childBase
|
|
if (y <= 0) {
|
|
box.scroll(-1)
|
|
screen.render()
|
|
y = 0
|
|
}
|
|
|
|
const line = box.getScreenLine(y + box.childBase)
|
|
program.cursorPos(y, line.search(/\S/))
|
|
}
|
|
})
|
|
|
|
box.key('down', function () {
|
|
program.showCursor()
|
|
|
|
const [n] = getLine(program.y)
|
|
const rest = [...index.keys()].filter(i => i > n)
|
|
if (rest.length > 0) {
|
|
const next = Math.min(...rest)
|
|
|
|
let y = box.getScreenNumber(next) - box.childBase
|
|
if (y >= box.height) {
|
|
box.scroll(1)
|
|
screen.render()
|
|
y = box.height - 1
|
|
}
|
|
|
|
const line = box.getScreenLine(y + box.childBase)
|
|
program.cursorPos(y, line.search(/\S/))
|
|
}
|
|
})
|
|
|
|
box.key('right', function () {
|
|
const [n, line] = getLine(program.y)
|
|
program.showCursor()
|
|
program.cursorPos(program.y, line.search(/\S/))
|
|
const path = index.get(n)
|
|
if (!expanded.has(path)) {
|
|
expanded.add(path)
|
|
render()
|
|
}
|
|
})
|
|
|
|
box.key('left', function () {
|
|
const [n, line] = getLine(program.y)
|
|
program.showCursor()
|
|
program.cursorPos(program.y, line.search(/\S/))
|
|
const path = index.get(n)
|
|
if (expanded.has(path)) {
|
|
expanded.delete(path)
|
|
render()
|
|
}
|
|
})
|
|
|
|
box.on('click', function (mouse) {
|
|
const [n, line] = getLine(mouse.y)
|
|
if (mouse.x >= stringWidth(line)) {
|
|
return
|
|
}
|
|
|
|
program.hideCursor()
|
|
program.cursorPos(mouse.y, line.search(/\S/))
|
|
|
|
const path = index.get(n)
|
|
if (expanded.has(path)) {
|
|
expanded.delete(path)
|
|
} else {
|
|
expanded.add(path)
|
|
}
|
|
render()
|
|
})
|
|
|
|
function getLine(y) {
|
|
const dy = box.childBase + y
|
|
const n = box.getNumber(dy)
|
|
const line = box.getScreenLine(dy)
|
|
return [n, line]
|
|
}
|
|
|
|
function render() {
|
|
let content
|
|
[content, index] = print(json, {expanded})
|
|
|
|
if (typeof content === 'undefined') {
|
|
content = 'undefined'
|
|
}
|
|
|
|
box.setContent(content)
|
|
screen.render()
|
|
}
|
|
|
|
box.focus()
|
|
render()
|
|
}
|
|
|
|
function walk(v, cb, path = '') {
|
|
if (!v) {
|
|
return
|
|
}
|
|
|
|
if (Array.isArray(v)) {
|
|
cb(path)
|
|
let i = 0
|
|
for (let item of v) {
|
|
walk(item, cb, path + '[' + (i++) + ']')
|
|
}
|
|
}
|
|
|
|
if (typeof v === 'object' && v.constructor === Object) {
|
|
cb(path)
|
|
let i = 0
|
|
for (let [key, value] of Object.entries(v)) {
|
|
walk(value, cb, path + '.' + key)
|
|
}
|
|
}
|
|
}
|