1 | "use strict"
|
2 |
|
3 | const lineEndingsRe = /\r\n|\r|\n/g
|
4 | function lineStarts(str) {
|
5 | const result = [0]
|
6 | lineEndingsRe.lastIndex = 0
|
7 | while (true) {
|
8 | const match = lineEndingsRe.exec(str)
|
9 | if (!match) break
|
10 | result.push(lineEndingsRe.lastIndex)
|
11 | }
|
12 | return result
|
13 | }
|
14 |
|
15 | function locationToIndex(location, lineStarts) {
|
16 | if (
|
17 | !location.line ||
|
18 | location.line < 0 ||
|
19 | !location.column ||
|
20 | location.column < 0
|
21 | ) {
|
22 | throw new Error("Invalid location")
|
23 | }
|
24 | return lineStarts[location.line - 1] + location.column - 1
|
25 | }
|
26 |
|
27 | function indexToLocation(index, lineStarts) {
|
28 | if (index < 0) throw new Error("Invalid index")
|
29 |
|
30 | let line = 0
|
31 | while (line + 1 < lineStarts.length && lineStarts[line + 1] <= index) {
|
32 | line += 1
|
33 | }
|
34 |
|
35 | return {
|
36 | line: line + 1,
|
37 | column: index - lineStarts[line] + 1,
|
38 | }
|
39 | }
|
40 |
|
41 | module.exports = class TransformableString {
|
42 | constructor(original) {
|
43 | this._original = original
|
44 | this._blocks = []
|
45 | this._lineStarts = lineStarts(original)
|
46 | this._cache = null
|
47 | }
|
48 |
|
49 | _compute() {
|
50 | if (!this._cache) {
|
51 | let result = ""
|
52 | let index = 0
|
53 | for (const block of this._blocks) {
|
54 | result += this._original.slice(index, block.from) + block.str
|
55 | index = block.to
|
56 | }
|
57 | result += this._original.slice(index)
|
58 | this._cache = {
|
59 | lineStarts: lineStarts(result),
|
60 | result,
|
61 | }
|
62 | }
|
63 | return this._cache
|
64 | }
|
65 |
|
66 | getOriginalLine(n) {
|
67 | if (n < 1 || n > this._lineStarts.length) {
|
68 | throw new Error("Invalid line number")
|
69 | }
|
70 | return this._original
|
71 | .slice(this._lineStarts[n - 1], this._lineStarts[n])
|
72 | .replace(lineEndingsRe, "")
|
73 | }
|
74 |
|
75 | toString() {
|
76 | return this._compute().result
|
77 | }
|
78 |
|
79 | replace(from, to, str) {
|
80 | this._cache = null
|
81 | if (from > to || from < 0 || to > this._original.length) {
|
82 | throw new Error("Invalid slice indexes")
|
83 | }
|
84 | const newBlock = { from, to, str }
|
85 | if (
|
86 | !this._blocks.length ||
|
87 | this._blocks[this._blocks.length - 1].to <= from
|
88 | ) {
|
89 | this._blocks.push(newBlock)
|
90 | } else {
|
91 | const index = this._blocks.findIndex((other) => other.to > from)
|
92 | if (this._blocks[index].from < to) throw new Error("Can't replace slice")
|
93 | this._blocks.splice(index, 0, newBlock)
|
94 | }
|
95 | }
|
96 |
|
97 | originalIndex(index) {
|
98 | let block
|
99 | for (block of this._blocks) {
|
100 | if (index < block.from) break
|
101 |
|
102 | if (index < block.from + block.str.length) {
|
103 | return
|
104 | } else {
|
105 | index += block.to - block.from - block.str.length
|
106 | }
|
107 | }
|
108 | if (index < 0 || index > this._original.length) {
|
109 | throw new Error("Invalid index")
|
110 | }
|
111 | if (index == this._original.length) {
|
112 | if (block.to && block.to === this._original.length) {
|
113 | return block.from + block.str.length
|
114 | }
|
115 | return this._original.length
|
116 | }
|
117 | return index
|
118 | }
|
119 |
|
120 | originalLocation(location) {
|
121 | const index = locationToIndex(location, this._compute().lineStarts)
|
122 | const originalIndex = this.originalIndex(index)
|
123 | if (originalIndex !== undefined) {
|
124 | return indexToLocation(originalIndex, this._lineStarts)
|
125 | }
|
126 | }
|
127 | }
|