You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fx/fx.js

264 lines
5.4 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 test = blessed.box({
parent: screen,
hidden: true,
tags: false,
width: '100%',
})
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) {
6 years ago
program.disableMouse() // If exit program immediately, stdin may still receive
setTimeout(() => process.exit(0), 10) // mouse events which will be printed in stdout.
})
6 years ago
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)
6 years ago
if (typeof pretender !== 'undefined' && typeof pretender !== 'function') {
json = pretender
}
} catch (e) {
// pass
}
}
render()
})
6 years ago
box.key('.', function () {
box.height = '100%-1'
input.show()
6 years ago
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 pos = box.childBase + program.y
const rest = [...index.keys()].filter(i => i < pos)
if (rest.length > 0) {
const next = Math.max(...rest)
const y = next - box.childBase
if (y <= 0) {
box.scroll(-1)
screen.render()
}
const line = box.getScreenLines()[next]
const x = line.search(/\S/)
program.cursorPos(y, x)
}
})
box.key('down', function () {
program.showCursor()
const pos = box.childBase + program.y
const rest = [...index.keys()].filter(i => i > pos)
if (rest.length > 0) {
const next = Math.min(...rest)
const y = next - box.childBase
if (y >= box.height) {
box.scroll(1)
screen.render()
}
const line = box.getScreenLines()[next]
const x = line.search(/\S/)
program.cursorPos(y, x)
}
})
box.key('right', function () {
program.showCursor()
const pos = box.childBase + program.y
const path = index.get(pos)
if (!expanded.has(path)) {
expanded.add(path)
render()
}
})
box.key('left', function () {
program.showCursor()
const pos = box.childBase + program.y
const path = index.get(pos)
if (expanded.has(path)) {
expanded.delete(path)
render()
}
})
box.on('click', function (mouse) {
program.hideCursor()
const pos = box.childBase + mouse.y
const line = box.getScreenLines()[pos]
if (mouse.x >= stringWidth(line)) {
return
}
const x = line.search(/\S/)
program.cursorPos(mouse.y, x)
const path = index.get(pos)
if (expanded.has(path)) {
expanded.delete(path)
} else {
expanded.add(path)
}
render()
})
function render() {
let content
[content, index] = print(json, expanded)
if (typeof content === 'undefined') {
content = 'undefined'
}
// TODO: Move to own fork of blessed.
let row = 0
for (let line of content.split('\n')) {
if (stringWidth(line) > box.width) {
test.setContent(line)
const pad = test.getScreenLines().length - 1
const update = new Map()
for (let [i, path] of index.entries()) {
if (i > row) {
index.delete(i)
update.set(i + pad, path)
}
}
row += pad
for (let [i, path] of update.entries()) {
index.set(i, path)
}
}
row++
}
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)
}
}
}