178 lines
4.2 KiB
JavaScript
178 lines
4.2 KiB
JavaScript
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.getLinePos = getLinePos;
|
|
exports.getLine = getLine;
|
|
exports.getPrettyContext = getPrettyContext;
|
|
|
|
function findLineStarts(src) {
|
|
const ls = [0];
|
|
let offset = src.indexOf('\n');
|
|
|
|
while (offset !== -1) {
|
|
offset += 1;
|
|
ls.push(offset);
|
|
offset = src.indexOf('\n', offset);
|
|
}
|
|
|
|
return ls;
|
|
}
|
|
|
|
function getSrcInfo(cst) {
|
|
let lineStarts, src;
|
|
|
|
if (typeof cst === 'string') {
|
|
lineStarts = findLineStarts(cst);
|
|
src = cst;
|
|
} else {
|
|
if (Array.isArray(cst)) cst = cst[0];
|
|
|
|
if (cst && cst.context) {
|
|
if (!cst.lineStarts) cst.lineStarts = findLineStarts(cst.context.src);
|
|
lineStarts = cst.lineStarts;
|
|
src = cst.context.src;
|
|
}
|
|
}
|
|
|
|
return {
|
|
lineStarts,
|
|
src
|
|
};
|
|
}
|
|
/**
|
|
* @typedef {Object} LinePos - One-indexed position in the source
|
|
* @property {number} line
|
|
* @property {number} col
|
|
*/
|
|
|
|
/**
|
|
* Determine the line/col position matching a character offset.
|
|
*
|
|
* Accepts a source string or a CST document as the second parameter. With
|
|
* the latter, starting indices for lines are cached in the document as
|
|
* `lineStarts: number[]`.
|
|
*
|
|
* Returns a one-indexed `{ line, col }` location if found, or
|
|
* `undefined` otherwise.
|
|
*
|
|
* @param {number} offset
|
|
* @param {string|Document|Document[]} cst
|
|
* @returns {?LinePos}
|
|
*/
|
|
|
|
|
|
function getLinePos(offset, cst) {
|
|
if (typeof offset !== 'number' || offset < 0) return null;
|
|
const {
|
|
lineStarts,
|
|
src
|
|
} = getSrcInfo(cst);
|
|
if (!lineStarts || !src || offset > src.length) return null;
|
|
|
|
for (let i = 0; i < lineStarts.length; ++i) {
|
|
const start = lineStarts[i];
|
|
|
|
if (offset < start) {
|
|
return {
|
|
line: i,
|
|
col: offset - lineStarts[i - 1] + 1
|
|
};
|
|
}
|
|
|
|
if (offset === start) return {
|
|
line: i + 1,
|
|
col: 1
|
|
};
|
|
}
|
|
|
|
const line = lineStarts.length;
|
|
return {
|
|
line,
|
|
col: offset - lineStarts[line - 1] + 1
|
|
};
|
|
}
|
|
/**
|
|
* Get a specified line from the source.
|
|
*
|
|
* Accepts a source string or a CST document as the second parameter. With
|
|
* the latter, starting indices for lines are cached in the document as
|
|
* `lineStarts: number[]`.
|
|
*
|
|
* Returns the line as a string if found, or `null` otherwise.
|
|
*
|
|
* @param {number} line One-indexed line number
|
|
* @param {string|Document|Document[]} cst
|
|
* @returns {?string}
|
|
*/
|
|
|
|
|
|
function getLine(line, cst) {
|
|
const {
|
|
lineStarts,
|
|
src
|
|
} = getSrcInfo(cst);
|
|
if (!lineStarts || !(line >= 1) || line > lineStarts.length) return null;
|
|
const start = lineStarts[line - 1];
|
|
let end = lineStarts[line]; // undefined for last line; that's ok for slice()
|
|
|
|
while (end && end > start && src[end - 1] === '\n') --end;
|
|
|
|
return src.slice(start, end);
|
|
}
|
|
/**
|
|
* Pretty-print the starting line from the source indicated by the range `pos`
|
|
*
|
|
* Trims output to `maxWidth` chars while keeping the starting column visible,
|
|
* using `…` at either end to indicate dropped characters.
|
|
*
|
|
* Returns a two-line string (or `null`) with `\n` as separator; the second line
|
|
* will hold appropriately indented `^` marks indicating the column range.
|
|
*
|
|
* @param {Object} pos
|
|
* @param {LinePos} pos.start
|
|
* @param {LinePos} [pos.end]
|
|
* @param {string|Document|Document[]*} cst
|
|
* @param {number} [maxWidth=80]
|
|
* @returns {?string}
|
|
*/
|
|
|
|
|
|
function getPrettyContext({
|
|
start,
|
|
end
|
|
}, cst, maxWidth = 80) {
|
|
let src = getLine(start.line, cst);
|
|
if (!src) return null;
|
|
let {
|
|
col
|
|
} = start;
|
|
|
|
if (src.length > maxWidth) {
|
|
if (col <= maxWidth - 10) {
|
|
src = src.substr(0, maxWidth - 1) + '…';
|
|
} else {
|
|
const halfWidth = Math.round(maxWidth / 2);
|
|
if (src.length > col + halfWidth) src = src.substr(0, col + halfWidth - 1) + '…';
|
|
col -= src.length - maxWidth;
|
|
src = '…' + src.substr(1 - maxWidth);
|
|
}
|
|
}
|
|
|
|
let errLen = 1;
|
|
let errEnd = '';
|
|
|
|
if (end) {
|
|
if (end.line === start.line && col + (end.col - start.col) <= maxWidth + 1) {
|
|
errLen = end.col - start.col;
|
|
} else {
|
|
errLen = Math.min(src.length + 1, maxWidth) - col;
|
|
errEnd = '…';
|
|
}
|
|
}
|
|
|
|
const offset = col > 1 ? ' '.repeat(col - 1) : '';
|
|
const err = '^'.repeat(errLen);
|
|
return `${src}\n${offset}${err}${errEnd}`;
|
|
} |