diff --git a/config.js b/config.js index db5b1ee..e7ad30b 100644 --- a/config.js +++ b/config.js @@ -10,14 +10,15 @@ const list = { } module.exports = { - space: global.FX_STYLE_SPACE || 2, - null: global.FX_STYLE_NULL || chalk.grey.bold, - number: global.FX_STYLE_NUMBER || chalk.cyan.bold, - boolean: global.FX_STYLE_BOOLEAN || chalk.yellow.bold, - string: global.FX_STYLE_STRING || chalk.green.bold, - key: global.FX_STYLE_KEY || chalk.blue.bold, - bracket: global.FX_STYLE_BRACKET || noop, - comma: global.FX_STYLE_COMMA || noop, - colon: global.FX_STYLE_COLON || noop, - list: global.FX_STYLE_LIST || list, + space: global.FX_STYLE_SPACE || 2, + null: global.FX_STYLE_NULL || chalk.grey.bold, + number: global.FX_STYLE_NUMBER || chalk.cyan.bold, + boolean: global.FX_STYLE_BOOLEAN || chalk.yellow.bold, + string: global.FX_STYLE_STRING || chalk.green.bold, + key: global.FX_STYLE_KEY || chalk.blue.bold, + bracket: global.FX_STYLE_BRACKET || noop, + comma: global.FX_STYLE_COMMA || noop, + colon: global.FX_STYLE_COLON || noop, + highlight: global.FX_STYLE_HIGHLIGHT || chalk.black.bgYellow, + list: global.FX_STYLE_LIST || list, } diff --git a/find.js b/find.js new file mode 100644 index 0000000..721943b --- /dev/null +++ b/find.js @@ -0,0 +1,34 @@ +'use strict' + +function* find(v, regex, path = []) { + if (regex.test(path.join(''))) { + yield path + return + } + + if (typeof v === 'undefined' || v === null) { + return + } + + if (Array.isArray(v)) { + let i = 0 + for (let value of v) { + yield* find(value, regex, path.concat(['[' + i++ + ']'])) + } + return + } + + if (typeof v === 'object' && v.constructor === Object) { + const entries = Object.entries(v) + for (let [key, value] of entries) { + yield* find(value, regex, path.concat(['.' + key])) + } + return + } + + if (regex.test(v)) { + yield path + } +} + +module.exports = find diff --git a/fx.js b/fx.js index acb0473..82319f7 100644 --- a/fx.js +++ b/fx.js @@ -5,6 +5,7 @@ const blessed = require('@medv/blessed') const stringWidth = require('string-width') const reduce = require('./reduce') const print = require('./print') +const find = require('./find') const config = require('./config') module.exports = function start(filename, source) { @@ -20,6 +21,10 @@ module.exports = function start(filename, source) { const expanded = new Set() expanded.add('') + // Current search regexp and generator. + let highlight = null + let findGen = null + const ttyFd = fs.openSync('/dev/tty', 'r+') const program = blessed.program({ input: tty.ReadStream(ttyFd), @@ -55,6 +60,14 @@ module.exports = function start(filename, source) { width: '100%', }) + const search = blessed.textbox({ + parent: screen, + bottom: 0, + left: 0, + height: 1, + width: '100%', + }) + const autocomplete = blessed.list({ parent: screen, width: 6, @@ -67,6 +80,7 @@ module.exports = function start(filename, source) { screen.title = filename box.focus() input.hide() + search.hide() autocomplete.hide() screen.key(['escape', 'q', 'C-c'], function () { @@ -147,6 +161,48 @@ module.exports = function start(filename, source) { render() }) + search.on('submit', function (pattern) { + let regex + const m = pattern.match(/^\/(.*)\/?([gimuy]*)$/) + if (m) { + try { + regex = new RegExp(m[1], m[2]) + } catch (e) { + // Wrong regexp. + } + } + highlight = regex + + if (highlight) { + findGen = find(json, highlight) + findNext() + } else { + findGen = null + } + + search.hide() + search.setValue('') + + box.height = '100%' + box.focus() + + program.cursorPos(0, 0) + render() + }) + + search.on('cancel', function () { + highlight = null + + search.hide() + search.setValue('') + + box.height = '100%' + box.focus() + + program.cursorPos(0, 0) + render() + }) + box.key('.', function () { box.height = '100%-1' input.show() @@ -155,7 +211,15 @@ module.exports = function start(filename, source) { complete('.') } input.readInput() - render() + screen.render() + }) + + box.key('/', function () { + box.height = '100%-1' + search.show() + search.setValue('/') + search.readInput() + screen.render() }) box.key('e', function () { @@ -186,6 +250,10 @@ module.exports = function start(filename, source) { } }) + box.key('n', function () { + findNext() + }) + box.key(['up', 'k'], function () { program.showCursor() let rest = [...index.keys()] @@ -362,12 +430,37 @@ module.exports = function start(filename, source) { if (code === '') { json = source } + + findGen = find(json, highlight) render() } + function findNext() { + if (!findGen) { + return + } + const {value: path, done} = findGen.next() + if (!done) { + let value = '' + for (let p of path) { + expanded.add(value += p) + } +console.error(value) + render() + + for (let [k, v] of index) { + if (v === value) { + const y = box.getScreenNumber(k) + box.scrollTo(y) + screen.render() + } + } + } + } + function render() { let content - [content, index] = print(json, {expanded}) + [content, index] = print(json, {expanded, highlight}) if (typeof content === 'undefined') { content = 'undefined' diff --git a/print.js b/print.js index d0e165a..b9a3ea4 100644 --- a/print.js +++ b/print.js @@ -3,10 +3,21 @@ const indent = require('indent-string') const config = require('./config') function print(input, options = {}) { - const {expanded} = options + const {expanded, highlight} = options const index = new Map() let row = 0 + function format(text, style) { + if (!highlight) { + return style(text) + } + return text + .replace(highlight, s => '' + s + '') + .split(//g) + .map((s, i) => i % 2 !== 0 ? config.highlight(s) : style(s)) + .join('') + } + function doPrint(v, path = '') { index.set(row, path) @@ -20,20 +31,20 @@ function print(input, options = {}) { } if (v === null) { - return config.null(v) + return format('null', config.null) } if (typeof v === 'number' && Number.isFinite(v)) { - return config.number(v) + return format(v.toString(), config.number) } if (typeof v === 'boolean') { - return config.boolean(v) + return format(v.toString(), config.boolean) } if (typeof v === 'string') { - return config.string(JSON.stringify(v)) + return format(JSON.stringify(v), config.string) } if (Array.isArray(v)) { @@ -71,7 +82,7 @@ function print(input, options = {}) { output += eol() let i = 0 for (let [key, value] of entries) { - const part = config.key(JSON.stringify(key)) + config.colon(':') + ' ' + doPrint(value, path + '.' + key) + const part = format(JSON.stringify(key), config.key) + config.colon(':') + ' ' + doPrint(value, path + '.' + key) output += indent(part, config.space) output += i++ < len - 1 ? config.comma(',') : '' output += eol()