156 lines
4.3 KiB
JavaScript
156 lines
4.3 KiB
JavaScript
/**
|
|
* line-column - Convert efficiently index to/from line-column in a string
|
|
* @module lineColumn
|
|
* @license MIT
|
|
*/
|
|
"use strict";
|
|
|
|
var isArray = require("isarray");
|
|
var isObject = require("isobject");
|
|
var slice = Array.prototype.slice;
|
|
|
|
module.exports = LineColumnFinder;
|
|
|
|
/**
|
|
* Finder for index and line-column from given string.
|
|
*
|
|
* You can call this without `new` operator as it returns an instance anyway.
|
|
*
|
|
* @class
|
|
* @param {string} str - A string to be parsed.
|
|
* @param {Object|number} [options] - Options.
|
|
* This can be an index in the string for shorthand of `lineColumn(str, index)`.
|
|
* @param {number} [options.origin=1] - The origin value of line and column.
|
|
*/
|
|
function LineColumnFinder(str, options) {
|
|
if (!(this instanceof LineColumnFinder)) {
|
|
if (typeof options === "number") {
|
|
return (new LineColumnFinder(str)).fromIndex(options);
|
|
}
|
|
return new LineColumnFinder(str, options);
|
|
}
|
|
|
|
this.str = str || "";
|
|
this.lineToIndex = buildLineToIndex(this.str);
|
|
|
|
options = options || {};
|
|
this.origin = typeof options.origin === "undefined" ? 1 : options.origin;
|
|
}
|
|
|
|
/**
|
|
* Find line and column from index in the string.
|
|
*
|
|
* @param {number} index - Index in the string. (0-origin)
|
|
* @return {Object|null}
|
|
* Found line number and column number in object `{ line: X, col: Y }`.
|
|
* If the given index is out of range, it returns `null`.
|
|
*/
|
|
LineColumnFinder.prototype.fromIndex = function (index) {
|
|
if (index < 0 || index >= this.str.length || isNaN(index)) {
|
|
return null;
|
|
}
|
|
|
|
var line = findLowerIndexInRangeArray(index, this.lineToIndex);
|
|
return {
|
|
line: line + this.origin,
|
|
col: index - this.lineToIndex[line] + this.origin
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Find index from line and column in the string.
|
|
*
|
|
* @param {number|Object|Array} line - Line number in the string.
|
|
* This can be an Object of `{ line: X, col: Y }`, or
|
|
* an Array of `[line, col]`.
|
|
* @param {number} [column] - Column number in the string.
|
|
* This must be omitted or undefined when Object or Array is given
|
|
* to the first argument.
|
|
* @return {number}
|
|
* Found index in the string. (always 0-origin)
|
|
* If the given line or column is out of range, it returns `-1`.
|
|
*/
|
|
LineColumnFinder.prototype.toIndex = function (line, column) {
|
|
if (typeof column === "undefined") {
|
|
if (isArray(line) && line.length >= 2) {
|
|
return this.toIndex(line[0], line[1]);
|
|
}
|
|
if (isObject(line) && "line" in line && ("col" in line || "column" in line)) {
|
|
return this.toIndex(line.line, ("col" in line ? line.col : line.column));
|
|
}
|
|
return -1;
|
|
}
|
|
if (isNaN(line) || isNaN(column)) {
|
|
return -1;
|
|
}
|
|
|
|
line -= this.origin;
|
|
column -= this.origin;
|
|
|
|
if (line >= 0 && column >= 0 && line < this.lineToIndex.length) {
|
|
var lineIndex = this.lineToIndex[line];
|
|
var nextIndex = (
|
|
line === this.lineToIndex.length - 1
|
|
? this.str.length
|
|
: this.lineToIndex[line + 1]
|
|
);
|
|
|
|
if (column < nextIndex - lineIndex) {
|
|
return lineIndex + column;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Build an array of indexes of each line from a string.
|
|
*
|
|
* @private
|
|
* @param str {string} An input string.
|
|
* @return {number[]} Built array of indexes. The key is line number.
|
|
*/
|
|
function buildLineToIndex(str) {
|
|
var lines = str.split("\n"),
|
|
lineToIndex = new Array(lines.length),
|
|
index = 0;
|
|
|
|
for (var i = 0, l = lines.length; i < l; i++) {
|
|
lineToIndex[i] = index;
|
|
index += lines[i].length + /* "\n".length */ 1;
|
|
}
|
|
return lineToIndex;
|
|
}
|
|
|
|
/**
|
|
* Find a lower-bound index of a value in a sorted array of ranges.
|
|
*
|
|
* Assume `arr = [0, 5, 10, 15, 20]` and
|
|
* this returns `1` for `value = 7` (5 <= value < 10),
|
|
* and returns `3` for `value = 18` (15 <= value < 20).
|
|
*
|
|
* @private
|
|
* @param arr {number[]} An array of values representing ranges.
|
|
* @param value {number} A value to be searched.
|
|
* @return {number} Found index. If not found `-1`.
|
|
*/
|
|
function findLowerIndexInRangeArray(value, arr) {
|
|
if (value >= arr[arr.length - 1]) {
|
|
return arr.length - 1;
|
|
}
|
|
|
|
var min = 0, max = arr.length - 2, mid;
|
|
while (min < max) {
|
|
mid = min + ((max - min) >> 1);
|
|
|
|
if (value < arr[mid]) {
|
|
max = mid - 1;
|
|
} else if (value >= arr[mid + 1]) {
|
|
min = mid + 1;
|
|
} else { // value >= arr[mid] && value < arr[mid + 1]
|
|
min = mid;
|
|
break;
|
|
}
|
|
}
|
|
return min;
|
|
}
|