UNPKG

bnash

Version:

A command-line editor for bash or compatible shells, aiming to provide great UX and productivity

147 lines (131 loc) 3.75 kB
const { registerLineDecorator } = require('../editor') const { NodeType, NodeTypeNames, builtins, parseBash, traverseAST } = require('../parser') const { getOption, setDefaultOptions } = require('../startup') const colors = require('../colors') const env = require('../env') function makeHL(type, loc) { return { type, start: loc.start.char, end: loc.end.char } } function getCommandType(cmd) { if (builtins.includes(cmd)) return NodeType.builtin let whichOut = env.which(cmd) if (!whichOut) return NodeType.commandError // This `which` output does not work in all bash environments ¯\_(ツ)_/¯ if (whichOut.endsWith('shell built-in command')) return NodeType.builtin if (whichOut.includes(': aliased to ')) return NodeType.alias return NodeType.program } function getSuffixType(s, line) { if (s.type == 'Redirect') return NodeType.redirect if (s.text[0] == '$') return NodeType.environment if (s.text[0] == '-') return NodeType.option let ch = line[s.loc.start.char] if (ch == '"' || ch == "'") return NodeType.quote return NodeType.parameter } function highlightNode(node, hls, line) { if (node.type != 'Command') return if (node.prefix) for (let p of node.prefix) hls.push(makeHL(NodeType.assignment, p.loc)) if (node.name) hls.push(makeHL(getCommandType(node.name.text), node.name.loc)) if (node.suffix) for (let s of node.suffix) hls.push(makeHL(getSuffixType(s, line), s.loc)) } function highlightComment(line, ast, hls) { let extra = line.substr(ast.loc.end.char + 1) if (extra.trim().startsWith('#')) { let pos = ast.loc.end.char + 1 hls.push({ type: NodeType.comment, start: line.indexOf('#', pos), end: line.length - 1 }) } } function highlight(line) { let ast = parseBash(line) let hls = [] if (!ast) return hls traverseAST(ast, n => { highlightNode(n, hls, line) }) highlightComment(line, ast, hls) return hls } function colorize(line, hls, colorFunc = applyColor) { if (hls.length == 0) return line let pos = 0 let result = '' for (let hl of hls) { if (pos < hl.start) { result += line.substring(pos, hl.start) } let chunk = line.substring(hl.start, hl.end + 1) result += colorFunc(chunk, hl) pos = hl.end + 1 } lastHL = hls.pop() result += line.substr(lastHL.end + 1) return result } let hlColors = null function applyColor(chunk, hl) { let colorName = hlColors[NodeTypeNames[hl.type]] return colors.colorize(colorName, chunk) } function setDefaults() { let defaultColors = { unknown: 'reset', program: 'green', builtin: 'green', alias: 'green', commandError: 'redBright', assignment: 'magentaBright', redirect: 'whiteBright', parameter: 'cyan', environment: 'magenta', option: 'cyanBright', quote: 'yellow', comment: 'blue' } setDefaultOptions('colors.syntaxHighlight', defaultColors) hlColors = getOption('colors.syntaxHighlight') } function start() { registerLineDecorator((plainLine, decoratedLine) => { if (plainLine == '') { //getCommandTypeFromCache = memoize(getCommandType) return decoratedLine } let hls = highlight(plainLine) return colorize(plainLine, hls) }) setDefaults() } // Exports used only for testing module.exports = { NodeType, highlight, colorize, start }