UNPKG

3.12 kBJavaScriptView Raw
1"use strict"
2
3const lineEndingsRe = /\r\n|\r|\n/g
4function 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
15function 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
27function 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
41module.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}